Element binding not updating when dependency property changed - wpf

I have a custom button control which has a dependency property "IsRequired"
public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.RegisterAttached(nameof(IsRequired), typeof(bool), typeof(RequiredButton), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
public bool IsRequired
{
get { return (bool)GetValue(IsRequiredProperty); }
set { SetValue(IsRequiredProperty, value); }
}
And implements an interface
public interface IRequiredControl
{
bool IsEnabled { get; }
bool IsRequired { get; }
}
And I have a converter that uses this interface
<sharedConverters:IsRequiredToImageConverter x:Key="IsRequiredToImageConverter"
DisabledImage="{StaticResource DisabledDrawing}"
NormalImage="{StaticResource NormalDrawing}"
RequiredImage="{StaticResource IsRequiredDrawing}" />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IRequiredControl requiredControl)
{
return !requiredControl.IsEnabled ? DisabledImage : requiredControl.IsRequired ? RequiredImage : NormalImage;
}
return DependencyProperty.UnsetValue;
}
I use the converter and bind the image source to the control, as seen below.
<DataTemplate x:Key="RightSideAddTemplate">
<sharedControls:RequiredButton x:Name="addButton"
Command="{x:Static commands:AddCommand}"
IsRequired="{Binding IsButtonRequired}">
<Image Source="{Binding ElementName=addButton, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IsRequiredToImageConverter}}" />
</sharedControls:RequiredButton>
</DataTemplate>
The issue occurs here where the converter is never called when the "IsButtonRequired" viewmodel property is changed. When I set the "IsRequired" of the "AddButton" explicitly to true it works correctly. How can I make the converter update on a change to the "IsRequired" property?
Note I have another solution working where I use a multivalue converter, but I would prefer to get element binding solution working, because it requires much less xaml.
<Image Style="{StaticResource AddImageButtonStyle}">
<Image.Source>
<MultiBinding Converter="{StaticResource IsRequiredToImageMultiConverter}">
<MultiBinding.Bindings>
<Binding ElementName="addButton" Path="IsEnabled" />
<Binding Path="IsButtonRequired" />
</MultiBinding.Bindings>
</MultiBinding>
</Image.Source>
</Image>

The Binding expression
<Image Source="{Binding ElementName=addButton,
Converter={StaticResource IsRequiredToImageConverter}}" />
binds directly to the RequiredButton control, and is not supposed to be triggered when a property of the control changes.
The proper way to implement this is a MultiBinding on both the IsEnabled and IsRequired properties of the control:
<MultiBinding Converter="{StaticResource IsRequiredToImageMultiConverter}">
<MultiBinding.Bindings>
<Binding ElementName="addButton" Path="IsEnabled"/>
<Binding ElementName="addButton" Path="IsRequired"/>
</MultiBinding.Bindings>
</MultiBinding>
The multi-value converter would have to test two boolean values.
Alternatively to using a MultiBinding, a Style with a set of MultiTriggers would also work:
<Style TargetType="sharedControls:RequiredButton">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource DisabledDrawing}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="true" />
<Condition Property="IsRequired" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource NormalDrawing}"/>
</Setter.Value>
</Setter>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="true" />
<Condition Property="IsRequired" Value="true" />
</MultiTrigger.Conditions>
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource IsRequiredDrawing}"/>
</Setter.Value>
</Setter>
</MultiTrigger>
</Style.Triggers>
</Style>

Related

WPF - Binding data template

