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

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

Related

Child controls in an ItemsControl are being disabled, but I can't figure out what's disabling them

I'm trying to create a browser-esque tab interface in WPF. I've got it put together and it worked for a while, but now all the buttons that make the tabs function are disabled. In the live visual tree it says overidden and shows coercion setting IsEnabled = false. None the parent items are disabled.
Couple things I've tried. I tried explicitly stating that everything was enabled. This didn't help. The next thing I tried was to wipe everything out and just use a plain button. This worked, so I think the problem is somewhere in my styles.
This Doesn't Work
<ItemsControl ItemsSource="{Binding PageViewModels}" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="70,15,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Background="{x:Null}" Height="24" MinWidth="140" Padding="0,0,0,0" BorderThickness="0" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" CommandParameter="{Binding }">
<Border MinWidth="140" Height="24" >
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsEqual}">
<Binding Path=".ID"/>
<Binding Path="DataContext.CurrentPageViewModel.ID" RelativeSource="{RelativeSource AncestorType=Window, Mode=FindAncestor}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="{StaticResource LIGHT_Main_100}"/>
<Setter Property="BorderThickness" Value="0,0,0,3"/>
<Setter Property="BorderBrush" Value="{StaticResource DARK_CYAN_Secondary_100}"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value="{StaticResource LIGHT_Main_85}"/>
<Setter Property="BorderThickness" Value="0,0,.5,.5"/>
<Setter Property="BorderBrush" Value="{StaticResource DARK_Faded }"/>
</Style>
</Border.Style>
<DockPanel VerticalAlignment="Stretch" Margin="10,0,0,0">
<Label DockPanel.Dock="Left" Content="{Binding Name}" FontSize="15" Padding="0" Loaded="Label_Loaded" >
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsEqual}">
<Binding Path=".ID"/>
<Binding Path="DataContext.CurrentPageViewModel.ID" RelativeSource="{RelativeSource AncestorType=Window, Mode=FindAncestor}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="{StaticResource DARK_Faded}"/>
<Setter Property="FontWeight" Value="ExtraLight"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Label.Style>
</Label>
<Button DockPanel.Dock="Right" Background="{x:Null}" Content="⤫" Height="24" Width="28" FontSize="15" Padding="0,-6,0,0" HorizontalAlignment="Right" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" BorderThickness="0" Command="{Binding ClosePage }" CommandParameter="{Binding}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource IsEqual}">
<Binding Path=".ID"/>
<Binding Path="DataContext.CurrentPageViewModel.ID" RelativeSource="{RelativeSource AncestorType=Window, Mode=FindAncestor}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Hidden"/>
</Style>
</Button.Style>
</Button>
</DockPanel>
</Border>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This Does Work
<ItemsControl ItemsSource="{Binding PageViewModels}" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="70,15,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<Button Content="Test"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
EDIT
This was probably what caused my issues. A while back I copied this code from an example online and didn't understand it, but it worked. I think canExecute was false because I was not sending a string parameter, but rather an object. Thanks for your help #Neil
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((string) p),
p => p is string);
}
return _changePageCommand;
}
}
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion // ICommand Members
}
The CanExecute for your ChangePageCommand command is probably returning false. To check simply remove the command binding and see if the button is still disabled.
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }"

how do I collapse parent content if all children are collapsed

I would like to show/collapse a panel (Stakpanel, Grid etc) if every children is collapsed and show it back if at least one if its children is visible again.
Which would be the best way to achive this? (converter, triggers, other thing?)
Thanks!!
I did a converter but it doesn´t fire when I change the visibility of children
public class HasChildrenVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Panel parent = value as Panel;
foreach (var child in parent.Children.OfType<UIElement>())
{
if (child.IsVisible)
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;//no implementado
}
}
this is the xaml example
<Window.Resources>
<local:HasChildrenVisibilityConverter x:Key="converter" />
</Window.Resources>
<StackPanel Visibility="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource converter}}">
<TextBlock x:Name="text1"> Text 1</TextBlock>
<TextBlock x:Name="text2"> Text 2</TextBlock>
</StackPanel>
A MultiDataTrigger would work quite well here. Here's a simple example with a StackPanel and a couple of TextBlock
I declare the Triggers in the Style and apply that Style to the relevant StackPanel
MainWindow.xaml
<Window.Resources>
<Style x:Key="ShowHideStyle" TargetType="StackPanel">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtName, Path=Visibility}" Value="Collapsed" />
<Condition Binding="{Binding ElementName=txtDescription, Path=Visibility}" Value="Collapsed" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Collapsed"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtName, Path=Visibility}" Value="Visible" />
<Condition Binding="{Binding ElementName=txtDescription, Path=Visibility}" Value="Visible" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<StackPanel Style="{StaticResource ShowHideStyle}" Height="300" Width="300" Background="Red">
<TextBlock x:Name="txtName" Text="name" />
<TextBlock x:Name="txtDescription" Text="description" />
</StackPanel>
<Button x:Name="btnHide" Width="100" Height="30" Content="hide" Click="btnHide_Click"/>
<Button x:Name="btnShow" Width="100" Height="30" Content="show" Click="btnShow_Click"/>
</StackPanel>
Mainwindow.xaml.cs
I just add the button click events to hide/show child elements
private void btnHide_Click(object sender, RoutedEventArgs e)
{
txtDescription.Visibility = System.Windows.Visibility.Collapsed;
txtName.Visibility = System.Windows.Visibility.Collapsed;
}
private void btnShow_Click(object sender, RoutedEventArgs e)
{
txtDescription.Visibility = System.Windows.Visibility.Visible;
txtName.Visibility = System.Windows.Visibility.Visible;
}

