将ValidationRule从TextBox绑定到StatusBarItem并显示错误消息

时间:2012-05-31 16:39:35

标签: wpf validation xaml binding

我正在创建一个我想要使用验证规则的应用程序,但是在某些屏幕上没有足够的空间来显示错误的字段旁边的结果错误,所以我想把它放在一个状态栏中该领域的底部。

这个示例来自我从网络拼凑在一起的几个位,它提供了一个使用不同规则验证并以不同方式显示错误的表单,但我不知道如何使用XAML将错误消息输入StatusBarItem。我确信有一种简单的方法可以做到这一点。有人可以帮帮我吗?

使用Framework 4.0在VS2010中编写了样本。

MainWindow.xaml

<Window x:Class="SampleValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:c="clr-namespace:SWL.Libraries.SysText"
        Title="Sample ValidationRule WPF" Height="350" Width="525"
        Loaded="Window_Loaded" WindowStartupLocation="CenterScreen">
  <Window.Resources>
    <ControlTemplate x:Key="validationTemplate">
      <!--
      <TextBlock Foreground="Red" FontSize="20">***</TextBlock>
      -->
      <DockPanel LastChildFill="True">
        <TextBlock DockPanel.Dock="Right" Foreground="Red" Margin="5" FontSize="8pt" Text="***" />
        <AdornedElementPlaceholder />
      </DockPanel>
    </ControlTemplate>
    <Style x:Key="textBoxInError1" TargetType="{x:Type TextBox}">
      <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
          <Setter Property="ToolTip"
                  Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                  Path=(Validation.Errors)[0].ErrorContent}" />
        </Trigger>
      </Style.Triggers>
    </Style>
    <Style x:Key="textBoxInError2"  TargetType="{x:Type TextBox}">
      <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
          <ControlTemplate>
            <DockPanel LastChildFill="True">
              <TextBlock DockPanel.Dock="Right" Foreground="Red" Margin="5" FontSize="8pt"
                         Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
              <Border BorderBrush="Red" BorderThickness="2">
                <AdornedElementPlaceholder Name="MyAdorner" />
              </Border>
            </DockPanel>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
      <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
          <Setter Property="ToolTip"
                  Value="{Binding RelativeSource={RelativeSource Self},
                  Path=(Validation.Errors)[0].ErrorContent}" />
        </Trigger>
      </Style.Triggers>
    </Style>
  </Window.Resources>
  <Grid>
    <TextBlock Height="24" HorizontalAlignment="Left" Margin="36,73,0,0" Name="textBlock1"
               Text="Node Address:" VerticalAlignment="Top" Width="87" />
    <TextBlock Height="24" HorizontalAlignment="Left" Margin="36,112,0,0" Name="textBlock2"
               Text="Node Name:" VerticalAlignment="Top" Width="78" />
    <TextBox Height="24" HorizontalAlignment="Left" Margin="129,70,0,0" Name="textBox1"
             VerticalAlignment="Top" Width="119" TextChanged="textBox1_TextChanged"
             TabIndex="0" Style="{StaticResource textBoxInError2}"
             Validation.ErrorTemplate="{StaticResource validationTemplate}">
      <Binding Path="NodeAddress" UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules>
          <c:NumberRangeRule Min="1" Max="100" />
        </Binding.ValidationRules>
      </Binding>
    </TextBox>
    <TextBox Height="24" HorizontalAlignment="Left" Margin="129,109,0,0" Name="textBox2"
             VerticalAlignment="Top" Width="119" TextChanged="textBox2_TextChanged"
             TabIndex="1" Style="{StaticResource textBoxInError2}">
      <Binding Path="NodeName" UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules>
          <c:NameFormatRule MinLength="6" MaxLength="9" />
        </Binding.ValidationRules>
      </Binding>
    </TextBox>
    <StatusBar Height="23" HorizontalAlignment="Stretch" Margin="0,0,0,0"
               Name="myStatusBar" VerticalAlignment="Bottom">
      <StatusBarItem x:Name="errorStatusBarItem" Content="No errors" />
    </StatusBar>
    <Button Content="Close" Height="29" HorizontalAlignment="Left" Margin="108,227,0,0"
            Name="btnCLOSE" VerticalAlignment="Top" Width="85" Click="btnCLOSE_Click" TabIndex="3" />
    <Button Content="Apply" Height="29" HorizontalAlignment="Left" Margin="297,227,0,0"
            Name="btnAPPLY" VerticalAlignment="Top" Width="85" Click="btnAPPLY_Click" TabIndex="2" />
  </Grid>
