Suppose, I have created custom LoginForm as an UserControl, which consists of TextBox and PasswordBox. TextBox has installed custom ErrorTemplate. Naturally, I would like to have this LoginForm as reusable as can, therefore I want to separate validation logic from this LoginForm.
The problem is, that if I bind LoginForm's text property to the "validation-property" of the ViewModel : IDataErrorInfo, that is set as Window's DataContext, the ErrorTemplate is not being applied to LoginForm's TextBox even if I see debug logs from ViewModel's validator.
How can I validate child controls of reusable component via independent ViewModel?
use this error template in application resource:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="0.8">
<AdornedElementPlaceholder Name="adornerPlaceholder"></AdornedElementPlaceholder>
</Border>
</DockPanel>
</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>
After hours of struggling how to solve this, I came up with following solution which satisfies my requirements and so MVVM pattern:
Create in UserControl a DepdendencyProperty of type IDataErrorInfo which will be later implemented by your ViewModel (for my purposes, I used ISignUpValidator:IDataErrorInfo with UsernameValue property).
Lets say this property is registered under the name Validator (as default value I used "do-nothing" implementation of my interface).
Bind UserControl's TextBox.Text property to Validator property:
Lets say TextBox.Text property is exposed to UserControl under the name
Username:
Username="{Binding Path=Validator.UsernameValue, ElementName=UserControlName,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}"
Finally Bind your Viewmodel to your UserControl's Validator property
<Window.Resources>
<local:ViewModel x:Key="ViewModel"/>
<Window.Resources>
<local:LoginForm Validator={StaticResource ViewModel}>
Or alternatively, if your ViewModel is already set as Window's DataContext:
<local:LoginForm Validator="{Binding DataContext, ElementName=WindowName}"}>
Related
I had an interesting request from a client, there were a number challenges involved, the one that I thought would be the easiest, turned out to be the hardest.
The user needs to know that a value has been changed locally, but not yet persisted to the backend store. (A dirty state) We solved this with a data trigger on a style declared within each control on the page. The control background will be filled with yellow when the value is changed, then reset back to the control default when the save button is pressed.
The ModelView implements a custom interface : ILocalValueCache This has an indexer that should return Boolean to indicate if the current value has changed since the last data refresh.
The ModelView also Implements IDataErrorInfo and uses DataAnnotations Attributes for validation, so I can't simply use validation templates.
What I would like to do is simplify the XAML using a single Style or a control template this is hard because each control now has two bindings, one to Value and another to ValueIsLocal:
To be more specific, I would prefer a solution where the developer doesn't need to know the inner mechanics of how it works (what the field names for the xIsLocal flags are) or if the binding source even supports it.
Because the ViewModel implements this interface, (like IDataErrorInfo) I should be able to globally target control styles bound to the states described by the interface.
Here is a section of the XAML with some of the textboxes:
<TextBox Text="{Binding ScaleName}" Margin="5,2,5,2" Grid.Row="1" Grid.Column="2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ScaleNameIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="nbScaleCap" Text="{Binding ScaleCap}" Grid.Row="3" Grid.Column="0" Margin="5,2,5,2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ScaleCapIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="nbTareTol" Text="{Binding TareTol}" Grid.Row="3" Grid.Column="1" Margin="5,2,5,2">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding TareTolIsLocal}" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
As well as the indexer, each property on the View Model has an xxxIsLocal reciprocal property, So the following partial model corresponds to the above example:
string ScaleName { get; set; }
bool ScaleNameIsLocal { get; set; }
string ScaleCap { get; set; }
bool ScaleCapIsLocal { get; set; }
string TareTol { get; set; }
bool TarTolIsLocal { get; set; }
I've played around with using the indexer on the interface to get the IsLocal value but struggled with INotifyPropertyChanged implementation (getting the model to raise the indexer value changed event), that aside the bigger issue was how to make a single style with a binding that is based on the path of the content or text binding on the target control instead of the value of the binding result.
I was inspired by the IDataErrorInfo pattern and using the Validation.ErrorTemplate, it looks simple on the surface and such a simple repetitive pattern like this seems like something that WPF should be able to handle without too many issues.
I am not sure how often I will need this exact template, but it's a pattern that I'm sure I'd like to use again, where there is a potential for each property to have multiple states (not just Error) and to apply a different style using the state as a trigger.
I've edited this post because I haven't quite found what I wanted but thanks to Nikkita I am a step closer :)
By using a custom attached property, we can declare the binding to the flag field directly in the control and can now properly define the style triggers in a global style dictionary.
The ViewModel has not changed, but the XML from above is now simplifed:
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Style.Triggers>
<Trigger Property="att:IsLocal.Value" Value="True">
<Setter Property="Background" Value="{StaticResource LocalValueBackgroundBrush}" />
</Trigger>
</Style.Triggers>
</Style>
<TextBox Text="{Binding ScaleName}" Margin="5,2,5,2" Grid.Row="1" Grid.Column="2" att:IsLocal.Value="{Binding ScaleNameIsLocal}"></TextBox>
<TextBox Text="{Binding ScaleCap}" Grid.Row="3" Grid.Column="0" Margin="5,2,5,2" att:IsLocal.Value="{Binding ScaleCapIsLocal}"></TextBox>
<TextBox Text="{Binding TareTol}" Grid.Row="3" Grid.Column="1" Margin="5,2,5,2" att:IsLocal.Value="{Binding TareTolIsLocal}"></TextBox>
My biggest issue with the current solution is that I would still need to edit a lot of existing XAML if I wanted to apply this (or another) interface pattern to existing apps. Even in the current form there are over 20 fields, so that's 20 opportunities to get the binding wrong, or to accidentally skip one.
I would suggest you the "validator" pattern (look to spec INotifyDataErrorInfo) combined with custom Behaviour.
Validator crates the collection with results according bound property names in item and Bahaviour change the element. Check the MSDN help.
Xaml Example:
<TextBox
Name="a"
Text="{Binding *Variable*, Mode=OneWay}"
Header="Start"
Style ="{StaticResource MitfahrenDefaultTextEdit}" IsReadOnly="true"
Tapped="StartLocation_OnTapped">
<interactivity:Interaction.Behaviors>
<behaviors:RedFormFieldOnErrors
PropertyErrors="{Binding Path=Record.ValidationCollection[*Variable*]}"/>
</interactivity:Interaction.Behaviors>
</TextBox>
i want to write a WPF-Application that shows different pages/scenes. So, I have a ViewModel (MainViewModel) that provides a list of scenes (SceneViewModel). Every scene has a name which can be accessed by a property SceneName.
I create the MainViewModel in Window.DataContext:
<Window.DataContext>
<!-- Declaratively create an instance of the main model -->
<models:MainViewModel />
</Window.DataContext>
I then want a menu-item that lists all scenes. When one of the menu-items is clicked, the scene should change. Therefor i created an Command in the MainViewMode: ChangeSceneCommand.
In XAML I want to create the menu-list in the following way:
<Menu Grid.Row="0">
<Menu.Resources>
<Style x:Key="SetSceneCommandItem" TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding SceneName}"/>
<Setter Property="Command" Value="{Binding SetSceneCommand}"/> <-- Here is the problem
<Setter Property="IsChecked" Value="False" />
</Style>
</Menu.Resources>
<MenuItem Header="Scenes" ItemsSource="{Binding Scenes}" <-- Scenes is a list with scenes^^
ItemContainerStyle="{StaticResource SetSceneCommandItem}" /> <-- Here the style is set
</Menu>
The Item-Header binds well but the "SetSceneCommand" can not be found, because wpf tries to find the "SetSceneCommand"-property in the SceneViewModel. How can i say wpf to access the model in the datacontext out of the style?
PS: You may have noticed that SetSceneCommand will need a scene as parameter to work, but I wanted to implement that later.
You can use RelativeSource. If SetSceneCommand is part of the same context used by Menu then it will look like this:
<Setter
Property="Command"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Menu}}, Path=DataContext.SetSceneCommand}"/>
this tells Binding to go up the visual tree until Menu and take DataContext.SetSceneCommand from there
I have a view which contains a ContentControl which has its ContentTemplate changed dynamically depending on a boolean property within the view model using a data trigger.
<ContentControl>
<!-- MyFirstControl user control by default-->
<local:MyFirstControl/>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Different user control when trigger fired-->
<local:MySecondControl />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
The MySecondControl user control, which displays when the trigger is fired is intended to display a textblock, of which its Text property binds to a property also within the same view model.
I am obviously wrong, but my thinking was that the triggered control would inherit the same data context. Instead it is trying to use the MyFirstControl user control as its data context (I also receive this error: System.Windows.Data Error: 40 : BindingExpression path error:).
I have tried to explicitly state the data context of the triggered control with:
<local:MySecondControl DataContext="{Binding}"/>
However it is still using the default control (MyFirstControl) as its data context.
My question is, how do I force the triggered control to use the same data context as the view file it is within?
I am fairly new to the WPF scene so I hope this makes sense!
Thanks in advance.
There's a difference between Content and ContentTemplate.
Content is your actual content for the control, while ContentTemplate defines how to draw the Content
You are setting the Content property to MyFirstControl. Your trigger is changing the ContentTemplate property, so it is changing the way your Content (MyFirstControl) gets drawn so it is drawn using MySecondControl, however the Content itself is unchanged so the DataContext will still be your MyFirstControl.
You probably want to set the default ContentTemplate to MyFirstControl instead of the actual Content property.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<!-- Set default ContentTemplate -->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:MyFirstControl />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}"
Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Different user control when trigger fired-->
<local:MySecondControl />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Hope this will help:
<local:MySecondControl DataContext="{Binding Path=DataContext,
RelativeSource={RelativeSource TemplatedParent}}"/>
I have got ComboBox which is populated with collection of customTypes. On comboBox change, I would like to load/change content in the particular region so that it loads related data for the selected ComboBox item (it could be in the form of loading userControl or I i dont mind specifying DataTemplate for it).
It is similar to this question WPF Load Control question. But in this question, he is talking about individual DataTemplates in the actual Listbox, while I am talking about populating certain window region on ComboBox change.
I am using MVVM (and not PRISM) .Net 3.5.
U could use ContentControl which is the placeholder for the actual Content that is dynamically decided as per combobox selection.
The follwoing code is just for guidance
<Window ...>
<Window.Resources>
<MyView1 x:Key="MyView1" />
<MyView2 x:Key="MyView2" />
</Window.Resources>
...
<ComboBox x:Name="MyCombo">
<ComboBox.ItemsSource>
<sys:String>"MyView1"</sys:String>
<sys:String>"MyView2"</sys:String>
....
</ComboBox.ItemsSource>
</ComboBox>
...
<!-- This is where your region is loaded -->
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=MyCombo}"
Value="MyView1">
<Setter Property="Content"
Value="{StaticResource MyView1}"
</DataTrigger>
<DataTrigger Binding="{Binding Path=SelectedItem,
ElementName=MyCombo}"
Value="MyView2">
<Setter Property="Content"
Value="{StaticResource MyView2}"
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Window>
The data loading can be part of the MyView1 and MyView2 user control's constructor or your main UI's data context view model.
As far as I understand the question is how to change underlying data being bound to UI and not a DataTemplate only.
You can use EventSetter which will be handled in code behind where you can switch DataContext for a region you've mentioned:
<ComboBox>
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<EventSetter Event="Selector.SelectionChanged"
Handler="YourHandler"/>
</Style>
</ComboBox.Resources>
</ComboBox>
But from MVVM perspectives it could be not perfect solution so you can introduce your own ComboBox class wich is Command aware, see this SO post: WPF command support in ComboBox
In this way you can decouple logic from UI using Command.
I am creating a custom control.
I want the template for this control to use different controls for the root control based on the value of a dependency property called CanExpand. CanExpand is defined in the custom control class.
If CanExpand is true, I want to display using an Expander:
<ControlTemplate ...>
<Expander ...>
<!--...-->
<ContentPresenter/>
</Expander>
</ControlTemplate>
If CanExpand is false, I want to display using a HeaderedContentControl instead:
<ControlTemplate ...>
<HeaderedContentControl ...>
<!--...-->
<ContentPresenter/>
</HeaderedContentControl>
</ControlTemplate>
I thought of using a DataTemplateSelector, but this is a ControlTemplate not a DataTemplate and there is no selector property for the Template of a control.
I can't set the different controls to visible/hidden with a Trigger because the child content can only live under one control. Also, I don't think you can change the Content property using a Trigger.
Any suggestions?
Thanks.
Inside your Style, set the ControlTemplate property for the default state and then have a Trigger which sets the ControlTemplate property to a different template. For example:
<Style ...>
<Setter Property="ControlTemplate">
<ControlTemplate ...>
</ControlTemplate>
</Setter>
<Style.Triggers>
<Trigger Property="YourProperty" Value="WhateverValue">
<Setter Property="ControlTemplate">
<ControlTemplate ...>
</ControlTemplate>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Keep in mind you can have a trigger on the same property for multiple values, each value getting a completely different template.