2017-04-21 41 views
2

所以我有這樣的簡化版本控制:你如何獲得WPF驗證以泡沫到父級控制?

<local:ImageMapField x:Class="ImageApp.WPF.Controls.ImageMapContentField" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:ImageApp.WPF.Controls" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300" 
         x:Name="Me"> 

    <Grid HorizontalAlignment="Left"> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="150" /> 
      <ColumnDefinition Width="*"/> 
     </Grid.ColumnDefinitions> 
     <Label Grid.Column="0" HorizontalAlignment="Stretch" Style="{DynamicResource BaseLabelStyle}"> 
      <TextBlock Text="{Binding Header, RelativeSource={RelativeSource AncestorType=local:ImageMapContentField, Mode=FindAncestor}}" TextWrapping="WrapWithOverflow"></TextBlock> 
     </Label> 
     <StackPanel Grid.Column="1"> 
      <Image /> 
      <Border Margin="20,5,5,2"> 
       <ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" /> 
      </Border> 
     </StackPanel> 
    </Grid> 

</local:ImageMapField> 

,並用它像這樣我:

<controls:ImageMapContentField Header="Foo Date" 
           FieldName="FooDate" 
           ImageSource="{Binding MyImage, Mode=TwoWay}" 
           ItemsSource="{Binding Map.Items}" 
           Zoom="{Binding MapFieldZoom}"> 
    <controls:ImageMapContentField.DataEntryContent> 
     <Grid> 
      <Grid.ColumnDefinitions> 
       <ColumnDefinition Width="*" /> 
       <ColumnDefinition Width="*"/> 
      </Grid.ColumnDefinitions> 
      <TextBox Grid.Column="0" Text="{Binding MyDate, StringFormat=MM/dd/yyyy, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"> 
       <controls:WatermarkService.Watermark> 
        <TextBlock>Date</TextBlock> 
       </controls:WatermarkService.Watermark> 
      </TextBox> 
      <TextBox Grid.Column="1" Text="{Binding MyTime, StringFormat=HH\:mm}"> 
       <controls:WatermarkService.Watermark> 
        <TextBlock>Time</TextBlock> 
       </controls:WatermarkService.Watermark> 
      </TextBox> 
     </Grid> 
    </controls:ImageMapContentField.DataEntryContent> 
</controls:ImageMapContentField> 

的問題是,因爲我沒有我的模型的屬性綁定到的東西對ImageMapContentFieldValidation.HasErrorImageMapContentField始終爲false,從不觸發。

我得到的是默認的TextBox驗證。

我真正想要的是ImageMapContentField有一個粉紅色的背景。這適用於我直接綁定到其他控件的其他控件,但是我無法使用此控件來控制具有ContentPresenter的控件。

我希望我只是失去了一些東西,可以讓父母捕獲驗證。


如這裏要求是問題的一個小例子:

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:WpfApp1" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 


    <Window.Resources> 
     <Style TargetType="local:CustomTextField"> 
      <Setter Property="Validation.ErrorTemplate"> 
       <Setter.Value> 
        <ControlTemplate> 
         <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2"> 
          <AdornedElementPlaceholder/> 
         </Border> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
      <Style.Triggers> 
       <Trigger Property="Validation.HasError" Value="True"> 
        <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/> 
        <Setter Property="Background" Value="LightPink"/> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 

     <Style TargetType="local:CustomContentControl"> 
      <Setter Property="Validation.ErrorTemplate"> 
       <Setter.Value> 
        <ControlTemplate> 
         <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2"> 
          <AdornedElementPlaceholder/> 
         </Border> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
      <Style.Triggers> 
       <Trigger Property="Validation.HasError" Value="True"> 
        <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/> 
        <Setter Property="Background" Value="LightPink"/> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
    </Window.Resources> 

    <Window.DataContext> 
     <local:MyModel /> 
    </Window.DataContext> 
    <StackPanel> 
     <local:CustomTextField LabelText="Number 1" Value="{Binding Number1, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" /> 
     <local:CustomContentControl LabelText="Number 2"> 
      <local:CustomContentControl.DataEntryContent> 
       <TextBox Text="{Binding Number2, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" /> 
      </local:CustomContentControl.DataEntryContent>  
     </local:CustomContentControl> 
    </StackPanel> 
</Window> 

CustomTextField.xaml

<UserControl x:Class="WpfApp1.CustomTextField" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:WpfApp1" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300" 
      x:Name="Me"> 
     <StackPanel> 
      <Label Content="{Binding ElementName=Me, Path=LabelText}" /> 
      <TextBox Text="{Binding ElementName=Me, Path=Value}" /> 
     </StackPanel> 
</UserControl> 

CustomTextField.cs