</Window>

MainWindow.cs

using System;
using System.Collections.Generic;  
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace SampleValidation {
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window {
    public int NodeAddress { get; set; }
    public string NodeName { get; set; }
    public bool IsAllLoaded { get; set; }

    public MainWindow() {
      NodeAddress = 1;
      NodeName = "freddy";
      IsAllLoaded = false;
      InitializeComponent();
      btnAPPLY.Visibility = System.Windows.Visibility.Hidden;
      DataContext = this;
    }

    private void btnAPPLY_Click(object sender, RoutedEventArgs e) {
      // if there are no format errors reported by the validation rules
      Validator.ErrorText = "";
      if (Validator.IsValid(this))
        // Save the data
        btnAPPLY.Visibility = System.Windows.Visibility.Hidden; // hide the button indicating nothing new to save
      else
        MessageBox.Show("Cant Save Changes - Error in form\r\n" + Validator.ErrorText, "Save not allowed", MessageBoxButton.OK, MessageBoxImage.Error);
    }

    private void btnCLOSE_Click(object sender, RoutedEventArgs e) {
      if (btnAPPLY.Visibility != System.Windows.Visibility.Hidden) {
        MessageBoxResult myAnswer = MessageBox.Show("Save Changes?", "Confirmation", MessageBoxButton.YesNoCancel);

        if (myAnswer == MessageBoxResult.Cancel)
          return;
        if (myAnswer == MessageBoxResult.Yes)
          btnAPPLY_Click(sender, e);
      }
      this.Close();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e) {
      IsAllLoaded = true;
    }

    private void ShowModified() {
      if (IsAllLoaded)
          btnAPPLY.Visibility = System.Windows.Visibility.Visible;
    } // ShowModified

    private void textBox2_TextChanged(object sender, TextChangedEventArgs e) {
      ShowModified();
    }

    private void textBox1_TextChanged(object sender, TextChangedEventArgs e) {
      ShowModified();
    }
  } // class MainWindow

  public static class Validator {
    public static string ErrorText { get; set; }

    static Validator() {
      ErrorText = "";
    }

    public static bool IsValid(DependencyObject parent) {
      // Validate all the bindings on the parent
      bool valid = true;
      LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();

      while (localValues.MoveNext()) {
        LocalValueEntry entry = localValues.Current;

        if (BindingOperations.IsDataBound(parent, entry.Property)) {
          Binding binding = BindingOperations.GetBinding(parent, entry.Property);

          if (binding.ValidationRules.Count > 0) {
            BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
            expression.UpdateSource();

            if (expression.HasError) {
              ErrorText = expression.ValidationError.ErrorContent.ToString();
              valid = false;
            }
          }
        }
      }

      // Validate all the bindings on the children
      System.Collections.IEnumerable children = LogicalTreeHelper.GetChildren(parent);

      foreach (object obj in children) {
        if (obj is DependencyObject) {
          DependencyObject child = (DependencyObject)obj;

          if (!IsValid(child)) {
            valid = false;
          }
        }
      }
      return valid;
    }
  } // class Validator

ValidationRules.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Windows.Controls;

namespace SWL.Libraries.SysText {
  public class NumberRangeRule : ValidationRule {
    private int _min;
    private int _max;

    public NumberRangeRule() { }

    public int Min {
      get { return _min; }
      set { _min = value; }
    }

    public int Max {
      get { return _max; }
      set { _max = value; }
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
      int val = 0;

      try {
        if (((string)value).Length > 0)
          val = Int32.Parse((String)value);
      } catch (Exception e) {
        return new ValidationResult(false, "Illegal Characters or " + e.Message);
      }

      if ((val < Min) || (val > Max)) {
        return new ValidationResult(false, "Please Enter Number in Range: " + Min + " - " + Max + ".");
      } else {
        return new ValidationResult(true, null);
      }
    }
  }

  public class NameFormatRule : ValidationRule {
    private int _minLength;
    private int _maxLength;

    public NameFormatRule() { }

    public int MinLength {
      get { return _minLength; }
      set { _minLength = value; }
    }

    public int MaxLength 
      get { return _maxLength; }
      set { _maxLength = value; }
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
      try {
        if (((string)value).Length > 0) {
          if (((string)value).Length < MinLength || ((string)value).Length > MaxLength)
            return new ValidationResult(false, String.Format ("Enter a string of {0} to {1} characters in length", MinLength, MaxLength));
          return new ValidationResult(true, null);
        }
        return new ValidationResult(true, null);
      } catch (Exception e) {
        return new ValidationResult(false, "Illegal Characters or " + e.Message);
      }
    }
  }
}

