Dynamically use different UserControl based on datacontext - c#

I have a chunk of xaml that duplicates the same pattern six times and would like to reduce that footprint by eliminating the duplication. I'm running into one snag that I need help with.
Background: I have a class that instantiates another class six times (phases of a project as you will).
public ECN(string ecnNumber) {
_ECNNumber = ecnNumber;
//instantiate each phase to be populated or not
_PurchaseParts = new ECNPhase();
_PieceParts = new ECNPhase();
_Weldments = new ECNPhase();
_BOMCfg = new ECNPhase();
_Cleanup = new ECNPhase();
_PRAF = new ECNPhase();
}
Inside each phase is a collection of Changes (another class) referenced in the ECNPhase Class. Each phase has data that is unique to each phase that is shown in a unique view, this is where my snag is which I will show later.
Example of the duplicate xaml Code with the main difference being the different view inside each expander:
<StackPanel Margin="0">
<!--Section for Purchase parts-->
<StackPanel Orientation="Horizontal" >
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="Purchase Parts" Margin="0,0,10,0" Width="110">
<view:PurchasePartsView/>
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.Status}"/>
</StackPanel>
</StackPanel>
<!--Section for Piece Parts-->
<StackPanel Orientation="Horizontal">
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="Piece Parts" Margin="0,0,10,0" Width="110">
<view:PiecePartsView/>
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.Status}"/>
</StackPanel>
</StackPanel>
<!--duplicated four more times-->
</StackPanel>
What I'd like to do is:
<StackPanel>
<view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
<view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
<!--four more phases-->
</StackPanel>
Where the PhaseView will be the template that handles the duplication and this is where I'm hitting a snag. Each phase needs a unique view (userControl) selected based off of the datacontext of the PhaseView.
<StackPanel Orientation="Horizontal" >
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="DisplayName" Margin="0,0,10,0" Width="110">
<!--add somthing here to select the correct view based on the datacontext-->
<!--<local:PurchasePartsView/> This user control adds a datagrid that is unique to each phase-->
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=Status}"/>
</StackPanel>
</StackPanel>
I was thinking of using a datatrigger somehow lik shown below, but I haven't had any luck figuring it out. I know there's got to be a way to do this, and it's probably something simple and dumb. Any help would be much appreciated.
<DataTrigger Binding="{Binding Path=DisplayName}" Value="Purchase Parts">
<Setter Property="DataContext" Value="{Binding }"/> <!--Don't know how to bind the DataContext-->
</DataTrigger>
Thanks,

Ok, thanks to Bradley I looked into the DataTemplateSelector and this is what I came up with.
In my UserControl resources I set up several DataTemplates and a reference to the TemplateSelector class that overides the DataTemplateSelector class.
XAML Resources:
<UserControl.Resources>
<local:TemplateSelector x:Key="myTemplateSelector"/>
<DataTemplate x:Key="PurchasePartsTemplate">
<view:PurchasePartsView/>
</DataTemplate>
<DataTemplate x:Key="PiecePartsTemplate">
<view:PiecePartsView/>
</DataTemplate>
<!--Four more templates-->
</UserControl.Resources>
Code Behind for DataTemplateSelector override. Note: I couldn't figure out a way to bind to the ECNPhase class so I bound to the DisplayName property in my class to pull out the correct instance being represented.
class TemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
FrameworkElement element = container as FrameworkElement;
if(element != null && item != null && item is string) {
string phase = (string)item;
if(phase == "Purchase Parts") {
return element.FindResource("PurchasePartsTemplate") as DataTemplate;
}else if(phase == "Piece Parts") {
return element.FindResource("PiecePartsTemplate") as DataTemplate;
}
}
return null;
}
}
I'm calling this class in my UserContol like this:
<Expander Header="{Binding Path=DisplayName}" Margin="0,0,10,0" Content="{Binding Path=DisplayName}"
ContentTemplateSelector="{StaticResource myTemplateSelector}"/>
There isn't an items control associated with the expander so I used the content control. I pass the DisplayName into the control property and the contentTemplateSelector uses the myTemplateSelector resource which goes into the codebehind and selects the appropriate datatemplate to use based on the DisplayName.
Now I can call my reusable template like so:
<StackPanel Margin="0">
<view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
<view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
</StackPanel>
#Bradley, thank you for pointing me in the right direction.

