2

I'm developing an app with C# and WPF, I've my own slider custom control. and textboxes on the same window. All properties of my slider are DependencyProperty.

I use textboxes to change slider's properties. I want to use ValidationRule on textboxes. I wrote my own ValidationRule (derived from ValidationRule class). I want to pass some parameters to that ValidationRule. Here is code:

TextBox:

<TextBox HorizontalAlignment="Left" Height="24" Margin="10,169,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="40" Style="{DynamicResource archiveSearchTextBox}" MaxLength="3" HorizontalContentAlignment="Center" TabIndex="2">
        <TextBox.Text>
            <Binding UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" ElementName="gammaSlider" Path="LeftThumbValue" NotifyOnValidationError="True" ValidatesOnExceptions="True" ValidatesOnDataErrors="True">
                <Binding.ValidationRules>
                    <ExceptionValidationRule/>
                    <local:ZeroTo255MinMax>
                        <local:ZeroTo255MinMax.Parameters>
                            <local:ValidationParameters NumberCombineTo="{Binding ElementName=gammaSlider, Path=RightThumbValue}" ValTypeFor0to255="ShouldBeSmaller"/>
                        </local:ZeroTo255MinMax.Parameters>
                    </local:ZeroTo255MinMax>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>

ZeroTo255MinMax ValidationRule:

 public class ZeroTo255MinMax : ValidationRule
{
    private ValidationParameters _parameters = new ValidationParameters();
    public ValidationParameters Parameters
    {
        get { return _parameters; }
        set { _parameters = value; }
    }

    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        string numberStr = value as string;
        int val;

        if (int.TryParse(numberStr, out val))
        {
            if (val < 0 || val > 255)
                return new ValidationResult(false, "");
            else if (Parameters.ValTypeFor0to255 == ValidationParameters.ValTypes.ShouldBeBigger)
            {
                if (val <= Parameters.NumberCombineTo || val - Parameters.NumberCombineTo < 2)
                    return new ValidationResult(false, "");
            }
            else if (Parameters.ValTypeFor0to255 == ValidationParameters.ValTypes.ShouldBeSmaller)
            {
                if (val >= Parameters.NumberCombineTo || Parameters.NumberCombineTo - val < 2)
                    return new ValidationResult(false, "");
            }
            return new ValidationResult(true, "");
        }
        else
            return new ValidationResult(false, "");
    }
}

public class ValidationParameters : DependencyObject
{
    public enum ValTypes { ShouldBeSmaller, ShouldBeBigger };
    public static readonly DependencyProperty NumberCombineToProperty = DependencyProperty.Register("NumberCombineTo", typeof(int), typeof(ValidationParameters), new PropertyMetadata(0, new PropertyChangedCallback(OnNumberCombineToChanged)));
    public static readonly DependencyProperty ValTypeFor0to255Property = DependencyProperty.Register("ValTypeFor0to255", typeof(ValTypes), typeof(ValidationParameters), new PropertyMetadata(ValTypes.ShouldBeBigger));

    private static void OnNumberCombineToChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.CoerceValue(NumberCombineToProperty); }

    public int NumberCombineTo
    {
        get { return (int)GetValue(NumberCombineToProperty); }
        set { SetValue(NumberCombineToProperty, value); }
    }

    public ValTypes ValTypeFor0to255
    {
        get { return (ValTypes)GetValue(ValTypeFor0to255Property); }
        set { SetValue(ValTypeFor0to255Property, value); }
    }
}

My guess is, everything is fine but, the problem is, NumberCombineTo parameter is set to default (0) even if I change gammaSlider's RightThumbValue property. I need to update NumberCombineTo property when RightThumbValue is changed.