1 个答案:

答案 0 :(得分:0)

<强>更新 我最近不得不处理鼠标悬停错误图标时显示的多条错误消息。我使我的viewmodel实现了IDataErrorInfo。然后我在我的viewmodel中创建了一个类级别的Dictionary,它由每个控件的唯一标识符键入(我只使用了一个枚举),每个控件都有一个错误。然后我创建了一个string类型的公共属性,并将其命名为ErrorText。 ErrorText的getter迭代错误字典并将所有错误构建成一个字符串。

这是一个例子,它可能需要调整。我知道这是非常简化的,但应该让你朝着一个方向前进。对于复杂验证,您仍然可以使用您创建的ValidationRule对象进行验证,然后只检查返回的ValidationResult对象的IsValid属性。

// NOTE:  The enum member name matches the name of the property bound to each textbox
public enum ControlIDs
{
    TextBox1Value = 0,
    TextBox2Value
}

public class MyViewModel : IDataErrorInfo
{
    private readonly Dictionary<ControlIDs, string> errors;

    public string ErrorText
    {
        get
        {
            if (errors.ContainsKey(ControlIDs.TextBox1) && errors.ContainsKey(ControlIDs.TextBox2))
            {
                return "Errors: " + errors[ControlsIDs.TextBox1] + ", " + errors[ControlsIDs.TextBox2];
            }

            if (errors.ContainsKey(ControlIDs.TextBox1))
            {
                return "Error: " + errors[ControlsIDs.TextBox1];
            }

            if (errors.ContainsKey(ControlIDs.TextBox2))
            {
                return "Error: " + errors[ControlsIDs.TextBox2];
            }
        }
    }

    public MyViewModel()
    {
        errors = new Dictionary<ControlIDs, string>();
    }

    private void UpdateErrorCollection(ControlIDs fieldKey, string error)
    {
        if (errors.ContainsKey(fieldKey))
        {
            errors[fieldKey] = error;
        }
        else
        {
            errors.Add(fieldKey, error);
        }

        OnPropertyChanged("ErrorText");
    }

    #region IDataErrorInfo

    public string Error
    {
        get { throw new NotImplementedException(); }
    }

    public string this[string columnName]
    {
        string error = string.Empty;
        if (columnName == ControlIDs.TextBox1Value.ToString())
        {
            if (string.IsNullOrWhiteSpace(TextBox1Value))
            {
                error = "TextBox1 must contain a value";
                UpdateErrorCollection(ControlIDs.TextBox1Value, error);
            }
            else
            {
                errors.Remove(ControlIDs.TextBox1Value);
            }
        }
        else if (columnName == ControlIDs.TextBox2Value))
        {
            if (string.IsNullOrWhiteSpace(TextBox2Value))
            {
                error = "TextBox2 must contain a value";
                UpdateErrorCollection(ControlIDs.TextBox2Value, error);
            }
            else
            {
                errors.Remove(ControlIDs.TextBox2Value);
            }
        }

        // returning null indicates success
        return !string.IsNullOrWhiteSpace(error) ? error : null;
    }

    #endregion
}

现在只需将StatusBarItem TextBlock绑定到ErrorText属性。