Related

C# WPF Changing ContentControl Content based on DataTrigger not working

I am trying, with no luck, to change the content of a content control based upon a DataTrigger, that checks a binding to see if it matches a type, and if it does change the contentcontrols displayed content.
In short, I want this ContentControl's Content to change if the Source is a certain type. I have attempted to do this using Styles / DataTriggers
<ContentControl>
<ContentControl.Content>
<TextBlock Name="CollectionControlTextBox1"
Tag="."
PreviewMouseDown="CollectionControlDefaultDockPanel_MouseButtonDown"
PreviewMouseUp="CollectionControlTextBox1_PreviewMouseUp"
MouseMove="CollectionControlDefaultDockPanel_MouseMove"
Drop="CollectionControlDefaultDockPanel_Drop"
Foreground="{Binding Meta.ColorBrush, Mode=OneWay, TargetNullValue={StaticResource TextBrush}, FallbackValue={StaticResource TextBrush}}"
AllowDrop="True"
MinHeight="20"
Padding="5 2 0 0"
KeyboardNavigation.IsTabStop="False"
Focusable="True"
TextTrimming="CharacterEllipsis"
ContextMenu="{Binding ParentControl.MemberContextMenu, Mode=OneWay}"
Text="{Binding DisplayName, Mode=OneWay}">
<TextBlock.CommandBindings>
<CommandBinding Command="{x:Static iugo:EditorCommands.Metadata}" Executed="Metadata" />
</TextBlock.CommandBindings>
</TextBlock>
</ContentControl.Content>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Source, Converter={StaticResource IsAssociation}}" Value="True">
<Setter Property="Content">
<Setter.Value>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding IdEntry.Id}" ToolTip="{Binding IdEntry.Entry.Name}" FontSize="10" Foreground="{StaticResource DisabledTextBrush}" HorizontalAlignment="Center" Margin="0, 3, 0, 0"/>
<TextBlock Text="{Binding IdEntry.Entry.Name}" ToolTip="{Binding IdEntry.Entry.Name}" FontSize="13" Margin="5, 0, 0, 0"/>
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I have tested that the converter: IsAssociation is correctly being hit, and is correctly the right value, but the Content does not change to a stackpanel as defined in the DataTrigger. I have confirmed this by using Visual Studios' Visual Tree, and it still links to the old content.
A directly set Content like
<ContentControl>
<Content>
...
</Content>
</ContentControl>
has higher value precedence than a value set by a Setter in a Style Trigger. Your DataTrigger is hence ignored.
Instead of directly setting the Content, move the initial value to another Setter:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
<TextBlock ...>
...
</TextBlock>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger ...>
<Setter Property="Content">
<Setter.Value>
<StackPanel Orientation="Horizontal">
...
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Custom ListBox in WPF

