ValidationRule с параметрами

Я разрабатываю приложение с C # и WPF, у меня есть собственный настраиваемый элемент управления слайдером. и текстовые поля в одном окне. Все свойства моего ползунка DependencyProperty.

Я использую текстовые поля для изменения свойств ползунка. Я хочу использовать ValidationRule в текстовых полях. Я написал свой собственный ValidationRule (производный от класса ValidationRule). Я хочу передать некоторые параметры этому ValidationRule. Вот код:

Текстовое окно:

<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 Правило проверки:

 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); }
    }
}

Я предполагаю, что все в порядке, но проблема в том, что параметр NumberCombineTo установлен на default (0) даже если я изменю свойство RightThumbValue gammaSlider. Мне нужно обновить свойство NumberCombineTo при изменении RightThumbValue.


person cKNet    schedule 21.02.2015    source источник
comment
Трудно сказать без хорошего, минимального, полного примера кода. Вы смотрели на окно вывода отладчика? Есть ли там какие-либо диагностические сообщения об использовании? Вы подтвердили, что привязка к RightThumbValue работает вне сценария ValidationRule? Код, который вы разместили, кажется мне приемлемым, но это мало что говорит; Я пробую множество вещей в WPF, которые вроде бы должны работать, но не работают. :)   -  person Peter Duniho    schedule 21.02.2015
comment
Я отлаживал код построчно. RightThumbValue меняется так, как должно быть. Я думаю, что часть проверки binding неверна. Потому что NumberCombineTo НЕ меняется. Он имеет значение по умолчанию DependencyProperty's, даже если RightThumbValue изменилось.   -  person cKNet    schedule 22.02.2015
comment
Еще раз: видите ли вы какие-либо диагностические сообщения в окне вывода отладчика? Сбои привязки часто генерируют полезное сообщение. Глядя на ваш код, единственное, что кажется подозрительным, это то, что вы используете разные значения ElementName для привязок LeftThumbValue и RightThumbValue. Конечно, если у вас на самом деле есть два разных ползунка с двумя большими пальцами, это может быть хорошо; но если вы ожидаете, что оба будут связаны с одним и тем же ползунком, использование двух разных имен, вероятно, будет неправильным (т. е. используйте gammaSlider или myOwnSlider, но не оба).   -  person Peter Duniho    schedule 22.02.2015
comment
Мне очень жаль, что два из них — «gammaSlider». gammaSlider — это что-то вроде ЭТОГО   -  person cKNet    schedule 22.02.2015


Ответы (1)


Я написал простой полный пример кода на основе фрагментов, которые вы предоставили здесь, и я считаю, что смог воспроизвести основную проблему, с которой вы столкнулись.

Если вы запустите свой код в отладчике и посмотрите на окно «Вывод», вы, скорее всего, увидите сообщение, которое частично гласит:

Не удается найти управляющий элемент FrameworkElement или FrameworkContentElement для целевого элемента

Системе привязки WPF требуется один из этих элементов, чтобы иметь возможность искать имя исходного элемента (т. е. объекта для свойства ElementName). Но в этом сценарии вы пытаетесь связать свойство объекта, который сам по себе не является элементом, связанным с фреймворком, и не связан с ним каким-либо образом, видимым для WPF. Поэтому при попытке настроить привязку происходит сбой.

Я видел несколько разных статей, в которых рекомендовалось решить эту проблему с помощью «прокси-объекта». Обычно это соответствует шаблону объявления ресурса, привязанного к DataContext содержащего объекта, а затем использованию этого объекта в качестве Source для привязки. Но мне кажется сложным правильно настроить это, и это зависит от возможности установить конкретный DataContext объект, который содержит свойства, к которым вы действительно хотите привязаться. Существуют ограничения на то, насколько далеко вы можете зайти с помощью этой техники, поскольку количество привязок без элементов фреймворка растет.

Например:
Как выполнить привязку к данным, если DataContext не унаследован
Присоединение виртуальной ветви к логическому дереву в WPF
и даже здесь, на SO, Ошибка WPF: не удается найти управляющий элемент FrameworkElement для целевого элемента

Вместо этого мне кажется, что это одна из тех ситуаций, когда программный код работает лучше. Это всего лишь несколько строк кода для явной настройки привязки, и это полностью устраняет вопрос о том, может ли WPF действительно найти объект, к которому вы хотите привязаться.

Я получил пример XAML, который выглядит так:

<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>

Главное, что здесь отличается от вашего кода, это то, что я поместил объект ValidationParameters в ресурсы окна. Это позволяет мне легко ссылаться на него из кода программной части для привязки.

(Конечно, остальная часть кода тоже отличается, но не в какой-либо значимой форме. Мне показалось проще просто использовать два отдельных элемента управления Slider для базового примера, поскольку он встроен в WPF, и предоставить TextBlock элементов в окне. чтобы было легче видеть, что происходит).

Код программной части выглядит следующим образом:

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);
    }
}

Другими словами, он просто создает новый объект Binding, назначает исходный объект и свойство, извлекает объект ValidationParameters, к которому мы хотим привязать этот объект + свойство, а затем устанавливает привязку к свойству NumberCombineTo объекта ValidationParameters.

Когда я это делаю, свойство NumberCombineTo корректно обновляется при изменении значения slider2.Value и становится доступным для использования при вызове метода Validate() объекта ValidationRule.

person Peter Duniho    schedule 21.02.2015