cKNet
  • 635
  • 1
  • 10
  • 22
  • Hard to say without [a good, _minimal_, _complete_ code example](http://stackoverflow.com/help/mcve). Did you look at the debugger "Output" window? Are there any using diagnostic messages there? Have you confirmed that binding to `RightThumbValue` works outside the `ValidationRule` scenario? The code you posted seems okay to me, but that's not saying much; I try lots of things in WPF that seem like they ought to work, but which don't. :) – Peter Duniho Feb 21 '15 at 20:52
  • I've debugged the code line by line. `RightThumbValue` is changing as it supposed to be. I think the validation's **binding** part is wrong. Because `NumberCombineTo` is NOT changing. It has the `DependencyProperty's` default value even if `RightThumbValue` has changed. – cKNet Feb 21 '15 at 21:25
  • Again: are you seeing any diagnostic messages in the debugger's "Output" window? Binding failures often generate some kind of message that is useful. Looking at your code, the only thing that seems suspect is that you are using different `ElementName` values for the `LeftThumbValue` and `RightThumbValue` bindings. Of course, if you actually have two different two-thumb sliders this could be fine; but if you expected both to be bound to the same slider, the use of two different names is probably wrong (i.e. use `gammaSlider` or `myOwnSlider`, but not both). – Peter Duniho Feb 21 '15 at 21:51
  • I'm really sorry two of them is 'gammaSlider'. The gammaSlider is something like [THIS ONE](http://www.thejoyofcode.com/Creating_a_Range_Slider_in_WPF_and_other_cool_tips_and_tricks_for_UserControls_.aspx) – cKNet Feb 21 '15 at 21:57

1 Answers1

0

I wrote a simple complete code example based on the snippets you've provided here, and I believe I was able to reproduce the basic issue you're experiencing.

If you run your code in the debugger, and look at the "Output" window, you will likely see a message that reads in part:

Cannot find governing FrameworkElement or FrameworkContentElement for target element

The WPF binding system requires one of those elements for it to be able to look up the name of the source element (i.e. the object for the ElementName property). But in this scenario, you are trying to bind the property of an object that is neither itself a framework-related element, nor related to one in a way visible to WPF. So it fails when trying to configure the binding.

I've seen several different articles recommending that this issue be resolved through a "proxy object". This typically follows the pattern of declaring a resource bound to a containing object's DataContext, and then using that object as the Source for the binding. But it seems tricky to me to get this set up correctly, and it depends on being able to set a specific DataContext object that contains properties you do in fact want to bind to. There are limits to how far you could take this technique, as the number of framework-element-less bindings grows.

For example:
How to bind to data when the DataContext is not inherited
Attaching a Virtual Branch to the Logical Tree in WPF
and even here on SO, WPF Error: Cannot find govering FrameworkElement for target element

Instead, it seems to me that this is one of those situations where code-behind works better. It's just a few lines of code to set up the binding explicitly, and it completely avoids the issue of whether WPF can in fact find the object to which you want to bind.

I wound up with a XAML example that looks like this:

<Window x:Class="TestSO28645688ValidationRuleParameter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestSO28645688ValidationRuleParameter"
        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <local:ValidationParameters x:Key="validationParams1"
                                    ValTypeFor0to255="ShouldBeSmaller"/>
  </Window.Resources>
  <StackPanel>
    <Slider x:Name="slider1" Value=".25" />
    <Slider x:Name="slider2" Value=".5"/>
    <TextBlock Text="{Binding ElementName=slider1, Path=Value,
               StringFormat=slider1.Value: {0}}" />
    <TextBlock Text="{Binding ElementName=slider2, Path=Value,
               StringFormat=slider2.Value: {0}}" />
    <TextBlock Text="{Binding Source={StaticResource validationParams1},
                              Path=NumberCombineTo,
                              StringFormat=validationParams1.NumberCombineTo: {0}}" />
    <TextBox x:Name="textBox1" HorizontalAlignment="Left" VerticalAlignment="Top"
             Height="24" Width="400"
             Margin="5" TextWrapping="Wrap"
             MaxLength="3" HorizontalContentAlignment="Center" TabIndex="2">
      <TextBox.Text>
        <Binding UpdateSourceTrigger="PropertyChanged" Mode="TwoWay"
                 ElementName="slider1" Path="Value" NotifyOnValidationError="True"
                 ValidatesOnExceptions="True" ValidatesOnDataErrors="True">
          <Binding.ValidationRules>
            <ExceptionValidationRule/>
            <local:ZeroTo255MinMax Parameters="{StaticResource validationParams1}"/>
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>
  </StackPanel>
</Window>

The main thing here that is different from your code is that I put the ValidationParameters object in the window's resources. This allows me to easily reference it from the code-behind for binding there.

(Of course, the rest of the code is different too, but not in any meaningful way. I felt it simpler to just use two separate Slider controls for the basic example, since that's built into WPF, and to provide TextBlock elements in the window to make it easier to see what's going on).

The code-behind looks like this:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Binding binding = new Binding();

        binding.Source = slider2;
        binding.Path = new PropertyPath(Slider.ValueProperty);

        ValidationParameters validationParams = (ValidationParameters)Resources["validationParams1"];

        BindingOperations.SetBinding(validationParams, ValidationParameters.NumberCombineToProperty, binding);
    }
}

In other words, it simply creates a new Binding object, assigns the source object and property, retrieves the ValidationParameters object to which we want to bind that object+property, and then sets the binding on the ValidationParameters object's NumberCombineTo property.

When I do this, the NumberCombineTo property is correctly updated as the slider2.Value value changes, and it available for use when the Validate() method of the ValidationRule object is called.

Community
  • 1
  • 1
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136