I am trying to create a custom ListBox control in WPF for a chat Messenger. I am using an ellipse to show the online/offline user. The ellipse is to be displayed on left and some text in center of the ListBoxItem.
I want to set the ellipse fill propert to red/green based on some variable.
This is what I have done :
<ListBox Name="myList" HorizontalAlignment="Left" Height="232" Margin="117,74,0,0" VerticalAlignment="Top" Width="207">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Ellipse Name="ellipse" Fill="Red" DockPanel.Dock="Left">
<Ellipse.Triggers>
<Trigger Property="{Binding Online}" Value="True">
<Setter TargetName="ellipse" Property="Ellipse.Fill" Value="Green"/>
</Trigger>
</Ellipse.Triggers>
</Ellipse>
<TextBlock Text="{Binding text}"></TextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and in the code :
myList.Items.Add(new { text="Hello",Online="True" });
I am getting an error as
Cannot find the static member 'FillProperty' on the type 'ContentPresenter'.
What am I doing wrong here?
Obviously this is wrong: Property="{Binding Online}"
Also you should use a Style for triggers, no need to set TargetName, and you need to take precedence into consideration, and use a Setter for the default value.
you are actually misleading WPF with some of these concerns.
Binding A property on trigger will not work. you have to use DataTrigger insteed of Triggers.
Implementing Trigger on the Fly for any control. most of the times not work. So go with Styles.
While you are creating Ellipse in template make sure you have created enough size for it. So that can be visible to users.
try this.
<Window.Resources>
<Style x:Key="elstyle" TargetType="Ellipse">
<Setter Property="Height" Value="5"/>
<Setter Property="Width" Value="5"/>
<Setter Property="Fill" Value="Red"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Online}" Value="true">
<Setter Property="Fill" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListBox x:Name="myList" HorizontalAlignment="Left" Height="232" Margin="117,74,0,0" VerticalAlignment="Top" Width="207">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Ellipse Name="ellipse" Margin="5" DockPanel.Dock="Left" Style="{DynamicResource elstyle}">
</Ellipse>
<TextBlock Text="{Binding Name}"></TextBlock>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
code behind .
public MainWindow()
{
Random r = new Random();
InitializeComponent();
for (int i = 0; i < 10; i++)
{
myList.Items.Add(new { Name = "Name" + i.ToString(), Online = Convert.ToBoolean(r.Next(-1, 1)) });
}
}
You need to set the initial colour via a Setter and not in XAML. For more information and see this question: Change the color of ellipse when mouse over
there are a few issues in your XAML
set the size of your Ellipse
you need to use Style instead of Ellipse.Triggers
set your Fill color in your Style if you wane be able to change it in XAML by some condition
here an working example for your problem
<DataTemplate>
<!--<DockPanel> juste because i like StackPanel-->
<StackPanel Orientation="Horizontal">
<!--<Ellipse Name="ellipse" Fill="Red" DockPanel.Dock="Left">-->
<Ellipse Name="ellipse" Width="15" Height="15">
<!--<Ellipse.Triggers>-->
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="Red"/>
<Style.Triggers>
<!--<Trigger Property="{Binding Online}" Value="True">-->
<DataTrigger Binding="{Binding Online}" Value="True">
<Setter Property="Fill" Value="LimeGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock Text="{Binding text}"/>
</StackPanel>
</DataTemplate>

After Treeview Loaded Event in WPF

I have a TreeView in my WPF application. In runtime I am binding data to the Treeview. Each node in a treeview is associated with path. I should change the color of the TreeView element If the file in the path associated with the element has an error. Say I need to change it to RED.
So Since I am binding it on fly after the treeview is entirely loaded I should traverse the tree again and I should check the path contains any error for every element in the TreeView.
How can I navigate the entire Tree element one by one from parent to root child and perform the error checking operation for each node.
Tree in XAML:
<TreeView Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" Name="treeView1"
VerticalAlignment="Stretch"
SelectedItemChanged="treeView1_SelectedItemChanged" HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Top" BorderThickness="0,0,0,1" BorderBrush="LightGray">
<TreeViewItem Header="Head Tree" ItemsSource="{Binding MainComps}">
<TreeViewItem.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Foreground" Value="RED" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeViewItem.ItemContainerStyle>
<TreeViewItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type TextBlock}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Head Tree" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MainCompViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Maincompname}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:FeatureViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FeatureName}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:CompViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Component}" />
</StackPanel>
</DataTemplate>
</TreeViewItem.Resources>
</TreeViewItem>
</TreeView>
In code behind:
I have treeview selection changed event.
How can I have AfterTreeViewLoaded event. Something like this.
Void TreeviewLoaded()
{
//Identify error. Change the color
}
Thanks in advance..
When you create tree items you should have some list of root items and bind the list to your TreeView.ItemsSource.
Then at any time you can check the paths and set boolean HasError (the property should exist in your ItemViewModel). Tree item's backround can be changed by style, like this:
<Setter Property="Background" Value="{Binding HasError, Converter={StaticResource HasErrorToBackroundConverter}" />
UPD:
Please see the following article http://blog.clauskonrad.net/2011/04/how-to-make-hierarchical-treeview.html.
Your ItemViewModel is just like the Folder from the article.
The list of root items is m_folders. Like the Folder contains a FullPath, your ItemViewModel will contain Path alongside HasError property.
When you need to check the path, you recursively pass m_folders, read the path from it, check it and set HasError.

