//----------------------------------------------------------------------- // // Copyright (c) 2009 Pixel Lab // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // Read more about the MIT License for software at it's wikipedia // page here: http://en.wikipedia.org/wiki/MIT_License // // If you happen to use this code in a project, please email me // robby@nerdplusart.com and let me know how and where you use // it. That is, after all, the fun part of sharing code: knowing // that it gets used. // // Find out more about PixelLab at http://pixellab.cc or read my // blog at http://nerdplusart.com. // //----------------------------------------------------------------------- namespace PixelLab { using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Markup; /// /// A simple text control that truncates the text to ellipses when there /// is insufficient room to display all of the text. /// public class DynamicTextBlock : ContentControl { #region Text (DependencyProperty) /// /// Gets or sets the Text DependencyProperty. This is the text that will be displayed. /// public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(DynamicTextBlock), new PropertyMetadata(null, new PropertyChangedCallback(OnTextChanged))); private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((DynamicTextBlock)d).OnTextChanged(e); } protected virtual void OnTextChanged(DependencyPropertyChangedEventArgs e) { this.InvalidateMeasure(); } #endregion #region TextWrapping (DependencyProperty) /// /// Gets or sets the TextWrapping property. This corresponds to TextBlock.TextWrapping. /// public TextWrapping TextWrapping { get { return (TextWrapping)GetValue(TextWrappingProperty); } set { SetValue(TextWrappingProperty, value); } } public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register("TextWrapping", typeof(TextWrapping), typeof(DynamicTextBlock), new PropertyMetadata(TextWrapping.NoWrap, new PropertyChangedCallback(OnTextWrappingChanged))); private static void OnTextWrappingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((DynamicTextBlock)d).OnTextWrappingChanged(e); } protected virtual void OnTextWrappingChanged(DependencyPropertyChangedEventArgs e) { this.textBlock.TextWrapping = (TextWrapping)e.NewValue; this.InvalidateMeasure(); } #endregion #region LineHeight (DependencyProperty) /// /// Gets or sets the LineHeight property. This property corresponds to TextBlock.LineHeight; /// public double LineHeight { get { return (double)GetValue(LineHeightProperty); } set { SetValue(LineHeightProperty, value); } } public static readonly DependencyProperty LineHeightProperty = DependencyProperty.Register("LineHeight", typeof(double), typeof(DynamicTextBlock), new PropertyMetadata(0.0, new PropertyChangedCallback(OnLineHeightChanged))); private static void OnLineHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((DynamicTextBlock)d).OnLineHeightChanged(e); } protected virtual void OnLineHeightChanged(DependencyPropertyChangedEventArgs e) { textBlock.LineHeight = LineHeight; this.InvalidateMeasure(); } #endregion #region LineStackingStrategy (DependencyProperty) /// /// Gets or sets the LineStackingStrategy DependencyProperty. This corresponds to TextBlock.LineStackingStrategy. /// public LineStackingStrategy LineStackingStrategy { get { return (LineStackingStrategy)GetValue(LineStackingStrategyProperty); } set { SetValue(LineStackingStrategyProperty, value); } } public static readonly DependencyProperty LineStackingStrategyProperty = DependencyProperty.Register("LineStackingStrategy", typeof(LineStackingStrategy), typeof(DynamicTextBlock), new PropertyMetadata(LineStackingStrategy.BlockLineHeight, new PropertyChangedCallback(OnLineStackingStrategyChanged))); private static void OnLineStackingStrategyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((DynamicTextBlock)d).OnLineStackingStrategyChanged(e); } protected virtual void OnLineStackingStrategyChanged(DependencyPropertyChangedEventArgs e) { this.textBlock.LineStackingStrategy = (LineStackingStrategy)e.NewValue; this.InvalidateMeasure(); } #endregion /// /// A TextBlock that gets set as the control's content and is ultiately the control /// that displays our text /// private TextBlock textBlock; /// /// Initializes a new instance of the DynamicTextBlock class /// public DynamicTextBlock() { // create our textBlock and initialize this.textBlock = new TextBlock(); this.Content = this.textBlock; } /// /// Handles the measure part of the measure and arrange layout process. During this process /// we measure the textBlock that we've created as content with increasingly smaller amounts /// of text until we find text that fits. /// /// the available size /// the base implementation of Measure protected override Size MeasureOverride(Size availableSize) { // just to make the code easier to read bool wrapping = this.TextWrapping == TextWrapping.Wrap; Size unboundSize = wrapping ? new Size(availableSize.Width, double.PositiveInfinity) : new Size(double.PositiveInfinity, availableSize.Height); string reducedText = this.Text; // set the text and measure it to see if it fits without alteration this.textBlock.Text = reducedText; Size textSize = base.MeasureOverride(unboundSize); while (wrapping ? textSize.Height > availableSize.Height : textSize.Width > availableSize.Width) { int prevLength = reducedText.Length; reducedText = this.ReduceText(reducedText); if (reducedText.Length == prevLength) { break; } this.textBlock.Text = reducedText + "..."; textSize = base.MeasureOverride(unboundSize); } return base.MeasureOverride(availableSize); } /// /// Reduces the length of the text. Derived classes can override this to use different techniques /// for reducing the text length. /// /// the original text /// the reduced length text protected virtual string ReduceText(string text) { return text.Substring(0, text.Length - 1); } } }