WPF error template not showing - wpf

I get no binding errors and this code works at another place. I haven't found out yet what I do now differently to the code where it works and it's not that much code.
In UserControl.Resource:
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<Grid Margin="2,0,0,0">
<Ellipse Width="20" Height="20" Fill="Red"/>
<TextBlock Foreground="White" Text="X" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</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>
Below in Xaml too:
<TextBlock Height="23" HorizontalAlignment="Left" Margin="22,90,0,0"
Text="Keywords" VerticalAlignment="Top"/>
<TextBox Height="23" HorizontalAlignment="Left" Margin="22,108,0,0"
VerticalAlignment="Top" Width="244">
<Binding Path="Tags" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
The button SAVE in my ViewModel is only activated when the Model.Tags property is longer 10 chars input from the user. The button activation/disable works fine when I enter 10,11 and then back 8 chars. All the property changes are fired.
Model:
namespace TBM.Model
{
public class Document : EntityBase , IDataErrorInfo
{
public int Id { get; set; }
public string DocumentName { get; set; }
public string Tags { get; set; }
public byte[] DocumentData { get; set; }
public int PeriodId { get; set; }
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public bool IsValid
{
get
{
foreach (string property in ValidatedProperties)
if (GetValidationError(property) != null)
return false;
return true;
}
}
static readonly string[] ValidatedProperties = { "Tags", };
private string GetValidationError(string propertyName)
{
if (Array.IndexOf(ValidatedProperties, propertyName) < 0)
return null;
string error = null;
switch (propertyName)
{
case "Tags": error = this.IsTagsEmpty(Tags); break;
default:
Debug.Fail("Unexpected property being validated on Document: " + propertyName);
break;
}
return error;
}
private string IsTagsEmpty(string value)
{
if (value != null && value.Trim().Length >= 10)
return null;
else
return "The keywords must have at least 10 chars!";
}
}
}
ViewModel:
public RelayCommand SaveDocumentCommand
{
get { return _saveDocumentCommand ?? (_saveDocumentCommand =
new RelayCommand(() => SaveDocument(),() => CanSaveDocument())); }
}
private bool CanSaveDocument()
{
return _document.IsValid;
}
//...
What does not work is the ErrorTemplate with the red Ellipse is not showing at all?
UPDATE: Exactly the below code works in a TEST project. But in my productive project It does not find the Resource??? Why the heck this?
<TextBlock Height="23" HorizontalAlignment="Left" Margin="22,89,0,0"
Text="Keywords" VerticalAlignment="Top"/>
<TextBox Style="{StaticResource bla}" Height="23" HorizontalAlignment="Left"
Margin="22,109,0,0" VerticalAlignment="Top" Width="244">
<Binding Path="Tags" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"
ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox>
<UserControl.Resources>
<Style x:Name="bla" TargetType="TextBox">
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="0,1,0,1"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder/>
<Grid Margin="2,0,0,0">
<Ellipse Width="20" Height="20" Fill="Red"/>
<TextBlock Foreground="White" Text="X" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</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>
</UserControl.Resources>

I had similar problem. Fight it for hours just to realize that something was wrong with adorner layer.
What I did is put my with controls inside . And that was it. For some reason this decorator layer sometimes is gone. This is certainly true for TabControl (but in my case it was some other reason).
So it should look like this
<AdornerDecorator>
<Grid>
<TextBox .../>
</Grid>
</AdornerDecorator>
Hope this helps!

I have had a similar problem to this, it turned out not to be the template but the validation was not returning what I expected.

The ViewModel have to implement IDataErrorInfo, not the Model. The ViewModel binds to your View as the DataContext, not the Model, so implement the interface in ViewModel and bind to corresponding properties in XAML.

Related

TabControl view doesn't update on template change