ListBox , DataTemplate and Triggers

i got a DataTemplate for a listboxitem and i want to create a triger , so when a user click an item the background will change and also the label
my code:
<Window.Resources>
<Style x:Key="RoundedItem" TargetType="ListBoxItem">
<EventSetter Event="MouseDoubleClick" Handler="listViewItem_MouseDoubleClick" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="ItemBorder" CornerRadius="10" BorderBrush="Black" BorderThickness="1" Margin="1" Background="Transparent">
<Label Name="ItemLabel" Foreground="Red" >
<ContentPresenter />
</Label>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="ItemBorder" Property="Background" Value="DeepSkyBlue" />
<Setter TargetName="ItemLabel" Property="Foreground" Value="Orange" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="TitleTemplate" DataType="models:Title" >
<StackPanel>
<Image Source="{Binding ThumbFilePath}" Width="50" HorizontalAlignment="Center"/>
<Label Content="{Binding Name}" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Description}" HorizontalAlignment="Center" TextWrapping="Wrap" Padding="5,5,5,5"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
What happend is that the TextBlock change his color and not the label..
anyone know why ?
Thanks.
The TextBlock inherits the Foreground definition from its parents in the visual tree. The Label, on the other hand, defines the Foreground in its default style.
Your approach is "non-WPF-like" - you shouldn't wrap the ContentPresenter in a Label control.
The right approach depends on whether you want all text in the item to change its Foreground, or just the label?
[In both cases, there's no apparent benefit to using a Label in the data template - so I'll assume that the label is changed to TextBlock.]
If the answer to the above question is that all text should be changed: in the ControlTemplate of the ListBoxItem, in the trigger for IsSelected, from the seccond setter remove TargetName="ItemLabel" so the final setter is:
<Setter Property="Foreground" Value="Orange" />
This will change the foreground of the item that will affect the foreground of both TextBlocks in the data template.
If you want to affect just one of the TextBlocks:
1. remove the setter for the foreground from the control template
2. add a trigger to your data template:
<DataTemplate>
<StackPanel>
<Image .../>
<TextBlock x:Name="Text01" ..../>
<TextBlock x:Name="Text02" ..../>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
<Setter TargetName="Text01" Property="Foreground" Value="Orange"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Side note: if you have to use Label control in your data template, bind its Foreground property to the Foreground of the list box item, like so:
<Label Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"....../>
If this doesn't help, it means that your list box item inherits its foreground, so use:
<Label Foreground="{Binding TextElement.Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"....../>
I want to tack on to this that I was experiencing a similar problem where I'd added a ListBox.ItemTemplate to my ListBox, and the styling then did not apply to the text anymore.
What I was doing was trying to display a list of languages (CultureInfo) for the user to select from, however I wanted the native names to display, not the English name. For some reason, not all languages have their native names capitalized in their CultureInfo, and NativeName is the only instance of their name, so I needed to apply a function to the CultureInfo.NativeName to capitalize the names myself. To accomplish this, I added the ItemTemplate with a Data Template inside, on which I applied a converter.
<ListBox IsSynchronizedWithCurrentItem="True" VerticalAlignment="Center" MinHeight="200" x:Name="cbLanguages"
ItemsSource="{Binding Path=SupportedCultures, Mode=OneWay, Source={StaticResource CultureResourcesDS}}"
FontSize="24" HorizontalAlignment="Stretch" Width="300" Margin="10"
Style="{DynamicResource ListBoxTemplate}" ItemContainerStyle="{DynamicResource ListBoxItemStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Converter={StaticResource NativeNameConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
After a while of searching, I came across XAMeLi's answer here, and changed the Label I'd put in the DataTemplate to a TextBox, and the ListBoxItemStyle I'd created worked again.
Basically, Labels and TextBoxes have different traits that can be exploited, or can cause annoying issues in this case. Here's a good explanation with some examples of the differences: http://joshsmithonwpf.wordpress.com/2007/07/04/differences-between-label-and-textblock/

