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
Related
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);
}
I have a system set up where a ContextMenu hierarchy is populated dynamically using a MVVM architecture. All of my bindings function properly except for Command. My view is a ContextMenu that specifies an ItemContainerStyle.
The ContextMenu's ItemContainerStyle is set to this:
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command, Mode=OneWay}"/>
<Setter Property="IsCheckable" Value="{Binding IsCheckable}"/>
<Setter Property="IsChecked" Value="{Binding IsChecked, Mode=TwoWay}"/>
<Setter Property="Header" Value="{Binding Label}"/>
<Setter Property="ItemsSource" Value="{Binding Children}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContextMenu.ItemContainerStyle>
So far, there is no ItemTemplate, as seemingly I have been able to accomplish all the desired functionality in the style.
The ViewModel must be constructed with an instance of the model it wraps, so it seems the DataContext of the ContextMenu cannot be explicitly set to the ViewModel (the compiler complains that it does not have a parameterless constructor. The complaint mentions that a type converter can also be used, though I am unsure as to what that actually means (could solve the issue).
The relevant pieces of my ViewModel are as follows, starting with the two following read-only public facing members that are available to be bound to:
public CommandBinding CommandBinding { get; private set; }
public RoutedCommand Command { get { return CommandBinding.Command as RoutedCommand; } }
CommandBinding and its command are instantiated in the constructor:
CommandBinding = new CommandBinding(new RoutedCommand(), CommandBinding_Executed, CommandBinding_CanExecute);
The methods referred to in that construction simply operate on members of the model, and are implemented as follows:
void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
if (ContextItem.Command != null) ContextItem.Command(ContextItem);
}
void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ContextItem.IsEnabled;
if (ContextItem.ExecuteConditions != null) e.CanExecute = ContextItem.ExecuteConditions.GetInvocationList().Cast<ExecuteCondition>().All(s => s() == true);
}
It seems that when the binding to Command actually works, all of the items appear dimmed, as if CanExecute were returning false. However, when I set a breakpoint in CanExecute, execution never breaks at that point (though perhaps this is due to layout threading?). Even if I explicitly set e.CanExecute to true and comment out the other lines in CommandBinding_CanExecute, the items still appear dimmed. In XAML, I have tried binding to both the Command and CommandBinding members with and without Path=, all to the same effect. When I set the binding mode to OneWayToSource, the debugger appropriately complains that the property is read-only and cannot be operated on (I want the ViewModel to provide the command, so this is intended).
I have read the other examples and solutions to related issues. For those that follow the MVVM pattern, I cannot determine how my implementation differs.
For any solution, I must insist that I can still require the ViewModel to be constructed with the model as a parameter, and I would prefer the view to remain all XAML, with no C# code behind.
Seems the CommandBinding was the issue. I ended up creating my own implementation of ICommand that allows me to specify Execute and CanExecute delegates on construction... which worked perfectly.
This fixed the problem and the implementation was simple, but it is still unclear to me as to why my use of CommandBindings was not working.
I have a control which has a default value for a property. When the control first gets its dataContext set, it assigns this property automatically.
In the xaml now, I want it to be possible to UNset this property. I've tried setting it to x:Null of just the empty string, but then I get an error because there's no converter for the property. How do I simply unassign this property from the xaml in the rare cases where I want the feature disabled?
code where it is originally set:
void OmniBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if( e.NewValue is BindingObjectBaseExtended )
{
BindingObjectBaseExtended value = (BindingObjectBaseExtended)e.NewValue;
this.SetBinding(OmniBox.ContextValidationMessagesProperty, new Binding() { Source = value, Path = new PropertyPath("ValidationMessages") });
}
}
xaml where I want to unset the property.
<Style TargetType="ui:OmniBox">
<Setter Property="ContextValidationMessages" Value="" />
</Style>
Note that if I do not set up the binding automatically when the data context changes, then by default there are no validation messages and I have to do the following in the xaml to set them up:
<Style TargetType="ui:OmniBox">
<Setter Property="ContextValidationMessages" Value="ValidationMessages" />
</Style>
What I'm trying to do is make the above binding the default for my custom OmniBox control, and allow the user to unset it or change it to something else.
DependencyProperty.UnsetValue cannot be used in XAML.
http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty.unsetvalue(v=vs.90).ASPX
Personally, I would create a separate dependency property, such as bool AutoBindValidation and make it default to true. If it is false, don't do anything when the DataContext changes. This is a little more self-documenting. Depending on what exactly you're trying to do, you might not want to publicly expose ContextValidationMessages at all.
If you really want to do it the way you posted, I'm not sure why setting it to {x:Null} would cause an error (unless the property type is not nullable). But this approach would have problems because DataContextChanged is going to occur after the XAML is parsed. So the user can set it to {x:Null}, but then the DataContext will change and your code will set up the default binding and trample the user's value. You could set up the binding in the control's contstructor, but then if the DataContext does not have a ValidationMessages property, your control will be spitting out binding errors.
This may be impossible, my best bet was this:
<Setter Property="ContextValidationMessages"
Value="{x:Static DependencyProperty.UnsetValue}" />
But that throws "Cannot unset setter value". So you better inverse your logic or keep the property unset another way.
I don't think there is any supported way to do this in the xaml itself. In your code you are setting a local value on the ContextValidationMessagesProperty. The Style setters you included would have a lower dependency property precedence and even if they were evaluated they would set a value based on the specified Value - not clear it. Maybe instead of setting the binding in code you could have a Setter in your default style for OmniBox that sets that property - e.g.
<Setter Property="ContextValidationMessages" Value="{Binding ValidationMessages}" />
If you have to conditionally set the Binding then you could create a custom IValueConverter that checks for the specified type (passed as the parameter). e.g.
public class IsAssignableFromConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Type typeParameter = parameter as Type;
if (typeParameter == null)
return DependencyProperty.UnsetValue;
return value != null && typeParameter.IsAssignableFrom(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Then you might use it like this:
<local:IsAssignableFromConverter x:Key="isAssignableConverter" />
<Style TargetType="ui:OmniBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource isAssignableConverter}, ConverterParameter={x:Type ui:BindingObjectBaseExtended}}" Value="True">
<Setter Property="ContextValidationMessages" Value="{Binding ValidationMessages}" />
</DataTrigger>
</Style.Triggers>
</Style>
In the case where you don't want this property to be applied you might set the Style for that instance of the OmniBox to a new style and make sure to set the OverridesDefaultStyle property to true.
I suppose another option is to create another dependency property that will call ClearValue on the ContextValidationMessages property but this seems like it could be a maintenance issue.
For certain cases you can 'reset' to the default value of the parent control by using a RelativeSource. For instance I'm using a DataGrid and this worked for me to reset back to the 'default'.
This is a textblock inside a datagrid cell.
<TextBlock Text="{Binding ServiceName}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<!-- Change text color to purple for FedEx -->
<Trigger Property="TextBlock.Text" Value="FedEx">
<Setter Property="TextBlock.Foreground" Value="Purple"/>
</Trigger>
<!-- Reset if the cell is selected, since purple on blue is illegible -->
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}" Value="True">
<Setter Property="TextBlock.Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
This seems clever enough to inherit the correct color even when the window is inactive.
I hope that this question has not been asked elsewhere, but I'm having difficulty finding the right search terms to bring up any already-existing answers to this problem.
I'm implementing a program in C# WPF (MVVM) with many interview-style screens where the user inputs scientific data in an orderly manner. We would like to have the Textbox's and DataGrid's change Foreground and Background colors on an individual basis based on whether the data in that control has been inputted by the user, inputted as a default value by the program, or is a template value from another file the user imported. On top of this, we would like the UI to respond to validation checks from IDataErrorInfo implemented in the ViewModel.
Thus, the data displayed in a TextBox could be blue if it is a templated value, green if a program default, black if user inputed, and red if IDataErrorInfo says it is bad data.
My initial answer for implementing this was to create a custom class:
class AdornerString{
private string _myString;
private bool _isTemplate;
private bool _isDefault;
public string MyString{
get{
etc.
}
set{
etc.
}
}
// accessor properties and Constructors omitted for brevity
}
Then I have all my TextBox.Text properties in the View bound like so:
<TextBox Text="{Binding Path=someAdornerString.MyString,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"/>
and apply a style with DataTriggers that responds to someAdornerString's properties to create the appropriate colors.
However, IDataErrorInfo on the ViewModel doesn't validate the TextBox anymore. Why is this so, and is there a better way to solve my problem? I can't validate inside the AdornerString class.
A possible work-around, though undesirable:
The only other solution I can think of is to have two properties in the ViewModel for each field entered by the user- one for the data itself and the other being the same custom class as above minus the string. However, this means I can't generalize the style used for the textboxes. Each TextBox would have to have a custom style something like this:
<TextBox.Style>
<Style TargetType="{x:Type TextBox}"
BasedOn="{StaticResource OtherStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=**instanceCustomClass**.IsDefault}"
Value="True">
<Setter Property="Foreground"
Value="Green" />
</DataTrigger>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="Foreground"
Value="OrangeRed" />
</Trigger>
</Style.Triggers>
</Style>
since each UI field has a specific custom class associated with it.
I would seriously rather not implement this way, as I have possibly 100+ pages of input screens, with each screen having 1-12 TextBox's each, ignoring the DataGrids thrown in the mix (with parellel arrays of data and their associated custom classes).
Any ideas? I've read about custom validation, though I yet don't see how this might help in this case.
Ignore making use of the IDataErrInfo validation all together, as it seems that the you really want to make it 1 of 4 values...and 'bad' data just happens to be one of them.
You need to keep the items on an even playing field since you are treating them the same, just differentiating colors. Use a single property with an object wrapping the value and the state of the model within the ViewModel. Ignore IDataErroInfo and then use a converter to provide the coloring and then add a delegate to the AdornerString that will be set to the validation function written in the ViewModel for it.
I'm having an issue when trying to do something which should be as easy as. I've attempted to use a Trigger based on a DependencyProperty or a DataTrigger - I can't get either to work.
XAML for the trigger is:
<Style x:Key="FileWatchButton" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="Main:Main.XmlFilesAvailableForLoading" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
And the associated code-behind is:
public static readonly DependencyProperty XmlFilesAvailableForLoadingProperty =
DependencyProperty.Register("XmlFilesAvailableForLoading", typeof(bool), typeof(Main));
public bool XmlFilesAvailableForLoading
{
get
{
try
{
return (bool)this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.DataBind,
(System.Windows.Threading.DispatcherOperationCallback)delegate { return GetValue(XmlFilesAvailableForLoadingProperty); },
XmlFilesAvailableForLoadingProperty);
}
catch (Exception)
{
return (bool)XmlFilesAvailableForLoadingProperty.DefaultMetadata.DefaultValue;
}
}
set
{
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.DataBind,
(System.Threading.SendOrPostCallback)delegate{ SetValue(XmlFilesAvailableForLoadingProperty, value); }, value);
}
}
Basically the dp is being set correctly by the presenter (it's based on a FileSystemWatcher class looking for one or more files) but the Trigger is not being fired. Is this a threading issue?
Thanks.
It's not clear if the code is complete, but it looks like the Property path in your trigger may be wrong. Does the button being styled have a Main property? I am guessing not; it looks like you are trying to trigger on a property of a different element, called Main -- is that right?
In any case, the namespace prefix is not required. If the button has a property named Main, then you can address this directly; if it doesn't, then the prefix won't help you.
My guess is that you probably need a DataTrigger whose binding refers to the Main element:
<local:Main Name="MyMain" ... /> <!-- this has the XmlFilesAvailableForLoading property -->
<DataTrigger Binding="{Binding XmlFilesAvailableForLoading, ElementName=MyMain}"
Value=True>
<Setter Property="Background" Value="Red" />
</DataTrigger>
On an unrelated note, you should have any non-boilerplate implementation in your DP getter and setter. Remember that the binding and styling system will bypass the getter and setter and talk directly to the underlying storage. So I'd strongly advise changing these back to just plain GetValue and SetValue calls.