0

I have a sets of user controls in my WPF application. One of them I've listed in code below:

RichSlider.xaml - control directly

<UserControl x:Name="RichSliderControl"
         x:Class="CustomControls.RichSlider"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         >
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Controls;component/Themes/RichSlider.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
</UserControl>

RichSlider.xaml - control style

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:CustomControls="clr-namespace:CustomControls"
                    >
    <Style x:Key="GlobalLabelStyle" TargetType="{x:Type FrameworkElement}">
        <Setter Property="Margin" Value="0"/>
        <Setter Property="TextElement.FontSize" Value="14"/>
        <Setter Property="TextElement.FontWeight" Value="Bold"/>
        <Setter Property="TextElement.FontFamily" Value="Century Gothic"/>
    </Style>

    <Style x:Key="ValueLabelStyle" TargetType="{x:Type Label}" BasedOn="{StaticResource GlobalLabelStyle}">
        <Setter Property="Content" Value="{Binding Value, ElementName=RichSliderControl, Mode=OneWay}"/>
    </Style>

    <Style TargetType="{x:Type Slider}">
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Foreground" Value="Gray"/>
        <Setter Property="Background" Value="#F5F5F5"/>
        <Setter Property="IsSnapToTickEnabled" Value="True"/>
        <Setter Property="Minimum" Value="{Binding Minimum, ElementName=RichSliderControl}"/>
        <Setter Property="Maximum" Value="{Binding Maximum, ElementName=RichSliderControl}"/>
        <Setter Property="Value" Value="{Binding Value, ElementName=RichSliderControl}"/>
        <Setter Property="SmallChange" Value="{Binding SmallChange, ElementName=RichSliderControl}"/>
        <Setter Property="LargeChange" Value="{Binding LargeChange, ElementName=RichSliderControl}"/>
    </Style>

    <ControlTemplate x:Key="HorizontalSlider" TargetType="{x:Type UserControl}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            <Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalContentAlignment="Center" Content="{Binding Text, ElementName=RichSliderControl, Mode=OneWay}"></Label>
            <Label Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" VerticalContentAlignment="Center" Style="{StaticResource ValueLabelStyle}"></Label>

            <Slider x:Name="ValueSlider"
                    Grid.Row="1"
                    Grid.Column="0"
                    Grid.ColumnSpan="2"
                    Orientation="Horizontal"
                    TickPlacement="BottomRight"
                    >
            </Slider>
        </Grid>
    </ControlTemplate>

    <ControlTemplate x:Key="VerticalSlider" TargetType="{x:Type UserControl}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="26"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            <Label Grid.Row="0" Grid.Column="0" HorizontalContentAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center">
                <TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Text="{Binding Text, ElementName=RichSliderControl, Mode=OneWay}"></TextBlock>
            </Label>
            <Label Grid.Row="1" Grid.Column="1" HorizontalContentAlignment="Center" Style="{StaticResource ValueLabelStyle}"></Label>

            <Slider x:Name="ValueSlider"
                    Grid.Row="0"
                    Grid.Column="1"
                    Orientation="Vertical"
                    TickPlacement="TopLeft"
                    IsDirectionReversed="True"
                    >
            </Slider>
        </Grid>
    </ControlTemplate>

    <Style TargetType="{x:Type CustomControls:RichSlider}">
        <Style.Triggers>
            <Trigger Property="Orientation" Value="Horizontal">
                <Setter Property="MinWidth" Value="100" />
                <Setter Property="MinHeight" Value="20" />
                <Setter Property="Template" Value="{StaticResource HorizontalSlider}" />
            </Trigger>
            <Trigger Property="Orientation" Value="Vertical">
                <Setter Property="MinWidth" Value="20" />
                <Setter Property="MinHeight" Value="100" />
                <Setter Property="Template" Value="{StaticResource VerticalSlider}" />
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

RichSlider.xaml.cs - control code

