I created a WPF (.Net 4) UserControl containing some ComboBoxes and a TextBox. In XAML, some ValidationRules are bound to the TextBox. If the TextBox contains invalid data, a red frame is shown, and the tooltip is set to the error description. Works well.
Next, I placed two instances of that UserControl on a form, and added a button. In XAML, the button is connected to a RelayCommand of the ViewModel. Now I want the button to be enabled only when both of the UserControls contain valid data only.
Not a problem, I thought, let me use a strategy which works elsewhere. I added a trigger:
<Button Content="_OK" ... Command="{Binding Path=OKCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=cascadingComboFrom, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=cascadingComboTo, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
But there is a catch: Validation.HasError is always false for the UserControls - the Validation failed for an element inside the UserControl, not for the UserControl proper.
I know that I can register the Click event of the button, check the validity there using the method shown in Detecting WPF Validation Errors, and prevent the execution of the RelayCommand by setting the Handled property of the RoutedEventArgs to true. But that looks more like WTF than WPF.
What do you suggest? How can I retrieve the Validation Errors of the UserControl's children? Or how can I get them in the RelayCommand's CanExecute method? Or some other tricks?
You can set a property on the command binding called ValidatesOnDataErrors.
Implementation would look something like this:
<Button Content="_OK" Command="{Binding, Path=OKCommand, ValidatesOnDataErrors=True}"/>
You can read more about it here.
The Button.IsEnabled property is already hard wired to the CanExecute method of your RelayCommand, so all you need to do is to set that return value to false when the form fields are invalid:
private bool CanExecute(object commandParameter)
{
return areFormFieldsValid;
}
Now, how you set the bool areFormFieldsValid variable to true or false is up to you... there are several ways of doing that. Personally, I prefer to use the IDataErrorInfo interface, which has a handy Error property that you can check. There are many online tutorial on how to implement this, so I won't repeat that here... however, the end result is something like this:
private bool CanExecute(object commandParameter)
{
return string.IsNullOrEmpty(yourDataObject.Error);
}
Related
I have a function in my UI where I want to be able to collapse/make visible a text message depending on the value of a custom property in my window object.
Using online references, I have come up with this code-behind to register the property:
public bool ValidInterval
{
get { return pValidInterval; }
}
private bool pValidInterval = true;
public static readonly DependencyProperty ValidIntervalProperty = DependencyProperty.Register("ValidInterval", typeof(bool), typeof(Settings), new UIPropertyMetadata(true));
And this corresponding XAML for the label:
<Label Name="DynamicWarning" Content="Time interval must be a valid positive integer.">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ValidInterval}" Value="true">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ValidInterval}" Value="false">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
Unfortunately, this does not work. I can get it to set the parameter depending on the initial value of the property, but doesn't update the visibility dynamically like I want. I have been looking at this for an hour and what I have seems consistent with examples I am finding online for similar operations. Am I doing something wrong, or can you not update the visibility on the fly? If the latter, how do I achieve an equivalent effect?
ΩmegaMan's answer is correct. But I wanted to clarify, you don't need a backing field with a dependency property, the static dependency property IS the backing field.
public bool ValidInterval
{
get { return (bool)GetValue(ValidIntervalProperty); }
set { SetValue(ValidIntervalProperty, value); }
}
However, if you aren't in need of a dependency object specifically, then you may just want to use INotifyPropertyChanged as ΩmegaMan has it right. Dependency properties are typically used when you need to bind another property to them, such as when making your own custom control. For example Visibility itself is the dependency property in your example and ValidInterval just needs to be a normal property that invokes the NotifyPropertyChanged event.
You need for the holder class of the properties which are bound to the page, to adhere to INotifyPropertyChanged Interface and implement it.
That process informs the bound controls on the page that something has changed, and when it has changed, then the control is "notified" of the change; then it reads afresh the property it is bound to.
For WPF/Xaml they specify the seperation of data concerns for the views to business logic, is done by implementing the Model-View-ViewModel or MVVM pattern.
The link provided is dry, and there are other resources which can describe on the net, but it simply says put all your business logic that is bound from the View to a separate View Model Class; which is instantiated on your View.
I provide a basic example, any version of .Net can be used, on my blog:
MVVM Example for Easier Binding
I have a XAML file in my Xamarin project that displays different views depending on the state of 2 picker views. The Picker View is a custom view that lets you display an Enumerator as a picker. The important part is, that the SelectedItem does fire the PropertyChanged notification.
So in my Xaml I define my style like this:
<Style x:Key="SinglePressureRelativeHumidity" TargetType="ContentView">
<Style.Triggers>
<MultiTrigger TargetType="ContentView">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference Mode}, Path=SelectedItem}"
Value="{x:Static enums:HumidityCalculatorMode.SinglePressure}" />
<BindingCondition Binding="{Binding Source={x:Reference KnownValue}, Path=SelectedItem}"
Value="{x:Static enums:HumidityCalculatorKnownValue.RelativeHumidity}" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="IsVisible" Value="true" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
This is placed inside the local resource dictionary. The References Mode and KnownValue refer to the custom picker views, which are correctly defined in the same xaml file.
I later have a StackLayout with multiple ContentPages each looking similar to the following:
<ContentView Style="{StaticResource SinglePressureRelativeHumidity}"
IsVisible="False">
<StackLayout>
<controls:TemperatureEntry Title="Temperature"
Temperature="{Binding HumidityCalculator.InputTemperature, Mode=TwoWay}"/>
<controls:PressureEntry Title="Test Pressure"
Pressure="{Binding HumidityCalculator.InputPressure, Mode=TwoWay}" />
</StackLayout>
</ContentView>
Where each ContentPage has its own Style with different Conditions.
Now to the problem, when I change the value of any of the picker the ContentPages get enabled or disabled as you would expect, the one where the style's MultiTrigger's Conditions are met gets set to visible all other are set to invisible.
However, the problem is that when loading the view all are set to invisible. So it is as if the trigger only checks when there are changes made by the user. I have tested various things.
First I tried setting the value of both pickers to the wanted default value after the InitializeComponent method without success. I made sure that the Property SelectedItem does fire the PropertyChanged notification with the correct name.
Second I tried inverting the isVisible property of the ContentViews to true but then all were visible which also wasn't what i wanted.
So how can I trigger the MultiTrigger with my default values?
I was able to fix this by binding the BindingCondition directly to the Model which the Pickers set their values to.
I am not sure why this fixed the issue.
I have a TextBox that is tied to a command like this:
<TextBox Text="{Binding Path=TextContent, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=MyCommand}" Key="Enter" />
</TextBox.InputBindings>
</TextBox>
The property TextContent is a string defined in the ViewModel. The command MyCommand is also defined in the ViewModel. The ViewModel does not know the View.
The command will be called whenever the TextBox has focus and the enter key is hit. Unfortunately, if CanExecute returns false, the user cannot see (visually) that the command was not executed, because there is no visual change in the TextBox.
I am looking for advice on how to show the user that the command could not be executed after he had pressed enter.
My ideas (and my doubts about them):
Disabling the TextBox when CanExecute returns false: This is no option because the return value of CanExecute can change everytime a letter is typed/changed (the text in the TextBox influences the outcome of CanExecute). When it is disabled for the first time, the user cannot type into it any more, so it will stay disabled forever.
Show a message box saying that the command was not executed: Remember, the ViewModel does not know the View. Is it even possible to open a message box from the ViewModel? Furthermore, where should I put the call to opening a message box? Not inside CanExecute because I only want to get the message box after hitting enter, not everytime CanExecute returns false. Maybe make CanExecute always return true and do the checks inside Execute: If checks are okay, do the command stuff, if not, show some message to the user. But then, the point of having CanExecute is missed entirely...
I want to keep MVVM, but some codebehind for redirecting stuff to the ViewModel seems okay for me.
I suggest the following solution.
Here's an example on how to notify the user which I'm working on at the moment.
I want the user to type in a data limit which is of type int, double or string.
It want to check so the user type in the correct type.
I use a property ValidateLimits which checks the string MyLimits which in your case is TextContent.
Everytime the user type in anything in the TextBox, ValidateLimits will check the string. If it is not a valid string in the textbox then return false otherwise return true.
If false then highlight it with the DataTrigger by setting some Properties on the TextBox which in my case is some Border and Foreground colors, also a ToolTip.
Also in your case you want to call your Validate method in your CanExecute method.
If you already have a function for checking that the command is OK then just add it to the DataTrigger binding.
<TextBox Text="{Binding MyLimit1, UpdateSourceTrigger=PropertyChanged}" Margin="-6,0,-6,0">
<TextBox.Style>
<Style TargetType="TextBox">
<!-- Properties that needs to be changed with the data trigger cannot be set outside the style. Default values needs to be set inside the style -->
<Setter Property="ToolTip" Value="{Binding FriendlyCompareRule}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ValidateLimits}" Value="false">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="ToolTip" Value="Cannot parse value to correct data type"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
public bool ValidateLimits
{
get
{
// Check if MyLimit1 is correct data type
return true/false;
}
}
Use a Property bool IsCommandExecuted in your Commandclass. Set this property accordingly.
Use a ToolTip and bind its IsOpen property to IsCommandExecuted property like this :
<TextBox ...>
<TextBox.ToolTip>
<ToolTip IsOpen="{Binding MyCommand.IsCommandExecuted}">...</ToolTip>
</TextBox.ToolTip>
</TextBox>
This explains the concept, modify it accordingly.
I have a custom window and would like to apply a custom style to the window when it cannot be accessed due to it having a child window (displayed via showdialog()). I assumed that the trigger property would be "IsEnabled", however this property is not set to false when showdialog() is called. I have also tried "Focusable" and looked through the list of properties in hope of finding the obvious solution. This led to me trying a data trigger which binds to "OwnedWindows.Count" but again this doesn't work! Surely this should be simple and I am missing something?
Triggers tried:
<Trigger Property="IsEnabled" Value="False">
<Trigger Property="Focusable" Value="False">
<DataTrigger Binding="{Binding Path=OwnedWindows.Count, RelativeSource={RelativeSource Self}}" Value="1" >
Note: Both IsEnabled and Focusable do the required job when I manually set the properties to False - so I know the trigger works, they just aren't being set when ShowDialog() is called.
There is no property like that. Use ComponentDispatcher.EnterThreadModal and ComponentDispatcher.LeaveThreadModal events instead. They are fired when a WPF modal dialog is shown or closed, respectively.
I'm trying to disable double clicks on buttons and currently the only way I know is to handle the PreviewMouseDoubleClick and set the e.Handled = true.
Is there a way to do this in a button style? Or even better, disable double clicks application wide?
I would use an attached behavior (see this article). For example, say I create an attached behavior called DisableDoubleClickAttachedBehavior which handles the double click mouse event and sets e.Handled = true.
Then, you can nicely set the property via a style in XAML:
<Style x:Key="DisableDoubleClickStyle">
<Setter Property="p:DisableDoubleClickAttachedBehavior.Enabled" Value="True" />
</Style>
<Button Style="{StaticResource DoubleClickDisabledStyle}">Hi!</Button>
Or, you can override the style for all buttons (like you wanted):
<Style TargetType="Button">
<Setter Property="p:DisableDoubleClickAttachedBehavior.Enabled" Value="True" />
</Style>
I tested this and it seems to work nicely.
I don't think it can be done in a style or application-wide. Best bet might be a class derived from Button that includes that event handler by default.