I'm trying to bind <DataTemplate> on Telerik RadDiagram ShapeStyle:
<telerik:RadDiagram x:Name="Diagram"
ShapeStyle="{StaticResource NodeStyle}"
GraphSource="{Binding GraphSource}"/>
This style looks like this:
<Grid.Resources>
<Style x:Key="NodeStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<MultiBinding Converter="{StaticResource StyleConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}"/>
<Binding Path=""/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
...
</Grid.Resources>
StyleConverter:
class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
if (values[0] is BilledMsisdnViewModel)
return targetElement.FindResource("BilledMsisdnControl");
else if(values[0] is BilledImeiViewModel)
return targetElement.FindResource("ImeiControl");
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Those resources (BilledMsisdnControl and ImeiControl) are defined in <UserControl.Resources>:
<UserControl.Resources>
<ResourceDictionary>
<local:BilledMsisdnControl x:Key="BilledMsisdnControl"/>
<local:ImeiControl x:Key="ImeiControl"/>
<local:StyleConverter x:Key="StyleConverter"/>
</ResourceDictionary>
</UserControl.Resources>
And they are created by me user controls.
So my goal here is according to binded view model type select user controll (this part works) and then use it as ContentTemplate (this don't work). I'm getting just object namespace nad name for ImeiControl or BilledMsisdnControl in diagram node.
How can I fix this?
According to the documentation you should use style selector
public class NodeStyleSelector : StyleSelector
{
public Style BilledMsisdnStyle { get; set; }
public Style BilledImeiStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
if (item is BilledMsisdnViewModel)
return BilledMsisdnStyle;
else if (item is BilledImeiViewModel)
return BilledImeiStyle;
else return base.SelectStyle(item, container);
}
}
Define styles with ContentTemplate. Inside ContentTemplate add your user controls.
<Style x:Key="BilledMsisdnStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<local:BilledMsisdnControl/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BilledImeiStyle" TargetType="telerik:RadDiagramShape">
<Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
<Setter Property="Content" Value="{Binding}" />
<Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<local:ImeiControl/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Add shape style selector
<styleselectors:NodeStyleSelector x:Key="CustomShapeStyleSelector"
BilledMsisdnStyle="{StaticResource BilledMsisdnStyle}"
BilledImeiStyle="{StaticResource BilledImeiStyle}" />
Use ShapeStyleSelector for your RadDiagram.
<telerik:RadDiagram x:Name="Diagram"
ShapeStyleSelector="{StaticResource CustomShapeStyleSelecto}"
GraphSource="{Binding GraphSource}"/>

Access Button's Tag property from ControlTemplate in XAML

In my Window I have a series of six buttons that indicate the six possible states for one of the properties of my ViewModel. The one that's active needs to be highlighted. To do this, I've created the following ControlTemplate for the buttons:
<ControlTemplate x:Key="SnijRichtingTemplate" TargetType="Button">
<Border Name="toggleButton" BorderThickness="1" BorderBrush="{StaticResource KleurRadioCheckOuter}" Background="Transparent" Width="20" Height="20" Cursor="Hand">
<TextBlock Name="text" Foreground="{StaticResource KleurRadioCheckOuter}"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag}"
ToolTip="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag.ToolTip}"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityToBooleanConverter}">
<Binding Path="SnijRichting" />
<Binding Path="Tag" RelativeSource="{RelativeSource TemplatedParent}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="toggleButton" Property="BorderBrush" Value="{StaticResource KleurTekstDonker}" />
<Setter TargetName="text" Property="Foreground" Value="{StaticResource KleurTekstDonker}" />
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="toggleButton" Property="BorderBrush" Value="{StaticResource Kleur2}" />
<Setter TargetName="text" Property="Foreground" Value="{StaticResource Kleur2}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
The template is then used like so:
<Button Grid.Column="0" Template="{StaticResource SnijRichtingTemplate}"
HorizontalAlignment="Right" Click="SnijRichting_Click"
Tag="{StaticResource XLinks}" />
Where the tag is just an instance defined in the XAML:
<wg:SnijRichting x:Key="XLinks" SnijAs="X" Negatief="True" />
The MultibindingConverter is nothing fancy:
public class EqualityToBooleanConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Basically, each button has a Tag with the new value. In the click handler, the ViewModel's property is set to the button's Tag. The button state is updated by checking whether the button's Tag is equal to the ViewModel property.
The problem is that this doesn't work. When the EqualityToBooleanConverter is executed, the second value is null. By removing the Path="Tag" bit from the second binding I see that the TemplatedParent is a ContentPresenter rather than the Button I was expecting, which explains why Tag is null. Now of course I could write a ValueConverter to get the correct value using VisualTreeHelper.GetParent to get the ContentPresenter's parent (which returns the desired Button), but surely there must be a way to do this from XAML? The obvious Path="Parent.Tag" doesn't work, since the Parent of the ContentPresenter is apparently a Border.
Does anyone know how to access the button's Tag property from XAML?
Found the problem. Turns out you need {RelativeSource Mode=Self}, not {RelativeSource TemplatedParent}.

multiple binding to IsEnable

