How to implement (bubbling) validation events for custom WPF control - c#

I have developed a custom WPF control:
public partial class PercentTextbox : UserControl, IDataErrorInfo, INotifyDataErrorInfo
And I put that control inside a UserControl along with some other controls:
<UserControl x:Class="UserControlContainingPercentTextboxAndStuff" DataContext="Something" ...>
<Grid>
<mycontrols:PercentTextbox Value="{Binding MyPercentageValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" ... />
<TextBox ... />
<mycontrols:PercentTextbox ... />
<TextBox ... />
<TextBox ... />
</Grid>
</UserControl>
And finally, I use another wrapping UserControl to show the above UserControl as dialog:
<UserControl ...>
<Grid>
<local:UserControlContainingPercentTextboxAndStuff ... />
<Button x:Name="SaveButton" Content="Save" ... />
<Button x:Name="CancelButton" Content="Cancel" ... />
</Grid>
</UserControl>
In the code behind of the latter, I want to subscribe to all validation errors, and disable the save button if there are errors.
Validation.AddErrorHandler(this, (sender, e) =>
{
SaveButton.IsEnabled = false;
Debug.WriteLine(e.Error);
});
I was thinking, that if I'd implement IDataErrorInfo or INotifyDataErrorInfo, WPF would somehow magically handle stuff for me, and create a ValidationError event (which would bubble up to the UserControl. But clearly, I am missing something essential here.
My question is: What do I have to implement in my custom control PercentTextbox in order to use it in arbitrary places and still get some kind of bubbling-up validation information which I can use in a container UserControl (e.g. to disable the SaveButton).

