Thursday, May 10, 2012

WPF Slider with annotations.

 Some days back I was looking for a way to annotate the tick-mars on the slider.

Again I will show what was the problem I was trying to solve.


Below Slider there are ticks, I wanted to annotate them with some meaningful labels. Unfortunately Slider doesn't have any APIs that would help me achieve this.

One of the obvious solutions that comes to mind is strategically placing the WPF Label control below the tick marks, but this will make this feature non-extensible. Each time you change the number of tick marks, you will have to update the label positions so that they come exactly below the tick marks. Something like this,


As you can see, I have failed at placing the labels correctly below the ticks, that is because I was limited by Visual Studio at choosing the nearest position, but not the exact location.

I did Google to search for the answers and found relevant information here and here.

The above post told everything except, tick marks were being replaced by string, but I wanted both tick marks and text!.

I started modifying the Slider control to include API that will accept comma(,) separated string to display below the ticks. Something like "1,,3,,5". I have placed two commas to let know the API that I don't want anything to displayed below the second or fourth tick marks. And I modified the algorithm a little so that I will not require Margin as mentioned in above post to correct the position of text below text. If I had used Margin it would have affected the placement of ticks along with that text, as a result the tick marks would have been misplaced.

Here is the algorithm that I used.

 public class CustomTickBar : TickBar
    {
        protected override void OnRender(DrawingContext dc)
        {
            string[] textArray = TickBarText.Split(',');
            FormattedText formattedText;
            Size size = new Size(base.ActualWidth, base.ActualHeight);
            double num = this.Maximum - this.Minimum;
            double num5 = this.ReservedSpace * 0.5;
            size.Width -= this.ReservedSpace;
            double tickFrequencySize = (size.Width * this.TickFrequency / (this.Maximum - this.Minimum));
            int j = 0;
            for (double i = 0; i <= num; i += this.TickFrequency)
            {
                string annotation;
                try
                {
                    annotation = textArray[j];
                }
                catch (System.IndexOutOfRangeException)
                {
                    annotation = "";
                }
                formattedText = new FormattedText(annotation, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Verdana"), 8, Brushes.Black);
                dc.DrawText(formattedText, new Point((tickFrequencySize * i) + num5-(formattedText.Width/2), 10));
                j++;
            }
            
            base.OnRender(dc); //This is essential so that tick marks are displayed. 
         } 
     }

Here is the sample output.
 

Whats more, since the tick bar is now part of slider itself, it can respond to property changes
Slider, like when the control is disabled the annotated text can gray out!


Tuesday, May 1, 2012

Inline ScrollViewer

Before I start explaining, let me show you the problem I was trying to solve,

As it can be seen from the image above, to scroll a single line of statement, real-estate required is twice that of actual data itself. 50% of the space has gone waste!

 So came up with this,

It did save space, though not 50%!. Some amount of space has been lost to the scroll button themselves.

Then I was inspired by scroll bar provided VS for browsing options on right click of project file, where the scroll buttons are removed/collapsed if  it can't be scrolled any further. Specifically in the image above the left scroll button doesn't make sense, since we are already at leftmost position, same applies to right scroll button when we have reached end.

 This is what I achieved at the end.




I have written below the style I used for scrollviewer. (In case if you need further clarifications feel free to contact me.)

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:CustomScroller.Controls">
    <Style TargetType="{x:Type local:CustomScrollViewer}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:CustomScrollViewer}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition />
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition />
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <RepeatButton x:Name="PART_LeftRepeatButton" Grid.Row="1" Grid.Column="0" Command="ScrollBar.LineLeftCommand">
                            <Path Fill="#FF444444" Stroke="#FF444444" Data="M 0,5 L 5,0 L 5,10 Z"/>
                        </RepeatButton>
                        <RepeatButton x:Name="PART_UpRepeatButton" Grid.Row="0" Grid.Column="1" Command="ScrollBar.LineUpCommand">
                            <Path Fill="#FF444444" Stroke="#FF444444" Data="M 0,5 L 5,0 L 10,5 Z"/>
                        </RepeatButton>
                        <ScrollContentPresenter Grid.Row="1" Grid.Column="1" />
                        <RepeatButton x:Name="PART_RightRepeatButton" Grid.Row="1" Grid.Column="2" Command="ScrollBar.LineRightCommand">
                            <Path Fill="#FF444444" Stroke="#FF444444" Data="M 0,0 L 5,5 L 0,10 Z"/>
                        </RepeatButton>
                        <RepeatButton x:Name="PART_DownRepeatButton" Grid.Row="2" Grid.Column="1" Command="ScrollBar.LineDownCommand">
                            <Path Fill="#FF444444" Stroke="#FF444444" Data="M 0,0 L 10,0 L 5,5 Z"/>
                        </RepeatButton>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
 

Here is the part of the code that decides when to show which repeat buttons.

 
//Updates the visiblity of Left and Right Repeat buttons. 
    leftRepeatButton.Visibility = ComputedHorizontalScrollBarVisibility;
    rightRepeatButton.Visibility = ComputedHorizontalScrollBarVisibility;
         
      if (ContentHorizontalOffset == 0)
      {
         leftRepeatButton.Visibility = Visibility.Collapsed;
      }
 
      if (ContentHorizontalOffset == ExtentWidth - ViewportWidth)
      {
        rightRepeatButton.Visibility = Visibility.Collapsed;
      }
    
 
//Updates the visiblity of Up and Down Repeat buttons. 
    upRepeatButton.Visibility = ComputedVerticalScrollBarVisibility;
    downRepeatButton.Visibility = ComputedVerticalScrollBarVisibility;

      if (ContentVerticalOffset == 0)
      {
        upRepeatButton.Visibility = Visibility.Collapsed;
      }
      if (ContentVerticalOffset == ExtentHeight - ViewportHeight)
      {
        downRepeatButton.Visibility = Visibility.Collapsed;
      }