@@ -4,6 +4,7 @@ namespace SuggestBoxLib
44 using System . Globalization ;
55 using System . Windows ;
66 using System . Windows . Controls ;
7+ using System . Windows . Data ;
78
89 /// <summary>
910 /// Enum for specifying where the ellipsis should appear.
@@ -50,16 +51,20 @@ public class PathTrimmingTextBlock : TextBlock
5051 DependencyProperty . Register ( "Path" ,
5152 typeof ( string ) ,
5253 typeof ( PathTrimmingTextBlock ) ,
53- new UIPropertyMetadata ( string . Empty ) ) ;
54+ new UIPropertyMetadata ( string . Empty , OnPathChanged ) ) ;
5455
5556 /// <summary>
5657 /// Implements the backing store of the <see cref="ShowElipses"/> dependency property.
5758 /// </summary>
5859 public static readonly DependencyProperty ShowElipsesProperty =
5960 DependencyProperty . Register ( "ShowElipses" , typeof ( EllipsisPlacement ) ,
60- typeof ( PathTrimmingTextBlock ) , new PropertyMetadata ( EllipsisPlacement . None ) ) ;
61+ typeof ( PathTrimmingTextBlock ) , new PropertyMetadata ( EllipsisPlacement . None , OnShowElipsesChanged ) ) ;
6162
62- private FrameworkElement mContainer ;
63+ private FrameworkElement _Container ;
64+
65+ private readonly TextBlock _MeasureBlock ;
66+ private double _lastMeasuredWith ;
67+ private string _lastString ;
6368 #endregion fields
6469
6570 #region constructor
@@ -68,7 +73,13 @@ public class PathTrimmingTextBlock : TextBlock
6873 /// </summary>
6974 public PathTrimmingTextBlock ( )
7075 {
71- this . mContainer = null ;
76+ _MeasureBlock = new TextBlock ( ) ;
77+ BindOneWay ( this , "Style" , _MeasureBlock , TextBlock . StyleProperty ) ;
78+ BindOneWay ( this , "FontWeight" , _MeasureBlock , TextBlock . FontWeightProperty ) ;
79+ BindOneWay ( this , "FontStyle" , _MeasureBlock , TextBlock . FontStyleProperty ) ;
80+ BindOneWay ( this , "FontStretch" , _MeasureBlock , TextBlock . FontStretchProperty ) ;
81+ BindOneWay ( this , "FontSize" , _MeasureBlock , TextBlock . FontSizeProperty ) ;
82+ BindOneWay ( this , "FontFamily" , _MeasureBlock , TextBlock . FontFamilyProperty ) ;
7283
7384 this . Loaded += new RoutedEventHandler ( this . PathTrimmingTextBlock_Loaded ) ;
7485 this . Unloaded += new RoutedEventHandler ( this . PathTrimmingTextBlock_Unloaded ) ;
@@ -97,6 +108,37 @@ public EllipsisPlacement ShowElipses
97108 #endregion properties
98109
99110 #region methods
111+ /// <summary>
112+ /// Updates the trimmed text based on current measurements, text input, and Show Ellipses parameter.
113+ /// </summary>
114+ protected virtual void UpdateText ( )
115+ {
116+ if ( _Container != null )
117+ this . Text = this . GetTrimmedPath ( this . Path , _Container . ActualWidth , this . ShowElipses ) ;
118+ //// else
119+ //// throw new InvalidOperationException("PathTrimmingTextBlock must have a container such as a Grid.");
120+ }
121+
122+ /// <summary>
123+ /// Update measured and trimmed text output if text input has changed.
124+ /// </summary>
125+ /// <param name="d"></param>
126+ /// <param name="e"></param>
127+ private static void OnPathChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e )
128+ {
129+ ( d as PathTrimmingTextBlock ) . UpdateText ( ) ;
130+ }
131+
132+ /// <summary>
133+ /// Update measured and trimmed text output if text trimming option has changed.
134+ /// </summary>
135+ /// <param name="d"></param>
136+ /// <param name="e"></param>
137+ private static void OnShowElipsesChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e )
138+ {
139+ ( d as PathTrimmingTextBlock ) . UpdateText ( ) ;
140+ }
141+
100142 /// <summary>
101143 /// Textblock is constructed and start its live - lets attach to the
102144 /// size changed event handler of the containing parent.
@@ -109,7 +151,7 @@ private void PathTrimmingTextBlock_Loaded(object sender, RoutedEventArgs e)
109151 if ( this . Parent is FrameworkElement )
110152 {
111153 p = ( FrameworkElement ) this . Parent ;
112- this . mContainer = p ;
154+ _Container = p ;
113155 }
114156 else
115157 {
@@ -126,15 +168,15 @@ private void PathTrimmingTextBlock_Loaded(object sender, RoutedEventArgs e)
126168 break ;
127169 }
128170
129- this . mContainer = p ;
171+ _Container = p ;
130172 }
131173 }
132174
133- if ( this . mContainer != null )
175+ if ( _Container != null )
134176 {
135- this . mContainer . SizeChanged += new SizeChangedEventHandler ( this . container_SizeChanged ) ;
177+ _Container . SizeChanged += new SizeChangedEventHandler ( this . container_SizeChanged ) ;
136178
137- this . Text = this . GetTrimmedPath ( this . mContainer . ActualWidth , this . ShowElipses ) ;
179+ UpdateText ( ) ;
138180 }
139181 //// else
140182 //// throw new InvalidOperationException("PathTrimmingTextBlock must have a container such as a Grid.");
@@ -147,8 +189,8 @@ private void PathTrimmingTextBlock_Loaded(object sender, RoutedEventArgs e)
147189 /// <param name="e"></param>
148190 private void PathTrimmingTextBlock_Unloaded ( object sender , RoutedEventArgs e )
149191 {
150- if ( this . mContainer != null )
151- this . mContainer . SizeChanged -= this . container_SizeChanged ;
192+ if ( _Container != null )
193+ _Container . SizeChanged -= this . container_SizeChanged ;
152194 }
153195
154196 /// <summary>
@@ -158,8 +200,7 @@ private void PathTrimmingTextBlock_Unloaded(object sender, RoutedEventArgs e)
158200 /// <param name="e"></param>
159201 private void container_SizeChanged ( object sender , SizeChangedEventArgs e )
160202 {
161- if ( this . mContainer != null )
162- this . Text = this . GetTrimmedPath ( this . mContainer . ActualWidth , this . ShowElipses ) ;
203+ UpdateText ( ) ;
163204 }
164205
165206 /// <summary>
@@ -170,71 +211,83 @@ private void container_SizeChanged(object sender, SizeChangedEventArgs e)
170211 private void PathTrimmingTextBlock_IsVisibleChanged ( object sender ,
171212 DependencyPropertyChangedEventArgs e )
172213 {
173- if ( this . mContainer != null && ( bool ) e . NewValue == true )
174- {
175- this . Text = this . GetTrimmedPath ( this . mContainer . ActualWidth , this . ShowElipses ) ;
176- }
214+ if ( ( bool ) e . NewValue == true )
215+ UpdateText ( ) ;
177216 }
178217
179218 /// <summary>
180219 /// Compute the text to display (with ellipsis) that fits the ActualWidth of the container
181220 /// </summary>
182- /// <param name="width"></param>
221+ /// <param name="inputString">Input string to measure whether it fits into container width or not.</param>
222+ /// <param name="containerWidth">ActualWidth restriction by container element (eg.: Grid)</param>
183223 /// <param name="placement"></param>
184224 /// <returns></returns>
185- private string GetTrimmedPath ( double width ,
225+ private string GetTrimmedPath ( string inputString ,
226+ double containerWidth ,
186227 EllipsisPlacement placement )
187228 {
229+ // Lets not measure the same thing twice
230+ if ( Math . Abs ( _lastMeasuredWith - containerWidth ) <= 0.5 &&
231+ string . Compare ( _lastString , inputString ) == 0 )
232+ return _MeasureBlock . Text ;
233+
234+ _lastMeasuredWith = containerWidth ;
235+ _lastString = inputString ;
236+
188237 string filename = string . Empty ;
189238 string directory = string . Empty ;
190239
191240 switch ( placement )
192241 {
193242 // We don't want no ellipses to be shown for a string shortener
194243 case EllipsisPlacement . None :
195- return this . Path ;
244+ _MeasureBlock . Text = inputString ;
245+ return inputString ;
196246
197247 // Try to show a nice ellipses somewhere in the middle of the string
198248 case EllipsisPlacement . Center :
199249 try
200250 {
201- if ( string . IsNullOrEmpty ( this . Path ) == false )
251+ if ( string . IsNullOrEmpty ( inputString ) == false )
202252 {
203- if ( this . Path . Contains ( string . Empty + System . IO . Path . DirectorySeparatorChar ) )
253+ if ( inputString . Contains ( string . Empty + System . IO . Path . DirectorySeparatorChar ) )
204254 {
205255 // Lets try to display the file name with priority
206- filename = System . IO . Path . GetFileName ( this . Path ) ;
207- directory = System . IO . Path . GetDirectoryName ( this . Path ) ;
256+ filename = System . IO . Path . GetFileName ( inputString ) ;
257+ directory = System . IO . Path . GetDirectoryName ( inputString ) ;
208258 }
209259 else
210260 {
211261 // Cut this right in the middle since it does not seem to hold path info
212- int len = this . Path . Length ;
213- int firstLen = this . Path . Length / 2 ;
214- filename = this . Path . Substring ( 0 , firstLen ) ;
262+ int len = inputString . Length ;
263+ int firstLen = inputString . Length / 2 ;
264+ filename = inputString . Substring ( 0 , firstLen ) ;
215265
216- if ( this . Path . Length >= ( firstLen + 1 ) )
217- directory = this . Path . Substring ( firstLen ) ;
266+ if ( inputString . Length >= ( firstLen + 1 ) )
267+ directory = inputString . Substring ( firstLen ) ;
218268 }
219269 }
220270 else
271+ {
272+ _MeasureBlock . Text = string . Empty ;
221273 return string . Empty ;
274+ }
222275 }
223276 catch ( Exception )
224277 {
225- directory = this . Path ;
278+ directory = inputString ;
226279 filename = string . Empty ;
227280 }
228281 break ;
229282
230283 case EllipsisPlacement . Left :
231- directory = this . Path ;
284+ directory = inputString ;
232285 filename = string . Empty ;
233286 break ;
234287
235288 case EllipsisPlacement . Right :
236289 directory = string . Empty ;
237- filename = this . Path ;
290+ filename = inputString ;
238291 break ;
239292
240293 default :
@@ -248,20 +301,12 @@ private string GetTrimmedPath(double width,
248301 if ( placement == EllipsisPlacement . Left )
249302 indexString = 1 ;
250303
251- TextBlock block = new TextBlock ( ) ;
252- block . Style = this . Style ;
253- block . FontWeight = this . FontWeight ;
254- block . FontStyle = this . FontStyle ;
255- block . FontStretch = this . FontStretch ;
256- block . FontSize = this . FontSize ;
257- block . FontFamily = this . FontFamily ;
258-
259304 do
260305 {
261- block . Text = FormatWith ( placement , directory , filename ) ;
262- block . Measure ( new Size ( double . PositiveInfinity , double . PositiveInfinity ) ) ;
306+ _MeasureBlock . Text = FormatWith ( placement , directory , filename ) ;
307+ _MeasureBlock . Measure ( new Size ( double . PositiveInfinity , double . PositiveInfinity ) ) ;
263308
264- widthOK = block . DesiredSize . Width < width ;
309+ widthOK = _MeasureBlock . DesiredSize . Width < containerWidth ;
265310
266311 if ( widthOK == false )
267312 {
@@ -273,7 +318,10 @@ private string GetTrimmedPath(double width,
273318 filename = filename . Substring ( indexString , filename . Length - 1 ) ;
274319 }
275320 else
321+ {
322+ _MeasureBlock . Text = string . Empty ;
276323 return string . Empty ;
324+ }
277325 }
278326 else
279327 {
@@ -285,12 +333,12 @@ private string GetTrimmedPath(double width,
285333 while ( widthOK == false ) ;
286334
287335 if ( changedWidth == false )
288- return this . Path ;
289-
290- if ( block != null ) // Optimize for speed
291- return block . Text ;
336+ {
337+ _MeasureBlock . Text = inputString ;
338+ return inputString ;
339+ }
292340
293- return FormatWith ( placement , directory , filename ) ;
341+ return _MeasureBlock . Text ;
294342 }
295343
296344 /// <summary>
@@ -324,6 +372,19 @@ public static string FormatWith(EllipsisPlacement placing,
324372
325373 return string . Format ( CultureInfo . InvariantCulture , formatString , args ) ;
326374 }
375+
376+ private void BindOneWay ( FrameworkElement soureOfBinding ,
377+ string sourcePathName ,
378+ FrameworkElement destinationOfBinding ,
379+ DependencyProperty destinationProperty )
380+ {
381+ Binding binding = new Binding ( ) ;
382+ binding . Path = new PropertyPath ( sourcePathName ) ;
383+ binding . Source = soureOfBinding ;
384+ binding . UpdateSourceTrigger = UpdateSourceTrigger . PropertyChanged ;
385+ binding . Mode = BindingMode . OneWay ;
386+ BindingOperations . SetBinding ( destinationOfBinding , destinationProperty , binding ) ;
387+ }
327388 #endregion methods
328389 }
329390}
0 commit comments