I need to change the view of a TabControl's content on-the-fly.
I am guessing the best way to accomplish this is to define the view as a DataTemplate, and then change said template using a trigger.
In my test app, the background color is tied to the same data trigger as the template. The background color updates immediately upon making the radio button selection.
Expected behavior: The Tab Item Content / DataTemplate also updates immediately.
Actual Behavior: Tab content view does not update until the tab selection is changed.
Here's my Minimal, Complete, and Verifiable example:
Window XAML
<Window x:Class="ChangeView.Window1"
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"
Title="Window1" Height="350" Width="400">
<Window.Resources>
<DataTemplate x:Key="ContentTemplate1">
<Grid>
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding MyBlurb}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ContentTemplate2">
<Grid>
<Label HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Content="{Binding MyHeader}" Background="Black" Foreground="White" FontSize="72"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType1}" Value="False">
<Setter Property="Background" Value="Chartreuse"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType1}" Value="True">
<Setter Property="Background" Value="Bisque"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Margin="10,38,0,0" Text="Content Template:"/>
<RadioButton x:Name="radio1" Margin="120,40,0,0" Grid.ColumnSpan="2" Content="1" GroupName="ViewSelect" IsChecked="{Binding Path=ViewType1}"/>
<RadioButton Margin="170,40,0,0" Grid.ColumnSpan="2" Content="2" GroupName="ViewSelect"/>
<TabControl Grid.Row="1" ItemsSource="{Binding TabGroup}">
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Margin" Value="10"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType1}" Value="True">
<Setter Property="ContentTemplate" Value="{DynamicResource ContentTemplate1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType1}" Value="False">
<Setter Property="ContentTemplate" Value="{DynamicResource ContentTemplate2}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<DataTemplate>
<Border x:Name="headerBorder">
<Label Content="{Binding MyHeader}" FontSize="20"/>
</Border>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
Code Behind
namespace ChangeView
{
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window, INotifyPropertyChanged
{
public ObservableCollection<TabData> TabGroup { get; set; } = new ObservableCollection<TabData>();
private bool _viewType1 = true;
public bool ViewType1
{
get { return _viewType1; }
set { _viewType1 = value; RaisePropertyChanged(nameof(ViewType1)); }
}
public Window1()
{
TabGroup.Add(new TabData("♻️", "Recycle"));
TabGroup.Add(new TabData("⚔", "Swords"));
TabGroup.Add(new TabData("⚗", "Chemistry"));
TabGroup.Add(new TabData("🌵", "Cactus"));
TabGroup.Add(new TabData("👺", "Tengu"));
TabGroup.Add(new TabData("🐙", "Octopus"));
DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class TabData : INotifyPropertyChanged
{
private string _myHeader, _myBlurb;
public TabData(string header, string blurb)
{
MyHeader = header;
MyBlurb = blurb;
}
public string MyHeader
{
get { return _myHeader; }
set { _myHeader = value; RaisePropertyChanged(nameof(MyHeader)); }
}
public string MyBlurb
{
get { return _myBlurb; }
set { _myBlurb = value; RaisePropertyChanged(nameof(MyBlurb)); }
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
After changing the radio button state, change the selected tab. You will then see the correct content template.
It looks as if, in a TabControl, changing the content template alone does not cause the content to be rendered. If you render new content by switching the selected tab, the current content template will then be used.
So let's write one ContentTemplate, which creates a ContentControl and switches the ContentControl's ContentTemplate. I've tested, and the ContentControl will re-render its content when its ContentTemplate changes. The bindings get a little bit verbose.
<TabControl ItemsSource="{Binding TabGroup}" Grid.Row="1">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl
x:Name="ContentCtl"
Content="{Binding}"
/>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding DataContext.ViewType1, RelativeSource={RelativeSource AncestorType=TabControl}}"
Value="True">
<Setter
TargetName="ContentCtl"
Property="ContentTemplate"
Value="{DynamicResource ContentTemplate1}"
/>
</DataTrigger>
<DataTrigger
Binding="{Binding DataContext.ViewType1, RelativeSource={RelativeSource AncestorType=TabControl}}"
Value="False"
>
<Setter
TargetName="ContentCtl"
Property="ContentTemplate"
Value="{DynamicResource ContentTemplate2}"
/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<Border x:Name="headerBorder">
<Label Content="{Binding MyHeader}" FontSize="20"/>
</Border>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
You could also do something ugly in your code behind to make the TabControl render itself again on command. Or maybe you can replace the metadata on TabControl.ContentTemplate.

WPF Binding ValidationRules on TextBox

This is my first attempt at using Binding.ValidationRules. They are working as expected with the exception of:
When I enter a TextBox value, for example: "abc" it's fine. When I backspace over letters "c", "b", then "a" the rule fires as it should and warns me the TextBox is empty. The strange behavior is that the string property, to which CompanyName is bound, still retains the "a". Therefore the string is not empty and I can't use the string.Empty comparison in my business logic.
It seems that the ValidationRule is preventing that last edit/character deletion to the string property.
I hope that makes sense, but I have included some images. The green "abc" Label in the image is bound to the same property as the TextBox.
What I want to do is control the visibility of a "Save" Button based on a valid TextBox. Pretty standard I would imagine, so I must be approaching this wrong or I a missing something simple.
<TextBox x:Name="CompanyNameTextBox" Grid.Row="1" TabIndex="1"
Style="{StaticResource TextBoxStyleWithError}"
Validation.ErrorTemplate="{StaticResource validationErrorTemplate}">
<TextBox.Text>
<Binding Path="objCompanyClass.CompanyName" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<HelperClass:CompanyNameValidator Max="50"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Style x:Key="TextBoxStyleWithError" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="DarkRed"/>
<Setter Property="Margin" Value="3 15 3 3"/>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="validationErrorTemplate">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" VerticalAlignment="Center" Margin="0 0 0 3">
<TextBlock Foreground="Red" FontSize="12" Margin="2,0,0,0"
Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
</StackPanel>
<AdornedElementPlaceholder x:Name="ErrorAdorner" ></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
C#
public class CompanyNameValidator : ValidationRule
{
public static bool TextEntryValid = false;
private int _max;
public int Max
{
get { return _max; }
set { _max = value; }
}
public override ValidationResult Validate (object value, System.Globalization.CultureInfo cultureInfo)
{
if (string.IsNullOrEmpty(value.ToString()))
{
TextEntryValid = false;
return new ValidationResult(false, "Company Name cannot be empty.");
}
else
{
if (value.ToString().Length > Max)
{
TextEntryValid = false;
return new ValidationResult(false, $"Cannot be more than {Max} characters long.");
}
}
TextEntryValid = true;
return ValidationResult.ValidResult;
}
}

XAML change button background if databinding value is true

I'm trying to change the background of a button, if a property of the data binding is true.
To give a short overview - I have a ListBox. I also gave this ListBox a style for the items:
<ListBox Margin="0,5,0,0" Background="Transparent"
BorderThickness="0"
ItemsSource="{Binding PaymentsAndCollectionData.PaymentsToCreditors}"
SelectedItem="{Binding PaymentsAndCollectionData.SelectedPaymentToCreditor}">
<ListBox.ItemContainerStyle>
.......
</ListBox.ItemContainerStyle>
</ListBox
Now, I want to put a button in this style. So, I used a ControlTemplate like so (I know this looks weird, and I could propably have done this differently!):
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Button Name="Button" HorizontalContentAlignment="Stretch" Background="Transparent" BorderThickness="0"
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},
Path=DataContext.PaymentsAndCollectionData.SelectPaymentToCreditorCommand}"
CommandParameter="{Binding}">
<DockPanel Name="DP" LastChildFill="False" Background="{TemplateBinding Background}">
<TextBlock DockPanel.Dock="Left" Text="{Binding Name}" Margin="20,0,10,0" Padding="0,5,0,5" Foreground="{TemplateBinding Foreground}"/>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Right">
<TextBlock Text="R" VerticalAlignment="Center" Foreground="{TemplateBinding Foreground}"/>
<TextBlock Text="{Binding Amount, StringFormat=N2}" VerticalAlignment="Center" Margin="0,0,10,0" Foreground="{TemplateBinding Foreground}"/>
</StackPanel>
</DockPanel>
</Button>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="DP" Property="Background" Value="{StaticResource APPLICATION_GREEN_COLOR}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So, the above works properly. The Name and Amount is set from the DataBinding. I am just failing to set the background, if IsSelected is true.
My binding object looks like this:
public class PaymentItem
{
public string Name { get; set; }
public decimal Amount { get; set; }
public bool IsSelected { get; set; }
}
If you change the IsSelected property after the object is created, you need to implement INotifyPropertyChanged interface in this object to make the Binding work.
public class PaymentItem : INotifyPropertyChanged
{
private bool _isSelected;
public string Name { get; set; }
public decimal Amount { get; set; }
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

WPF Menu binding using HierarchicalDataTemplate is not rendering menu items properly

I am building an WPF application trying to stick closely to MVVM principles. I am having a problem getting the menu to render correctly. I've tried a few approaches and am getting stuck. It seems like my binding is correct, but I'm not sure about my styles manipulation.
Here's the code I have the problem with. Like I said, it seems the binding is good, and I can even see the correct values for the Header menu items using Snoop, but all I see rendered is empty containers for menu items.
<DockPanel>
<DockPanel.Resources>
<HierarchicalDataTemplate x:Key="TopMenuHDT" ItemsSource="{Binding Children}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Header" Value="{Binding MenuText}" />
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding MenuIcon}" Height="16px" Width="16px" />
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
</DockPanel.Resources>
<Menu DockPanel.Dock="Top" Height="auto"
ItemsSource="{Binding TopMenuItems}"
ItemTemplate="{StaticResource TopMenuHDT}"/>
In my main ViewModel:
private ObservableCollection<MenuViewModel> _topMenuItems;
public ObservableCollection<MenuViewModel> TopMenuItems
{
get { return _topMenuItems; }
set
{
if (_topMenuItems == value)
return;
_topMenuItems = value; base.RaisePropertyChanged("TopMenuItems");
}
}
...
public void LoadMainMenu()
{
IList<ViewModels.MenuViewModel> fileMenuItems = PopulateFileMenuEntries();
IList<ViewModels.MenuViewModel> editMenuItems = PopulateEditMenuEntries();
_topMenuItems.Add(new ViewModels.MenuViewModel() { MenuText = "_File", Children = new ObservableCollection<ViewModels.MenuViewModel>(fileMenuItems) });
_topMenuItems.Add(new ViewModels.MenuViewModel() { MenuText = "_Edit", Children = new ObservableCollection<ViewModels.MenuViewModel>(editMenuItems) });
private IList<ViewModels.MenuViewModel> PopulateFileMenuEntries()
{
List<ViewModels.MenuViewModel> fileMenuItems = new List<ViewModels.MenuViewModel>();
fileMenuItems.Add(new ViewModels.MenuViewModel() { MenuText = "_Open", MenuIcon = new BitmapImage(new Uri("pack://application:,,,/Resources/OpenDocument16.png")) , Command = _mainWindowViewModel.OpenCommand });
fileMenuItems.Add(new ViewModels.MenuViewModel() { MenuText = "Open _Recent" });
return fileMenuItems;
}
MenuViewModel:
public class MenuViewModel : ObservableObject
{
internal MenuViewModel()
{
IsEnabled = true;
}
private string _menuText;
public string MenuText
{
get { return _menuText; }
set
{
if (_menuText == value)
return;
_menuText = value; base.RaisePropertyChanged("MenuText");
}
}
private ICommand _command;
public ICommand Command
{
get { return _command; }
set
{
if (_command == value)
return;
_command = value; base.RaisePropertyChanged("Command");
}
}
private BitmapImage _menuIcon;
public BitmapImage MenuIcon
{
get { return _menuIcon; }
set
{
if (_menuIcon == value)
return;
_menuIcon = value; base.RaisePropertyChanged("MenuIcon");
}
}
private ObservableCollection<MenuViewModel> _children;
public ObservableCollection<MenuViewModel> Children
{
get { return _children; }
set
{
_children = value; base.RaisePropertyChanged("Children");
}
}
}
Any help in getting this rendered correctly would be greatly appreciated.
EDIT:
Here's the final solution in case someone comes across this similar issue:
<DockPanel>
<Menu DockPanel.Dock="Top" Height="auto" ItemsSource="{Binding TopMenuItems}" >
<Menu.Resources>
<Image x:Key="MenuIconResource" Height="16" Width="16" Source="{Binding MenuIcon}" x:Shared="False" />
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Header" Value="{Binding MenuText}" />
<Setter Property="InputGestureText" Value="{Binding ShortcutText}" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
<Setter Property="Icon" Value="{StaticResource MenuIconResource}" />
<Setter Property="ItemsSource" Value="{Binding Children}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding }" Value="{x:Null}">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate>
<Separator Style="{StaticResource {x:Static MenuItem.SeparatorStyleKey}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.Resources>
</Menu>
Try this instead of your DataTemplate
<DockPanel>
<Menu DockPanel.Dock="Top" Height="auto"
ItemsSource="{Binding TopMenuItems}">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Header" Value="{Binding MenuText}" />
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding MenuIcon}" Height="16px" Width="16px" />
</Setter.Value>
</Setter>
<Setter Property="ItemsSource" Value="{Binding Children}"/>
</Style>
</Menu.Resources>
</Menu>
</DockPanel>

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