lundi 21 décembre 2015

Creating an expandable TextBlock control for WinRT, click to expand text

In this example we will look at how to create an expandable text block for a Windows 10 application.  I want a control on which I tell it its default height before it is expended and of course the text that need to be shown, a control that has a XAML code that looks like this:

 <controls:ExpandableTextBlock 
                Margin="0,0,0,0"
                CollapsedHeight="30"
                Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis lobortis libero vel justo sollicitudin auctor. Nulla blandit pulvinar augue ac feugiat. sit amet dolor. Suspendisse mattis dolor eu nulla volutpat, vitae accumsan sapien commodo.Vivamus id turpis sem."
            
                />

And that looks like this when its collapsed:


And like this when it is expended:



Let get coding:

First we will need to create a UserControl named ExpandableTextBlock, this control will contain one grid that holds 2 text blocks on that will hold our text and the other one that will hold our button or text that tells the user to click if he wishes to see the text.

Here is the XAML code:


 <Grid x:Name="LayoutRoot" Tapped="LayoutRoot_OnTap">
     <Grid.RowDefinitions>
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
      </Grid.RowDefinitions>

      <TextBlock  Grid.Row="0" 
                  x:Name="CommentTextBlock"  
                  HorizontalAlignment="Left" 
                  TextWrapping="Wrap"  
                  Height="Auto" 
                  Width="280"/>

      <StackPanel Grid.Row="1" 
                  Orientation="Horizontal" 
                  HorizontalAlignment="Right"
                  x:Name="ExpandHint" 
                  Visibility="Collapsed" 
                  Margin="0,5,0,0">
          <TextBlock  Text="View More" />
           <TextBlock Margin="10,0,10,0"
        Text="+" />
      </StackPanel>
</Grid>

We will need to create two DependencyProperty called Text and  CollapsedHeight which will allow you to pass the Text and the Height of your textblock to the UserControl.


        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text", typeof(string), typeof(ExpandableTextBlock), new PropertyMetadata(default(string), OnTextChanged));

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
       
        private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctl = (ExpandableTextBlock)d;
            ctl.CommentTextBlock.SetValue(TextBlock.TextProperty, (string)e.NewValue);
            ctl.CommentTextBlock.SetValue(TextBlock.HeightProperty, Double.NaN);

            ctl.CommentTextBlock.Measure(new Size(ctl.CommentTextBlock.Width, double.MaxValue));

            double desiredheight = ctl.CommentTextBlock.DesiredSize.Height;
            ctl.CommentTextBlock.SetValue(TextBlock.HeightProperty, (double)63);

            if (desiredheight > (double)ctl.CommentTextBlock.GetValue(TextBlock.HeightProperty))
            {
                ctl.ExpandHint.SetValue(StackPanel.VisibilityProperty, Visibility.Visible);
                ctl.MaxHeight = desiredheight;
            }
            else
            {
                ctl.ExpandHint.SetValue(StackPanel.VisibilityProperty, Visibility.Collapsed);
            }

            //Setting length (width) of TextBlock
            var boundsWidth = Window.Current.Bounds.Width;
            ctl.CommentTextBlock.SetValue(TextBlock.WidthProperty, boundsWidth);
        }

        public static readonly DependencyProperty CollapsedHeightProperty = DependencyProperty.Register(
            "CollapsedHeight", typeof(double), typeof(ExpandableTextBlock), new PropertyMetadata(default(double), OnCollapsedHeightChanged));


        public double CollapsedHeight
        {
            get { return (double)GetValue(CollapsedHeightProperty); }
            set { SetValue(CollapsedHeightProperty, value); }
        }

        private static void OnCollapsedHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctl = (ExpandableTextBlock)d;
            ctl.CollapsedHeight = (double)e.NewValue;
        }

in the OnTextChanged method you can see that we check to see if we need to show the hint that there is more text or not, as you can imagine it is pointless to show to the user an element saying that there is more text is there is not really more text.

and lastly we now need to create the méthode linked to the LayoutRoot_OnTap event, this methode will have to check to see if the content is fully visible or not and if not then show the whole content.


       private void LayoutRoot_OnTap(object sender, TappedRoutedEventArgs tappedRoutedEventArgs)
        {
           if ((Visibility)this.ExpandHint.GetValue(StackPanel.VisibilityProperty) == Visibility.Visible)
            {
                //transition
                this.CommentTextBlock.SetValue(TextBlock.HeightProperty, Double.NaN);

                this.ExpandHint.SetValue(StackPanel.VisibilityProperty, Visibility.Collapsed);
            }
        }

And there you have it, when the user clicks on the text block it will show the reste of the text.

Possible improvements:

You could add a third DependencyProperty that would allow you to manage the width of your text block thus instead of having the textblock taking the whole width of the screen like this:

//Setting length (width) of TextBlock
var boundsWidth = Window.Current.Bounds.Width;
ctl.CommentTextBlock.SetValue(TextBlock.WidthProperty, boundsWidth);

You could use a DependencyProperty to set the width.


Or you could also add another DependencyProperty which could manage the style of the TextBlock like this:



       public static readonly DependencyProperty TextStyleProperty = DependencyProperty.Register(
           "TextStyle", typeof(Style), typeof(ExpandableTextBlock), new PropertyMetadata(default(Style), OnTextStyleChanged));

        public Style TextStyle
        {
            get { return (Style)GetValue(TextStyleProperty); }
            set { SetValue(TextStyleProperty, value); }
        }

        private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctl = (ExpandableTextBlock)d;
            ctl.CommentTextBlock.SetValue(StyleProperty, (Style)e.NewValue);
        }


Happy coding.

you can find the Github code here