User Control - dependency property to Change Image Issues

i'm having issues setting the Image from a dependency property. It seems like the trigger doesnt fire. I just want hide/show and image, or set the source if possible.
public static readonly DependencyProperty HasSingleValueProperty =
DependencyProperty.Register("HasSingleValue", typeof(bool), typeof(LevelControl), new
FrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public bool HasSingleValue
{
get { return (bool)GetValue(HasSingleValueProperty); }
set { SetValue(HasSingleValueProperty, value); }
}
public LevelControl()
{
this.InitializeComponent();
//this.DataContext = this;
LayoutRoot.DataContext = this;
}
//Control Markup
<Grid x:Name="LayoutRoot">
<Image x:Name="xGreenBarClientTX" HorizontalAlignment="Stretch" Height="13" Margin="7,8.5,7,0"
Stretch="Fill"
VerticalAlignment="Top"
Width="47"
Canvas.Left="181.67"
d:LayoutOverrides="Height" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSingleValue}" Value="True">
<Setter Property="Opacity" Value="100"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasSingleValue}" Value="False">
<Setter Property="Opacity" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
Here we go
you have to update your binding as follows (I used Ellipse instead for testing)
<Window
x:Name="myWindow">
<Style TargetType="{x:Type Ellipse}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSingleValue, ElementName=myWindow}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasSingleValue, ElementName=myWindow}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
Then you need to set HasSingleValue
I created two buttons to show and hide my Ellipse.
I think the trouble may be with how you're trying to update the dependency property because I've made a slight alteration to your example to verify the bindings and such do work as expected. I had suspected that there may be a problem with binding to a property on the grid's data context from the image, but that appears to be fine.
It wasn't clear what kind of class you were providing as a data context, but if you're using dependency properties then it needs to be a DependencyObject.
I've provided an example here using a text block and a toggle button to change the dependency property. (assuming you're using code-behind)
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="xGreenBarClientTX" HorizontalAlignment="Stretch" Height="13" Margin="7,8.5,7,0"
VerticalAlignment="Top"
Width="200"
Canvas.Left="181.67" >
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSingleValue}" Value="True">
<Setter Property="Text" Value="Is Set"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasSingleValue}" Value="False">
<Setter Property="Text" Value="Is Not Set"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Button Grid.Column="1" Content="Toggle" Click="Button_Click" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
and the code-behind:
public MainWindow()
{
InitializeComponent();
LayoutRoot.DataContext = new VM() { HasSingleValue = true };
}
private void Button_Click( object sender, RoutedEventArgs e )
{
var vm = LayoutRoot.DataContext as VM;
if ( vm == null )
return;
vm.HasSingleValue = !vm.HasSingleValue;
}
public class VM : DependencyObject
{
public static readonly DependencyProperty HasSingleValueProperty =
DependencyProperty.Register( "HasSingleValue", typeof( bool ), typeof( VM ), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault ) );
public bool HasSingleValue
{
get { return (bool) GetValue( HasSingleValueProperty ); }
set { SetValue( HasSingleValueProperty, value ); }
}
}

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

WPF/XAML - DataTriggers to set ValidatesOnDataErrors = false/true when a radio button is checked