public partial class CustomTextField : UserControl 
    { 
     public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
                 "LabelText", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string))); 

     public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
                 "Value", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string))); 

     public string Value 
     { 
      get { return (string) GetValue(ValueProperty); } 
      set { SetValue(ValueProperty, value); } 
     } 

     public string LabelText 
     { 
      get { return (string) GetValue(LabelTextProperty); } 
      set { SetValue(LabelTextProperty, value); } 
     } 

     public CustomTextField() 
     { 
      InitializeComponent(); 
     } 
    } 

CustomContentControl.xaml

<UserControl x:Class="WpfApp1.CustomContentControl" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:WpfApp1" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300" 
      x:Name="Me"> 
     <Grid> 
      <StackPanel> 
       <Label Content="{Binding ElementName=Me, Path=LabelText}" /> 
       <ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" /> 
      </StackPanel> 
     </Grid> 
</UserControl> 

CustomContentControl.cs

public partial class CustomContentControl : UserControl 
    { 
     public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
                 "LabelText", typeof(string), typeof(CustomContentControl), new PropertyMetadata(default(string))); 

     public static readonly DependencyProperty DataEntryContentProperty = DependencyProperty.Register(
                 "DataEntryContent", typeof(object), typeof(CustomContentControl), new PropertyMetadata(default(object))); 

     public object DataEntryContent 
     { 
      get { return (object) GetValue(DataEntryContentProperty); } 
      set { SetValue(DataEntryContentProperty, value); } 
     } 

     public string LabelText 
     { 
      get { return (string) GetValue(LabelTextProperty); } 
      set { SetValue(LabelTextProperty, value); } 
     } 

     public CustomContentControl() 
     { 
      InitializeComponent(); 
     } 
    } 

MyModel.cs

public class MyModel : INotifyPropertyChanged 
    { 
     int _number1; 
     int _number2; 

     public int Number1 
     { 
      get { return _number1; } 
      set 
      { 
       _number1 = value; 
       OnPropertyChanged(); 
      } 
     } 

     public int Number2 
     { 
      get { return _number2; } 
      set 
      { 
       _number2 = value; 
       OnPropertyChanged(); 
      } 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 

     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 
     { 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

+0

您是否有完整的項目來重現問題? –

+0

@SimonMourier添加了一個示例。 – TyCobb

回答

1

的WPF驗證已冒泡到父控件(即使子控件是一個ContentPresenter內) - Validation.ErrorEvent

這裏的問題是,即使該事件冒泡,附加的屬性Validation.HasError沒有得到更新 - 這基本上是由於控件的屬性綁定中沒有錯誤。因此,你看不到背景變化。

爲了糾正這一點 - 您可以使用此代碼:

更新風格主窗口。XAML

<Style TargetType="local:CustomContentControl"> 
     <Setter Property="Validation.ErrorTemplate"> 
      <Setter.Value> 
       <ControlTemplate> 
        <Border BorderBrush="Red" BorderThickness="2" CornerRadius="2"> 
         <AdornedElementPlaceholder/> 
        </Border> 
       </ControlTemplate> 
      </Setter.Value> 
     </Setter> 
     <Style.Triggers> 
      <Trigger Property="HasErrors" Value="True"> 
       <Setter Property="Background" Value="LightPink"/> 
      </Trigger> 
     </Style.Triggers> 
    </Style> 

而且,更新CustomContentControl添加HasErrors依賴屬性,並驗證錯誤事件處理程序

public static readonly DependencyProperty HasErrorsProperty = DependencyProperty.Register("HasErrors", typeof(bool), typeof(CustomContentControl), new PropertyMetadata(false)); 

    public bool HasErrors 
    { 
     get { return (bool)GetValue(HasErrorsProperty); } 
     set { SetValue(HasErrorsProperty, value); } 
    } 

    public CustomContentControl() 
    { 
     InitializeComponent(); 

     Validation.AddErrorHandler(this, (s, args) => { 
      if (args.Action == ValidationErrorEventAction.Added) 
      { 
       this.ToolTip = args.Error.ErrorContent; 
       HasErrors = true; 
      } 
      else 
      { 
       this.ToolTip = null; 
       HasErrors = false; 
      } 
     }); 
    } 

和你的背景將被更新。 enter image description here

+0

謝謝!我知道必須有一些我錯過的東西。希望以某種方式觸發標準的'Validation.HasError',所以它就像其他所有東西一樣,但是這會起作用並且非常簡單。 – TyCobb

+0

是的 - 我的第一本能也是手動將錯誤添加到Validation.Errors集合中以觸發Validation.HasError中的更新。但引入一個自定義的依賴屬性似乎更簡單;特別是如果您只有一個綁定驗證進行跟蹤。如果你有多個綁定來跟蹤(多個子控件) - 那麼會推薦使用Validation.Errors來跟蹤錯誤。 http://stackoverflow.com/questions/2174251/wpf-validation-manually-adding-errors-into-validation-errors-collection – Ada