How do you bind a viewmodel which implements IDataErrorInfo to a UserControl and propagate the validation errors?

I've written a UserControl that exposes a few dependency properties to modify the layout of the control (i.e. think of it as a generic type-safe editor control - so my data types are dates (edited through a date picker), enumerations (edited through a combobox) and a numeric (edited through a textbox). I've also exposed the value of the 3 editor controls as a dependency property so that it can be databound.
The controls within the usercontrol all bind to the exposed dependency properties to get their values (with appropriate converters where necessary).
This control forms a tiny piece of a larger UI which binds to a viewmodel - the value to be edited, the datatype flag for the custom control and a list of possible valid values are all bound to an object in the viewmodel.
My problem is this: I've implemented IDataErrorInfo on the viewmodel and set the controls' bindings within the custom usercontrol to have ValidatesOnDataErrors=True, NotifyOnValidationError=True but the validation is not displaying.
This is the XAML for my usercontrol (there is no other code-behind logic beyond the dependency property declarations):
<UserControl.Resources>
<!-- Converters -->
<local:ValidationBooleanToImageConverter x:Key="ValidationBooleanToImageConverter"/>
<local:ValidationErrorToStringConverter x:Key="ValidationErrorToStringConverter"/>
<local:DataTypeToVisibilityConverter DataType="Date" x:Key="DateDataTypeToVisibilityConverter"/>
<local:DataTypeToVisibilityConverter DataType="Numeric" x:Key="NumericDataTypeToVisibilityConverter"/>
<local:DataTypeToVisibilityConverter DataType="Enumeration" x:Key="EnumerationDataTypeToVisibilityConverter"/>
<local:DateToStringConverter x:Key="DateToStringConverter"/>
<!-- Styles -->
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors).CurrentItem, Converter={StaticResource ResourceKey=ValidationErrorToStringConverter}}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<toolkit:DatePicker Height="25" Margin="0,0,5,0" Visibility="{Binding Path=DataType, ElementName=UserControl, Converter={StaticResource ResourceKey=DateDataTypeToVisibilityConverter}}" SelectedDate="{Binding Path=Value, ElementName=UserControl, Converter={StaticResource ResourceKey=DateToStringConverter}}"/>
<ComboBox Height="25" Margin="0,0,5,0" Visibility="{Binding Path=DataType, ElementName=UserControl, Converter={StaticResource ResourceKey=EnumerationDataTypeToVisibilityConverter}}" SelectedItem="{Binding Path=Value, ElementName=UserControl, Mode=TwoWay}" ItemsSource="{Binding Path=Items, ElementName=UserControl}" />
<TextBox x:Name="_txtValue" Height="25" Margin="0,0,5,0" Visibility="{Binding Path=DataType, ElementName=UserControl, Converter={StaticResource ResourceKey=NumericDataTypeToVisibilityConverter}}" Text="{Binding Path=Value, ElementName=UserControl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"/>
<Image Grid.Column="2" Grid.Row="0" Source="{Binding ElementName=_txtValue, Path=(Validation.HasError), Converter={StaticResource ResourceKey=ValidationBooleanToImageConverter} }" ToolTip="{Binding ElementName=_txtValue, Path=ToolTip}" Width="16" Height="16"/>
</Grid>
...and the usercontrol reference from within the larger View is...
<control:ValueEditorControl DataType="{Binding Path=ContextualSelectedTagDataType}" Items="{Binding Path=ContextualSelectedTagItems}" Value="{Binding Path=ContextualSelectedTagDataObjectValue, Mode=TwoWay}" Height="25" VerticalAlignment="Top"/>
Can anyone point me in the right direction?
Does your VM implement INotifyPropertyChanged? Even if you implement IDataErrorInfo if WPF isn't notified of changes to the VM then it won't bind to those changes.
That being said I would change your ToolTip setter to this:
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
If you want the entire Style I would recommend this:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<AdornedElementPlaceholder Name="controlWithError" />
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background" Value="LightPink"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>

Categories

Resources