public partial class RichSlider : UserControl
{
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(RichSlider));

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(double), typeof(RichSlider));

    public static readonly DependencyProperty MinimumProperty =
        DependencyProperty.Register("Minimum", typeof(double), typeof(RichSlider));

    public static readonly DependencyProperty MaximumProperty =
        DependencyProperty.Register("Maximum", typeof(double), typeof(RichSlider));

    public static readonly DependencyProperty SmallChangeProperty =
        DependencyProperty.Register("SmallChange", typeof(double), typeof(RichSlider));

    public static readonly DependencyProperty LargeChangeProperty =
        DependencyProperty.Register("LargeChange", typeof(double), typeof(RichSlider));

    public static readonly DependencyProperty OrientationProperty =
        DependencyProperty.Register("Orientation", typeof(Orientation), typeof(RichSlider));

    private static readonly RoutedEvent ValueChangedEvent =
        EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<double>), typeof(RichSlider));

    [Bindable(true)]
    public string Text
    {
        get => (string)GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }

    [Bindable(true)]
    public double Value
    {
        get => (double)GetValue(ValueProperty);
        set
        {
            var val = value;

            if (val < Minimum)
            {
                val = Minimum;
            }

            if (val > Maximum)
            {
                val = Maximum;
            }

            SetValue(ValueProperty, val);
            RaiseEvent(new(ValueChangedEvent));
        }
    }

    [Bindable(true)]
    public double Minimum
    {
        get => (double)GetValue(MinimumProperty);
        set
        {
            var min = value;

            if (Value < min)
            {
                Value = min;
            }

            if (Maximum < min)
            {
                Maximum = min;
            }

            SetValue(MinimumProperty, min);
        }
    }

    [Bindable(true)]
    public double Maximum
    {
        get => (double)GetValue(MaximumProperty);
        set
        {
            var max = value;

            if (Value > max)
            {
                Value = max;
            }

            if (max < Minimum)
            {
                max = Minimum;
            }

            SetValue(MaximumProperty, max);
        }
    }

    [Bindable(true)]
    public double SmallChange
    {
        get => (double)GetValue(SmallChangeProperty);
        set
        {
            var change = value;

            if (change > LargeChange)
            {
                change = LargeChange;
            }

            SetValue(SmallChangeProperty, change);
        }
    }

    [Bindable(true)]
    public double LargeChange
    {
        get => (double)GetValue(LargeChangeProperty);
        set
        {
            var change = value;

            if (change < SmallChange)
            {
                change = SmallChange;
            }

            SetValue(LargeChangeProperty, change);
        }
    }

    [Bindable(true)]
    public Orientation Orientation
    {
        get => (Orientation)GetValue(OrientationProperty);
        set => SetValue(OrientationProperty, value);
    }

    public RichSlider()
    {
        InitializeComponent();
    }

    public event RoutedPropertyChangedEventHandler<double> ValueChanged
    {
        add
        {
            AddHandler(ValueChangedEvent, value);
        }
        remove
        {
            RemoveHandler(ValueChangedEvent, value);
        }
    }

    public override void OnApplyTemplate()
    {
        if (GetTemplateChild("ValueSlider") is Slider slider)
        {
            slider.MouseWheel += new MouseWheelEventHandler(ValueSlider_MouseWheel);
            slider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(ValueSlider_ValueChanged);
        }

        base.OnApplyTemplate();
    }

    private void ValueSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        RaiseEvent(new(ValueChangedEvent));
    }

    private void ValueSlider_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        var directionReversed = (sender as Slider).IsDirectionReversed;
        
        if (((e.Delta > 0 && !directionReversed) || (e.Delta < 0 && directionReversed)) && Value < Maximum)
        {
            Value++;
        }
        else if (((e.Delta < 0 && !directionReversed) || (e.Delta > 0 && directionReversed)) && Value > Minimum)
        {
            Value--;
        }
    }
}

Ok. I'm adding this control to my window.xaml and binding some value like:

xmlns:CustomControls="clr-namespace:CustomControls;assembly=Controls"
....
<CustomControls:RichSlider Minimum="1" Maximum="5" SmallChange="1" Orientation="Horizontal" Value="{Binding Path=Acceleration, Mode=OneWay}" Text="{DynamicResource Acceleration}"></CustomControls:RichSlider>

And I have a ListBox of items, which contains Name and Acceleration properties. So, when I'm clicking on ListBoxItem, the value in my RichSlider is updating according to the value of Acceleration property in item. So, seems like the binding is working ok. But no. When I'm changing the value in my RichSlider manually, the value is no longer updated for all other items and is stuck with the value I set. As evidence:

<TextBox Text="{Binding Path=Name, Mode=OneWay}"></TextBox>

The Text value is updating after selecting another item, not depends from changing I made. I also checked the way like:

<TextBox Text="{Binding Path=Acceleration, Mode=OneWay}"></TextBox>

and it's also works fine. So, this way I made the conclusion that the issue is in my control, not in Binding, cause for TextBox it works, but for RichSlider - not.

So, does anyone have any thoughts what I'm doing wrong?

Vlad i Slav
  • 59
  • 2
  • 12
  • 1
    The setter of a dependency property must not call anything else than SetValue. This is because the setter may be bypassed, and the framework may call SetValue directly, e.g. when a dependency property value is provided by data binding. Implement appropriate PropertyChangedCallbacks or CoercevalueCallbacks. – Clemens Mar 26 '22 at 08:39
  • 1
    Also be aware that directly setting the Value property (e.g by `Value++`) replaces a OneWay Binding of the Value property. A TwoWay Binding would not be replaced, but it might be more appropriate to call `SetCurrentValue(ValueProperty, Value + 1);` – Clemens Mar 26 '22 at 08:44
  • 1
    It is also unclear why you not simply derive your control from [RangeBase](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.rangebase?view=windowsdesktop-6.0). – Clemens Mar 26 '22 at 11:46

0 Answers0