I need to bind a TextBox that meets two criteria:
IsEnabled if Text.Length > 0
IsEnabled if user.IsEnabled
Where user.IsEnabled is pulled from a data source. I was wondering if anyone had a easy method for doing this.
Here is the XAML:
<ContentControl IsEnabled="{Binding Path=Enabled, Source={StaticResource UserInfo}}">
<TextBox DataContext="{DynamicResource UserInfo}" Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding Path=Text, RelativeSource={RelativeSource Self}, Converter={StaticResource LengthToBool}}"/>
</ContentControl>
As GazTheDestroyer said you can use MultiBinding.
You can also acomplish this with XAML-only solution using MultiDataTrigger
But you should switch the conditions cause triggers support only equality
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text.Length}" Value="0" />
<Condition Binding="{Binding Source=... Path=IsEnabled}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger>
</Style.Triggers>
If one of the condition is not met the value be set to its default or value from the style. But do not set local value as it overrides style's and trigger's values.
Since you only need a logical OR, you just need two Triggers to your each of the properties.
Try this XAML:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=InputText, Path=Text}" Value="" >
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=MyIsEnabled}" Value="False" >
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<Label>MyIsEnabled</Label>
<CheckBox IsChecked="{Binding Path=MyIsEnabled}" />
</StackPanel>
<TextBox Name="InputText">A block of text.</TextBox>
<Button Name="TheButton" Content="A big button.">
</Button>
</StackPanel>
I set DataContext to the Window class which has a DependencyProperty called MyIsEnabled. Obviously you would have to modify for your particular DataContext.
Here is the relevant code-behind:
public bool MyIsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public static readonly DependencyProperty MyIsEnabledProperty =
DependencyProperty.Register("MyIsEnabled", typeof(bool), typeof(MainWindow), new UIPropertyMetadata(true));
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
Hope that helps!
Bind IsEnabled using a MultiBinding

Equiv. to Coalesce() in XAML Binding?

In SQL I can do this:
Select Coalesce(Property1, Property2, Property3, 'All Null') as Value
From MyTable
If Property1, 2 and 3 are all null, then I get 'All Null'
How do I do this in XAML? I tried the following, but no luck:
<Window.Resources>
<local:Item x:Key="MyData"
Property1="{x:Null}"
Property2="{x:Null}"
Property3="Hello World" />
</Window.Resources>
<TextBlock DataContext="{StaticResource MyData}">
<TextBlock.Text>
<PriorityBinding TargetNullValue="All Null">
<Binding Path="Property1" />
<Binding Path="Property2" />
<Binding Path="Property3" />
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
The result should be 'Hello World' but instead it is 'All Null'
I hope my question is clear.
You'd have to build a custom IMultiValueConverter to do that and use a MultiBinding. PriorityBinding uses the first binding in the collection that produces a value successfully. In your case, the Property1 binding resolves immediately, so it's used. Since Property1 is null, the TargetNullValue is used.
A converter like this:
public class CoalesceConverter : System.Windows.Data.IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values == null)
return null;
foreach (var item in values)
if (item != null)
return item;
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And MultiBinding like this:
<Window.Resources>
<local:Item x:Key="MyData"
Property1="{x:Null}"
Property2="{x:Null}"
Property3="Hello World" />
<local:CoalesceConverter x:Key="MyConverter" />
</Window.Resources>
<TextBlock DataContext="{StaticResource MyData}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding Path="Property1" />
<Binding Path="Property2" />
<Binding Path="Property3" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Since you are binding to a String, null is a valid value for the PriorityBinding. I'm not sure what your Item class's property types are, but if you use Object, and set them to DependencyProperty.UnsetValue, you will get the behavior you are looking for.
The PriorityBinding documentation's remarks section describes how it works in more detail.
The PriorityBinding is only looking for DependencyProperty.UnsetValue to advance to the next Binding. Since Property1 exists it is set and the PriorityBinding is taking the value of it.
For a pure XAML solution, this Style will do the job:
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text"
Value="{Binding Property1}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Property1}"
Value="{x:Null}">
<Setter Property="Text"
Value="{Binding Property2}" />
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Property1}"
Value="{x:Null}" />
<Condition Binding="{Binding Property2}"
Value="{x:Null}" />
</MultiDataTrigger.Conditions>
<Setter Property="Text"
Value="{Binding Property3}" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Property1}"
Value="{x:Null}" />
<Condition Binding="{Binding Property2}"
Value="{x:Null}" />
<Condition Binding="{Binding Property3}"
Value="{x:Null}" />
</MultiDataTrigger.Conditions>
<Setter Property="Text"
Value="All Null" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Although, it's a bit convoluted way of doing it, and IMHO, doesn't belong in the UI but in the ViewModel.

wpf - validation - how to show tooltips and disable "run" button

