Mauro Bilotti
Mauro Bilotti

Reputation: 6232

WPF: How to set a dynamic range of numbers for vertical slider?

I'm currently working with an WPF MVVM project which has a user control used by several views but with a different range of values. Here is an example of what i need.

values enter image description here enter image description here

As you can see, the control has to respond with a different behavior depending on the values which i need to show in the slidder (no matter the numbers, this is just an example). One of the problems is that the project is developed using the MVVM design pattern, so the 'code behind' should not be a good option (but if it solves the problem, it is fine for me), so i think it might be a bit harder to do it in the XAML.

The pics which I have shown before was done it in static way by creating a grid just beside the slider with a couple of textblocks, but is impossible to hold this solution because i have to create a new one anytime I need it. I thought to start a solution by using a StackPanel or DockPanel with a set height... but if you think a little this is not a good option when you want to show two values because you need to specify the vertical align for each of this values, and this doesn't sounds good.

The only way I think is to make an override or something which allows the slider to show the Ticks based on the height of the slider... is there a way to do something like this?

I just wrote some lines of code, but nothing relevant because I don't know how to solve this task. I don't need code, I need ideas.

Upvotes: 2

Views: 2793

Answers (1)

Jero Franzani
Jero Franzani

Reputation: 473

We work together so i will leave the solution we have found to this problem in case anyone else is interested.

First we have created 2 classes.

1) A class which inherits from SLIDER with 2 dependency properties for the intermediate range (the green numbers in the image) like this:

class NumberedSlider : System.Windows.Controls.Slider
    {
        public static readonly DependencyProperty RangeMinProperty = DependencyProperty.Register(
                 "RangeMinSlider", typeof(int), typeof(NumberedTickBar), new FrameworkPropertyMetadata(0));

        public int RangeMin
        {
            get { return (int)base.GetValue(RangeMinProperty); }
            set { base.SetValue(RangeMinProperty, value); }
        }

        public static readonly DependencyProperty RangeMaxProperty = DependencyProperty.Register(
                  "RangeMaxSlider", typeof(int), typeof(NumberedTickBar), new FrameworkPropertyMetadata(0));

        public int RangeMax
        {
            get { return (int)base.GetValue(RangeMaxProperty); }
            set { base.SetValue(RangeMaxProperty, value); }
        }
    }

2) a class for the TickBar of the slider which will render de numbers:

public class NumberedTickBar : TickBar
    {
        public static readonly DependencyProperty RangeMinProperty = DependencyProperty.Register(
            "RangeMin", typeof (int), typeof (NumberedTickBar), new FrameworkPropertyMetadata(0));

        public int RangeMin
        {
            get { return (int) base.GetValue(RangeMinProperty); }
            set { base.SetValue(RangeMinProperty, value); }
        }

        public static readonly DependencyProperty RangeMaxProperty = DependencyProperty.Register(
            "RangeMax", typeof (int), typeof (NumberedTickBar), new FrameworkPropertyMetadata(0));

        public int RangeMax
        {
            get { return (int) base.GetValue(RangeMaxProperty); }
            set { base.SetValue(RangeMaxProperty, value); }
        }

        protected override void OnRender(DrawingContext dc)
        {
            Size size = new Size(base.ActualWidth, base.ActualHeight);

            int tickCount;

            if ((this.Maximum - this.Minimum)%this.TickFrequency == 0)
                tickCount = (int) ((this.Maximum - this.Minimum)/this.TickFrequency);
            else
                tickCount = (int) ((this.Maximum - this.Minimum)/this.TickFrequency) + 1;

            double tickFrequencySize = (size.Height*this.TickFrequency/(this.Maximum - this.Minimum));

            // Draw each tick text
            for (int i = 0; i <= tickCount; i++)
            {
                int value = Convert.ToInt32(this.Minimum + this.TickFrequency*i);

                string text = Convert.ToString(value, 10);
                FormattedText formattedText;
                if (value >= this.RangeMin && value <= this.RangeMax)
                    formattedText = new FormattedText(text, CultureInfo.GetCultureInfo("en-us"),
                                                      FlowDirection.LeftToRight, new Typeface("Arial Rounded MT Bold"), 16,
                                                      Brushes.Green);
                else
                    formattedText = new FormattedText(text, CultureInfo.GetCultureInfo("en-us"),
                                                      FlowDirection.LeftToRight, new Typeface("Arial Rounded MT Bold"), 16,
                                                      Brushes.DarkBlue);

                dc.DrawText(formattedText, new Point(30, (tickFrequencySize*(tickCount - i)-6)));
            }
        }
    }

Then we have replaced the tickbar on control template of the vertical slider like the following example:

Vertical slider with labels on the ticker

but also adding the 2 new values of dependency properties:

 <common:NumberedTickBar   Margin="0,0,0,10" x:Name="TopTick" 
                                      SnapsToDevicePixels="True" Placement="Left" 
                                      Fill="{StaticResource GlyphBrush}" Width="4" 
                                      RangeMax="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RangeMax}"
                                      RangeMin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=RangeMin}"/>

Finally on the xaml of the user control we have some code like this one:

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50*"/>
            <ColumnDefinition Width="50*"/>
        </Grid.ColumnDefinitions>
        <Grid>
            <common:NumberedSlider x:Name="SliderValue" MinHeight="180" Margin="5 15 5 15" Orientation="Vertical" 
                                   Maximum="89" 
                                   Minimum="77" 
                                   Value="{Binding BarValue, Mode=TwoWay}"  
                                   TickFrequency="1" 
                                   IsSnapToTickEnabled="True" 
                                   RangeMin="82" 
                                   RangeMax="85"
                                   IsEnabled="{Binding SliderEnabled}"/>
        </Grid>
        <Grid Grid.Column="1">
            <ProgressBar Orientation="Vertical" Width="70" 
                         Minimum="{Binding ElementName=SliderValue,Path=Minimum}" 
                         Maximum="{Binding ElementName=SliderValue,Path=Maximum}" 
                         Value="{Binding ElementName=SliderValue, Path=Value}" Margin="0 14" Background="Gray" MinHeight="200"
                         Foreground="{Binding ColorBar, Converter= {StaticResource setSteelPointLevelConverter}}"                          />
        </Grid>
    </Grid>

Hope this approch can help anyone else.

Upvotes: 3

Related Questions