The IDataErrorInfo and INotifyDataErrorInfo are supposed to be implemented on the model side, not on the UI side. Then you can set the ValidatesOnDataErrors = True or ValidatesOnNotifyDataErrors = True options on your Bindings, so that the binding validation system jumps in. There are some good tutorials on the web about that. It's not the UI telling that something is invalid, but the data this UI represents.
The data validation concept is tightly coupled with the data bindings. If you want your user control to perform its own "UI" validation, use the coercing and validation callbacks of the dependency properties. However, this has nothing to do with the data validation of the binding system. The validation callback will cause the property system to throw an exception that you can handle as you wish (e.g. you can use the ExceptionValidationRule for your bindings).
Take a look on the Validation.Error attached event documentation (which you're actually trying to observe by calling Validation.AddErrorHandler). It states:
Occurs when the bound element runs into a validation error, but only
for bindings with the NotifyOnValidationError value set to true.
So you have two options now:
implement the validation on the model side and set up your bindings accordingly (you have to do this for each binding to your custom control's properties)
use the dependency property validation callbacks

Related

WPF share common property between controls

i've two content controls how can i share a common property between them,
for example if i select some value from combobox in the first content control,
how can the second control know it
<telerikNavigation:RadTabItem Header="1">
<StackPanel>
<ContentControl Content="{Binding EGRPExtractViewModel.View}" />
</StackPanel>
</telerikNavigation:RadTabItem>
<telerikNavigation:RadTabItem Header="2">
<ContentControl Content="{Binding EGRPRightObjectViewModel.View}" />
</telerikNavigation:RadTabItem>
Thanks
You need to use two way binding then respond to the property changing in your ViewModel.
<ContentControl Content="{Binding EGRPRightObjectViewModel.View,Mode=TwoWay}" />
See the MSDN docs for how to respond to changed properties: http://msdn.microsoft.com/en-us/library/ms743695(v=vs.110).aspx
You do not bind view properties. You can bind control properties in same view, so one possibility for you will be to create a control which exposes bindable properties specifically for this reason.
When using mvvm normally view-model should provide all needed properties to the view. If it's a property from another view-model, then it is still have to be provided by view-model of this view (search for questions about how to pass data between view-models to example, here is one).

Binding to code behind from custom control

I have a GridView that has several buttons. One of them is defined by the following template:
<DataTemplate x:Name="SubjectItemTemplate">
<Canvas Width="340" Height="170" VerticalAlignment="Top">
<Controls:ThreeImageButton HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,0,0,0"
NormalStateImageSource="{Binding NormalImage}"
HoverStateImageSource="{Binding HoverImage}"
PressedStateImageSource="{Binding PressedImage}" Command="{Binding Path=NavigateToUnitsPage}"
CommandParameter="{Binding}" Canvas.Left="0" Canvas.Top="0">
</Controls:ThreeImageButton>
</Canvas>
</DataTemplate>
Now I have a custom control as you can see, called ThreeImageButton. The button works fine when I use it on its own. But when I have it in the DataTemplate it won't bind properties to the code behind.
Right now, I have
x:Name="MyThreeImageButton"
in the custom button definition. And I connect to the code-behind like this:
<TextBlock Text="{Binding ElementName=MyThreeImageButton, Path=NormalStateImageSource}"/>
(This is just a test to display the text, in the actual code I would assign an image source to another property that is referred to by an element).
Right now, nothing is displayed in the TextBlock. What is the correct binding syntax I'm supposed to use to reach my properties?
Thanks!
Edit: I am setting the variable in the InitializeComponent function and I am using SetValue on the DependencyProperty.
Edit: Let me add the following information to be more clear
Scenario I:
In DataTemplate for GridView:
<UserControl CustomParameter="Literal Text">
In UserControl:
<TextBlock Text="{Binding CustomParameter}">
in UserControl .cs: this.DataContext = this
works!
Scenario II:
In DataTemplate for GridView:
<UserControl CustomParameter="{Binding ValueFromDataItem">
In UserControl:
<TextBlock Text="{Binding CustomParameter}">
in UserControl .cs: this.DataContext = this
nope!
I see,
So setting up a two-way binding to a custom property in a user control can be tricky because a user control cannot bind to a CLR property. Not only that but setting the data context on a user control has an unexpected effect on the binding inside it.
You can solve these problems with a little slight of code. Basically back your CLR properties with dependency properties and set the data context on a child element instead of the root user control.
Take a look at this sample. Let's pretend you have the following MainPage. That MainPage will eventually use our custom user control. So let's set the stage.
Here's the code-behind:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = new /* your view model */
{
Title = Guid.NewGuid().ToString(),
};
}
}
In the code above I am simulating a complex view model with a simple anonymous class. It would be silly for you to implement your own like this, but at the same time it is silly for me to build a simple sample with the complete scaffolding. I bring this up only so it does not confuse you - as it could look like I am suggesting this approach in prod.
Here's the XAML:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<local:MyUserControl Text="{Binding Title}" />
</Grid>
In the XAML above, there is absolutely nothing special. I already have reference to the user control in the local namespace and I simply declare it here.
Okay, now that we have a consumer of the control, it's worth pointing out that in testing developers can mistakenly think that their binding is working because they test with literal values. Literal values bind fine. It's binding from the underlying view model that hick-ups.
Let's say another thing, some developers tend to avoid dependency properties because the require a little more typing. People remember that [kbd]propdp[/kbd] is a handy Visual Studio snippet that stubs out a dependency property for you.
Take a look at this user control. It has two controls, a TextBox and a TextBlock which are there to demonstrate the OneWay and TwoWay functionality of this binding approach. We also implement INotifyPropertyChanged on the user control. For the most part, adding a view model in the case of a user control is overkill because the user control already acts like a view model. It's up to the developer, but it seems dumb to me.
Here's the code behind:
public sealed partial class MyUserControl : UserControl, INotifyPropertyChanged
{
public MyUserControl()
{
this.InitializeComponent();
}
// text property
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValueDp(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyUserControl), null);
// bindable
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value,
[System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In the ode above, I have create a "Text" property and backed it with a dependency property. For a matter of reuse I have also implemented SetValueDp() which could be used again and again if I had more than a single property. Even though this demo has but one, I wanted to include this because the repetitive logic should certainly be abstracted out like this.
Here's the XAML:
<Grid Background="Black" DataContext="{Binding ElementName=userControl}">
<StackPanel>
<TextBox Text="{Binding Text, Mode=TwoWay}"
MinHeight="100" Padding="15" FontWeight="Light" FontSize="50" />
<TextBlock Text="{Binding Text}"
MinHeight="100" Padding="15" FontWeight="Light" FontSize="50" />
</StackPanel>
</Grid>
In the XAML above, I do nothing special insofar as binding. The syntax simply binds to the Text property using the Mode appropriate to the control. Just like you would do normally. However, what's worth noticing is that the DataContext is NOT set on the user control. Instead, it is set on the Grid. As a point of fact, any control in the tree other than the user control could be used like this. Just don't set the data context of the user control.
That is it by the way.
I have tested it to make sure it works. Demonstrating both one and two way binding is pretty handy here. I might even turn this into a blog in case other developers want to find it and don't discover this question. Thanks for your question!
Best of luck!
As the comments alluded to, your DataTemplate is placing the datacontext of the items to whatever object you are adding to your list. This is not the same as the surrounding user control's data context. If you want to reference that datacontext's commands, do the following in the DataTemplate's bindings:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.NormalImage}
What this is saying is to go out and find the user control ancestor and use its datacontext and then look for the NormalImage property. If you run into problems, check your output window for binding errors. It is very helpful in finding binding problems.

Calling a command in my ViewModel from a binding to an event in my XAML?

I am currently writing a Windows 8 application. I am trying to call a method in my ViewModel. I want this method to be called when an item is double clicked. I have defined the following DataTemplate in my XAML to do this:
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Horizontal">
<Image Width="185" Height="185" Stretch="Fill" Source="{Binding Path=Image}" DoubleTapped="{Binding Path=MethodIWishToBindTo}" IsDoubleTapEnabled="True" />
</StackPanel>
</DataTemplate>
The problem, of course, is the error message for my binding to MethodIWishToBindTo:
Invalid value for 'DoubleTapped'. Event values must be text
What is the best way for me to get around this ? I could call the method in the code-behind, however the method uses a property in my ViewModel, "SelectedItemInList", which I don't believe can be accessed from the code behind.
Can anyone offer me some advice for this problem ?
Thanks a lot.
You could use Interactivity and a custom behavior to trigger the event. Here's a post that topically covers an example:
MVVM-Light EventToCommand Behavior for CheckBox Checked/Unchecked in Silverlight
MVVM-Light definitely makes this easier, but it's possible without as well.
Here's an example of without: http://blog.roboblob.com/2010/01/26/binding-ui-events-from-view-to-commands-in-viewmodel-in-silverlight-4/

How to set focus to textbox using MVVM?

How to focus a textbox from ViewModel wpf?
<TextBox Name="PropertySearch"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay, Path=PropertySearch,
ValidatesOnDataErrors=True}"
Width="110"
Height="25"
Margin="10" />
You can do this by adding a property to your ViewModel (or use an existing property) that indicates when the SetFocus should happen but the View should be responsible for actually setting the focus since that is purely View related.
You can do this with a DataTrigger.
View:
<Grid Name="LayoutRoot" DataContext="{StaticResource MyViewModelInstance}">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding UserShouldEditValueNow}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=PropertySearch}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBox Name="PropertySearch" Text="{Binding UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Path=PropertySearch, ValidatesOnDataErrors=True}" Width="110" Height="25" Margin="10" />
</Grid>
ViewModel:
// When you think the view should set focus on a control
this.UserShouldEditValueNow = true;
The example above is simplified by just using a boolean ViewModel property "UserShouldEditValueNow". You can add a property like this to your ViewModel or use some other exising property that indicates this state.
Note: So why is it done this way in MVVM? One reason is, suppose the View author decided to replace the TextBox with a ComboBox, or even better, suppose your property was an integer value that had both a TextBox to view/edit the number and a Slider as another way to edit the same value, both controls bound to the same property... how would the ViewModel know which control to set focus on? (when it shouldn't even know what control, or controls, are bound to it in the first place) This way the View can select which control to focus by changing the ElementName binding target in the DataTrigger Setter.
Happy coding!
The question you should be asking yourself is "why does my ViewModel need to know which control has the focus?"
I'd argue for focus being a view-only property; it's an interaction property, and has nothing to do with the conceptual state. This is akin to the background color of a control: why would you represent it in the VM? If you need to manage the focus in a custom way, it's probably better to use a view-level object to do the job.
In your parent control, add the following property:
FocusManager.FocusedElement="{Binding ElementName=PropertySearch}"
While purists may argue for leaving this out of the VM, there are cases where it may make sense to do so from the VM.
My approach has been to make the view implement an interface, pass that interface to the ViewModel, and then let the VM call methods on the interface.
Example:
public interface IFocusContainer
{
void SetFocus(string target);
}
A couple things to keep in mind:
A VM might serve more than one instance of a view, so your VM might want to have a collection of references to IFocusContainer instances, not just one.
Code the VM defensively. You don't know whether there are 0, 1 or 20 views listening.
The "target" parameter of SetFocus() should probably be "loosely" coupled to the VM. You don't want the VM caring about the exact control names in the UI. Rather, the VM should indicate a name that is defined solely for focus management. In my case, I created some attached properties that would allow me to "tag" controls with "focus names".
To implement the interface, you can:
Implement it in the code-behind
Create some behaviors that know how to attach to the ViewModel that is present in the DataContext.
There's nothing wrong with implementing it on the Code Behind, but the behavior approach does allow a XAML only hookup if that's important to you.
In the implementation of the interface, you can use the visual tree to locate the control, or you could just code up a switch statement for a known set of focusable items.

Is there an MVVM-friendly way to swap views without value converters firing unnecessarily?

I thought what I was doing was right out of the Josh Smith MVVM handbook, but I seem to be having a lot of problems with value converters firing when no data in the view-model has changed.
So, I have a ContentControl defined in XAML like this:
<ContentControl Grid.Row="0" Content="{Binding CurrentViewModel}" />
The Window containing this ContentControl references a resource dictionary that looks something like this:
<ResourceDictionary ...>
<DataTemplate DataType="{x:Type lib_vm:SetupPanelViewModel}">
<lib_v:SetupPanel />
</DataTemplate>
<DataTemplate DataType="{x:Type lib_vm:InstructionsPanelViewModel}">
<lib_v:InstructionsPanel />
</DataTemplate>
</ResourceDictionary>
So, basically, the two data templates specify which view to show with which view-model.
This switches the views as expected whenever the CurrentViewModel property on my window's view-model changes, but it also seems to cause value converters on the views to fire even when no data has changed. It's a particular problem with IMultiValueConverter classes, because the values in the value array get set to DependencyProperty.UnsetValue, which causes exceptions unless I specifically check for that. But I'm getting other weird side effects too.
This has me wondering if I shouldn't just do everything manually, like this:
Instantiate each view.
Set the DataContext of each view to the appropriate view-model.
Give the ContentControl a name and make it public.
Handle the PropertyChanged event for the window.
In the event handler, manually set the Content property of the ContentControl to the appropriate view, based the CurrentViewModel (using if statements).
This seems to work, but it also seems very inelegant. I'm hoping there's a better way.
Could you please advise me the best way to handle view switching so that value converters don't fire unnecessarily?
You should look at PRISM or any other composite UI framework. Prism will give you a great mechanism for this type of thing.
I solved this by getting rid of all IValueConverter and IMultiValueConverter classes and just using the ViewModel to provide all data. It turns out, this requires less code and hassle, and doesn't sacrifice anything that I'm aware of.

Categories