Hi
I need to validate some of textboxes in my application. I decied to use validation rule
"DataErrorValidationRule". That's why in my class I implemented IDataErrorInfo interface and wrote aproperiate functions. In my xaml code I added bindings and validation rules to textboxes
<TextBox x:Name="txtName" Grid.Column="3" Grid.Row="1" TextAlignment="Center" >
<TextBox.Text>
<Binding Path="Name" >
<Binding.ValidationRules>
<DataErrorValidationRule></DataErrorValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Validation of this textbox is OK - I mean red frame appears on textbox if data is wrong. However what I need to do is to show tooltip on that textbox, but what is more important I have to disable button "Run" if any textboxes have wrong data. What is the best way to do taht ??
EDIT
First problem was solved, but I have an another. I need to use MultiBindings to validate my Button. So I did sth like that
<Button x:Name="btnArrange" Grid.Column="0" Content="Rozmieść" Click="btnArrange_Click" >
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource BindingConverter}">
<Binding ElementName="txtName" Path="Validation.HasError" />
<Binding ElementName="txtSurname" Path="Validation.HasError"/>
<Binding ElementName="txtAddress" Path="Validation.HasError"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
My Converter looks like that
public class Converters : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(values !=null && values.Length > 0)
{
if (values.Cast<type>().Count(val => val) > 0)
return false;
return true;
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
#endregion
}
However I get invalidCastException in this converter. What is a proper cast in that case? I thoght as if HasError is a bool type so I should cast to bool.
To show the error message in a tool tip put this into your Application.Resources:
<Style x:Key="textBoxInError" 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>
( Example from http://msdn.microsoft.com/en-us/library/system.windows.controls.validation.errortemplate.aspx )
To enable/disable a button you could use something along the line of
<Button x:Name="btnOK" Content="OK" IsDefault="True" Click="btnOK_Click">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txt1, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=txt2, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
or you could implement ICommand and use command binding.
EDIT
Here is a fully working example. It displays a window with two TextBoxes. The Button is enabled if and only if both TextBoxes are non-empty. Create a project called ValidationDemo and put the following files in it:
MainWindow.xaml:
<Window x:Class="ValidationDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="146" Width="223">
<Window.Resources>
<Style 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>
</Window.Resources>
<Grid>
<Label Content="A" Height="28" HorizontalAlignment="Left" Margin="46,7,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Name="txtA" Text="{Binding Path=TextA, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Height="23" HorizontalAlignment="Left" Margin="69,12,0,0" VerticalAlignment="Top" Width="120" />
<Label Content="B" Height="28" HorizontalAlignment="Left" Margin="46,39,0,0" Name="label2" VerticalAlignment="Top" />
<TextBox Name="txtB" Text="{Binding Path=TextB, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Height="23" HorizontalAlignment="Left" Margin="69,41,0,0" VerticalAlignment="Top" Width="120" />
<Button Name="btnOk" Content="OK" Height="23" HorizontalAlignment="Left" Margin="114,70,0,0" VerticalAlignment="Top" Width="75" Click="btnOk_Click">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtA, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=txtB, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace ValidationDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Model model = new Model();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.model;
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}
Model.cs:
using System;
using System.ComponentModel;
namespace ValidationDemo
{
public class Model : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
private string textA = string.Empty;
public string TextA
{
get
{
return this.textA;
}
set
{
if (this.textA != value)
{
this.textA = value;
this.OnPropertyChanged("TextA");
}
}
}
private string textB = string.Empty;
public string TextB
{
get
{
return this.textB;
}
set
{
if (this.textB != value)
{
this.textB = value;
this.OnPropertyChanged("TextB");
}
}
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = string.Empty;
switch (columnName)
{
case "TextA":
if (string.IsNullOrEmpty(this.textA))
{
result = "'A' must not be empty";
}
break;
case "TextB":
if (string.IsNullOrEmpty(this.textA))
{
result = "'B' must not be empty";
}
break;
}
return result;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
<Window.Resources>
<Style x:Key="ElementInError" TargetType="{x:Type FrameworkElement}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- ... -->
<TextBox x:Name="txtName" Style="{StaticResource ElementInError}">
<!-- ... -->
</TextBox>
<!-- ... -->
<Button x:Name="OkButton" Content="Ok" Margin="5" Click="OkButton_Click">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=txtName,Path=(Validation.HasError)}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
If you create a validation summary you can bind the IsEnabled property of your "Run" button to it's HasErrors property.
You'll need to use an intermediate property or converter as you want the IsEnabled to be true when HasErrors is false (and vice versa).

Resources