I am working on an application that implements the MVVM design pattern with DataAnnotations. The application is a dynamically generated list of pages. On one of those pages, I have 10 required fields with 2 yes/no radio buttons. Those 10 fields are divided into two groups and each group is wwapped with a border tag. Each border's visibility is bound with the radio buttons for hidden/visible.
My question is if yes was selected and the related 5 required text boxes are displayed how can i set the ValidatesOnDataErrors to false/true and clear the text boxes values of the other hidden required TextBoxes?
Here is a code Snippet.
thanks
<Border>
<Border.Style>
<Style>
<Setter Property="Border.Visibility" Value="Hidden"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PresentlyEmployed_yes, Path=IsChecked}"
Value="True">
<Setter Property="Border.Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid Height="Auto" Width="Auto">
<Label Name="JobTitle"
Content="{x:Static properties:Resources.JobTitlelbl}" />
<TextBox Name="JobTitle" Text="{Binding JobTitle, Mode=TwoWay,
ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding PrimaryInsuredBusinessDuties, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged, IsAsync=True}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=PresentlyEmployed_yes, Path=IsChecked}"
Value="True">
<Setter Property="Text" Value="{Binding JobTitle, Mode=TwoWay,
ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=PresentlyEmployed_yes, Path=IsChecked}"
Value="False">
<Setter Property="Text" Value="{Binding JobTitle, Mode=TwoWay,
ValidatesOnDataErrors=False, UpdateSourceTrigger=PropertyChanged}"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</Border>
Try setting the Validation.Template to {x:Null} if it shouldn't show the Validation Error
<StackPanel>
<ListBox x:Name="MyListBox" SelectedIndex="0">
<ListBoxItem>Validate Value 1</ListBoxItem>
<ListBoxItem>Validate Value 2</ListBoxItem>
</ListBox>
<TextBox Text="{Binding Value1, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=MyListBox}" Value="1" >
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox Text="{Binding Value2, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=MyListBox}" Value="0" >
<Setter Property="Validation.ErrorTemplate" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
Sure, here is how my validationbase class looks like (Simplified)
public class ValidationViewModelBase : ViewModelBase, IDataErrorInfo, IValidationExceptionHandler
{
private Dictionary<string, Func<ValidationViewModelBase, object>> _propertyGetters;
private Dictionary<string, ValidationAttribute[]> _validators;
/// <summary>
/// Gets the error message for the property with the given name.
/// </summary>
/// <param name="propertyName">Name of the property</param>
public string this[string propertyName]
{
IList<string> fieldsNames = new List<string>();
{
if (propertyName == "PresentlyEmployed")
{
//if its true then
fieldsNames.Add("JobTitle");
AddFieldsValidation(fieldsNames);
}else{
fieldsNames.Add("EmploymentAddress");
RemoveValidation(fieldsNames);
}
if (this.propertyGetters.ContainsKey(propertyName))
{
var propertyValue = this.propertyGetters[propertyName](this);
var errorMessages = this.validators[propertyName]
.Where(v => !v.IsValid(propertyValue))
.Select(v => v.ErrorMessage).ToArray();
return string.Join(Environment.NewLine, errorMessages);
}
return string.Empty;
}
/// <summary>
/// Gets an error message indicating what is wrong with this object.
/// </summary>
public string Error
{
get
{
var errors = from validator in this.validators
from attribute in validator.Value
where !attribute.IsValid(this.propertyGetters[validator.Key](this))
select attribute.ErrorMessage;
return string.Join(Environment.NewLine, errors.ToArray());
}
}
}
/// <summary>
/// Gets the number of properties which have a validation attribute and are currently valid
/// </summary>
public int ValidPropertiesCount
{
get
{
var query = from validator in this.validators
where validator.Value.All(attribute => attribute.IsValid(this.propertyGetters[validator.Key](this)))
select validator;
var count = query.Count() - this.validationExceptionCount;
return count;
}
}
}
/// <summary>
/// Gets the number of properties which have a validation attribute
/// </summary>
public int TotalPropertiesWithValidationCount
{
get
{
return this.validators.Count();
}
}
public ValidationViewModelBase()
{
this.validators = this.GetType()
.GetProperties()
.Where(p => this.GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, p => this.GetValidations(p));
this.propertyGetters = this.GetType()
.GetProperties()
.Where(p => this.GetValidations(p).Length != 0)
.ToDictionary(p => p.Name, p => this.GetValueGetter(p));
}
private ValidationAttribute[] GetValidations(PropertyInfo property)
{
return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
}
private Func<ValidationViewModelBase, object> GetValueGetter(PropertyInfo property)
{
return new Func<ValidationViewModelBase, object>(viewmodel => property.GetValue(viewmodel, null));
}
private int validationExceptionCount;
public void ValidationExceptionsChanged(int count)
{
this.validationExceptionCount = count;
this.OnPropertyChanged("ValidPropertiesCount");
}

Resources