I have a WPF form with multiple controls, and I want a button ('Assign') to be enabled if and only if a variety of conditions on those controls are true. Some conditions include testing whether textboxes are empty.
I initially achieved this by binding the contents of the textboxes to properties and binding the IsEnabled property of the button in the XAML:
<TextBox Name="NewName" Text="{Binding NewName}" />
(etc)
<Button Name="Assign" ... IsEnabled="{Binding Path=AssignEnabled}" />
with a corresponding method in the C# ViewModel:
public bool AssignEnabled
{
get
{
return !string.IsNullOrWhiteSpace(this.NewName) && ... (etc)
}
set
{
...
this.NotifyPropertyChanged("AssignEnabled");
...
}
}
The problem was that this caused the button to be updated when the focus was lost from the respective textbox, not whenever the text was changed. I could have used the TextChanged property on each textbox in the XAML to call code, but this seemed overcomplicated.
To fix this, I removed the binding and switched to DataTriggers like this:
<Button Name="Assign" ... >
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, ElementName=NewNameOption}" Value="true" />
<Condition Binding="{Binding Text, ElementName=NewName}" Value="{x:Static sys:String.Empty}" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False"/>
</MultiDataTrigger>
...
(more conditions here)
...
</Style.Triggers>
</Style>
</Button.Style>
</Button>
This worked absolutely fine. The only drawback was that the unit tests I had previously written to test the IsEnabled status of the button were no longer usable (since they used the AssignEnabled property of the ViewModel which is no longer bound to the button).
To allow those unit tests to work, I needed the AssignEnabled property to reflect the IsEnabled status of the button. I expected to be able to fix this by adding a OneWayToSource binding, like this:
<Button Name="Assign" ... IsEnabled="{Binding AssignEnabled, Mode=OneWayToSource}">
with the property changed to:
public bool AssignEnabled { get; set; }
However, it seems as though this binding, even though set to OneWayToSource, overrides the DataTriggers, since the enabling/disabling of the button no longer works at all.
I know there are other options, such as using a Converter or implementing ICommand, but I would prefer to keep this simple if possible and fix the above method (or at least understand why it doesn't work). How can I access the IsEnabled property of the button without violating the ViewModel paradigm or preventing the DataTriggers from working?
You can keep your first solution and set UpdateSourceTrigger=PropertyChanged for the bindings. In this way Bindings will change instantly when text changes and not only when focus is lost. By the way this solution gives you mor flexibility as you can perform more complex testing on your fields (for exemple test email adress format).
Related
When I have a control A that contains a control B, there are properties Prop that are inherited. That means, B.Prop will automatically take the value of A.Prop if B.Prop is not explicitly set. As far as I know, IsEnabled is such a property.
Now I have a situation where I do set the value of B.IsEnabled explicitly, and still it is overwritten by the value of A.IsEnabled. Why is that so, and how can I correct it?
In this situation A is a StackPanel and B a TextBox:
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding InDisableMode}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBox Text="some text">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding InDisableMode}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
The above XAML snippet has its DataContext set to my ViewModel. The ViewModel contains a property InDisableMode which is a bool. When it is false, everything is as expected: The StackPanel is enabled and the TextBox is disabled.
But when InDisableMode is true, both the StackPanel and the TextBox are disabled although both Triggers should trigger!
Note: I know I can databind IsEnabled to InDisableMode in both controls (in the TextBox directly, in the StackPanel by using a complement converter). I have not tried if this works since I want to do this with Triggers anyway.
EDIT:
The point of disabling the StackPanel is to disable all of its children easily (except the TextBox which I want to enable instead). Any other ideas how to solve this task without changing the parent-child-relationship or creating new controls? At the moment, the only way I see is to disable all children except the TextBox one by one...
If you disable a control, its children are disabled. Since a StackPanel is not interactive, there is no reason to disable it other than to disable its interactive children.
If you want to enable a control A while its parent B is disabled, you can't do that. B cannot be the parent if you want to enable A while B is disabled.
For a workaround, you can put them both in a Grid, with the TextBox defined last, to superimpose the TextBox on top of the StackPanel. Then it will be within the StackPanel's area but it won't be a child of the StackPanel.
This happens because UIElement.IsEnabled property uses value coercion by inheriting the value from its parent. This it does by using CoerceValueCallback. Value coercion is ranked first in Dependency Property Setting Precedence List.
So, to override this behavior, we have two options. Firstly, to use AddOwner() to register our type as new owner of IsEnabled property. Secondly, to override the metadata using OverrideMetadata(). This second method would work only if you inherit directly from UIElement.
So, lets say we want our Button to behave differently, we should create a new Button like below :
public class CButton : Button
{
public static readonly DependencyProperty IsEnabled;
static CButton()
{
IsEnabled = UIElement.IsEnabledProperty.AddOwner(typeof(CButton),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.None,
UIElement.IsEnabledProperty.DefaultMetadata.PropertyChangedCallback,
new CoerceValueCallback(IsEnabledCoerceCallback)));
}
private static object IsEnabledCoerceCallback(DependencyObject d, object baseValue)
{
return (bool) baseValue;
}
}
Here, we are returning assigned value as it is from IsEnabledCoerceCallback. Before returning, you can also introduce the behavior : If user doesn't provide any value for IsEnabled, then use inherited value from parent, else use CButton.IsEnabled user assigned value.
On a side note, try setting null in place of new CoerceValueCallback(IsEnabledCoerceCallback) , see what happens.
I am struggling to wrap my head around the real benefit of binding in WPF.
I have an application with a large textbox, designed for taking several hundred characters of user input. I have bound this to a "Text" string in my ViewModel. This works OK.
I also have a button with content "Submit". I need to change the content of this button once or twice, so I am doing it in the click event method in the window's code behind. I could, of course, bind the text to the ViewModel, but is it really worth it?
Should everything have a binding? What if I need to display a MessageBox? That will need some logic inside the onclick.
Should click events me as follows:
private void button_Login_Click(object sender, RoutedEventArgs e)
{
viewModel.DoSomething();
}
..where everything gets handed to the ViewModel?
I know this is a general question but I have tried my best to ask direct, answerable questions.
UI concerns are perfectly fine residing in your codebehind.
Business concerns should reside in your ViewModels. The commands and information they expose are what should be bound to elements in your UI.
Since changing the text in a button based on what the button is supposed to do is a UI concern, binding the text of the button to your ViewModel would be pointless.
I wouldn't put any code in codebehind. Create an ICommand property in your ViewModel and bind the buttons Command property to it. I use the ICommand implementation from MVVM Light (RelayCommand) but you can create your own or use one of the many other frameworks.
I'd then have a State property (ProcessStatus here) that I use a DataTrigger with to update the text on my button.
ViewModel
public ICommand LoginCommand
{
get
{
return new RelayCommand(() =>
{
ProcessStatus = Status.AUTHORIZING;
DoSomething();
});
}
}
private Status _processStatus;
public Status ProcessStatus
{
get { return _processStatus; }
set
{
if (value ==_processStatus)
return;
_processStatus= value;
RaisePropertyChanged("ProcessStatus");
}
}
View
<Button Command="{Binding LoginCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content"
Value="Submit" />
<Setter Property="IsEnabled"
Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ProcessStatus}"
Value="{x:Static enum:Status.AUTHORIZING}">
<Setter Property="Content"
Value="Authorizing..." />
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
I have two textbox.
Textbox A and Textbox B.
I want to bind these two text boxes to a single command button Button C.
That is if text of any the two text box is changed by the user then only the command button should get active.
Its really easy to achieve the above from Code Behind file but I was wondering that is it possible to bind a single control Button C to two elements Textbox A and Textbox B and achieve the needed through XAML.
Thanks and Regards.
If you want to enable the button if any of the two textboxes has text, you can use a MultiDataTrigger:
<TextBox x:Name="TextBoxA" />
<TextBox x:Name="TextBoxB" />
<Button x:Name="ButtonC">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Text, ElementName=TextBoxA}" Value=""/>
<Condition Binding="{Binding Text, ElementName=TextBoxB}" Value=""/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
My Suggestion is as follows:
Have TextBoxA bind to Field1 and TextBoxB bind to Field2 and
bind the Command of ButtonC to a relay command. Ensure that you implement the CanExecuteMethod
Articles here: MSDN Article on RelayEvents and here: Implementation of RelayCommand
In the canExecute method have an implementation that looks something like this:
public bool CanExecuteButtonC(object a)
{
If (!string.IsNullOrEmpty(Field1) && !string.IsNullorEmpty(Field2))
return true;
return false;
}
if the canExecute method returns false, the button will automatically be disabled, and if returns true it will be activated.
bind IsEnabled of button to a boolean property
code is not compiled or tested.
public bool IsButtonEnabled
{
get
{
return !String.IsNullorEmpty(String1) && !String.IsNullorEmpty(String2);
}
}
Make sure propertychanged for IsButtonEnabled is fired when the strings are changed
public string String1
{
//get should be here
set
{
_string1 = value;
OnPropertyChanged("IsButtonEnabled");
OnPropertyChanged("String1");
}
}
So, I have this Window with some controls. In the resources section I've defined this style:
<Style x:Key="StyleNavBar" TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentTheme, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<DataTrigger.Value>
<theme:WinTheme>WindowsClassic</theme:WinTheme>
</DataTrigger.Value>
<Setter Property="Background" Value="#FFFFFFFF" />
</DataTrigger>
</Style.Triggers>
</Style>
In my Window I define an attached property named 'CurrentTheme' which stores (based on an enum) the current theme used system-wide. Here's the code:
public static readonly DependencyProperty CurrentSystemThemeProperty =
DependencyProperty.RegisterAttached(
"CurrentSystemTheme",
typeof(WinTheme),
typeof(MainWindow),
new UIPropertyMetadata(WinTheme.AeroGlass));
public WinTheme CurrentTheme
{
get
{
return (WinTheme)GetValue(CurrentSystemThemeProperty);
}
set
{
SetValue(CurrentSystemThemeProperty, value);
}
}
Everytime the user changes the system theme, my window receives a callback via WndProc, informing that the theme has changed. And, as you can see, the default value of the CurrentTheme property is WinTheme.AeroGlass. Then I have a grid styled with the style defined above:
<Grid Height="34" Name="grdNavBar" VerticalAlignment="Top" Style="{DynamicResource StyleNavBar}">
My goal is to change the style based on the value of CurrentTheme, but the trigger defined above does not work when the theme changes (it only works with the default value of CurrentTheme, i.e., isn't reacting to changes in the property).
Any ideas on how to accomplish this?
You've passed a different name to DependencyProperty.RegisterAttached than the name you've given the property in C#. So the DP system thinks it's called CurrentSystemTheme, but your code thinks it's called CurrentTheme. Try passing CurrentTheme as the first argument to RegisterAttached.
Also, you might want to enable WPF debug log output for data binding (which is on by default in older versions of WPF, but in .NET 4/VS 2010, you need to go and switch it on in the Tools->Options window under Debugging->Output Window). That way, I usually set the WPF Trace Settings -> Data Binding option to All. That way if a data binding fails, you'll see an error in the Output window. This might help you diagnose why that data trigger is failing.
I've hit a bit of a dead end in trying to figure this one out... Using the MVVM pattern in WPF, our C# Model fires an event to say something has happened. I want to be able handle that event in my ViewModel and then either kick of a storyboard or change the visibility of a hidden panel on the current Xaml Page. This has to be handled with no Code Behind.
I can sync for the event in my ViewModel, update a property to say what the name of that event is and fire a NotifyPropertyChanged even but how do I get that to either kick off a storyboard or map to a boolean true/false on the Visibility property of my Grid? The property I bind to hs to be the event name as different grids may be shown based on different events so I need a way of mapping this to a boolean. However the ideal solution would be to kick off a storyboard. I've looked at DataTriggers but they all seem to be linked to styles and not to actual pages.
Any ideas of how I can achieve this?
Thanks!
I've used this in the past to kick off a storyboard in code-behind
Storyboard animation = (Storyboard)this.FindResource("ShowPanelStoryboard");
animation.Begin();
This code goes behind the View, not in the ViewModel. Personally, I don't mind some code behind my View providing it is only related the View. In the project I used this in, I added a listener to the VisibilityChanged event and when it got changed to Visible, I ran the storyboard.
As for showing your popup, there's a few ways. One of my favorites was just adding an IsPopupShown property to the ViewModel, binding my panel's visibility to it, and setting it to true anytime the popup should be shown. The ViewModel then handles the events that trigger the popup being shown or not.
An alternative as suggested by Dave White is to use a converter. If your value is not always true/false then you could create a converter that checks if a bound value is equal to the ConverterParameter, and return a Visibility value.
From your comment, it seems to me like what you may want to do is expose an Event property of type object in your view model. When the view model receives an event, it sets Event to an object of a type appropriate for that event. In your XAML, you have this:
<ContentControl Content="{Binding Event}"/>
and in the resource dictionary define a DataTemplate for each specific type of event you want to display. If Event is null, nothing gets displayed. If Event contains an object that you've defined a DataTemplate for, it gets displayed using that template.
Yes, you'll need to create a class for each type of event (if you don't already have one).
Another way is to implement the poor man's template selector:
<TextBlock Text="This is displayed if Foo contains 'BAR'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAR">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="This is displayed if Foo contains 'BAZ'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAZ">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
It's kind of stupidly verbose, but it's an easy way to handling a lot of mutually-exclusive display options.
Bind the Visibility property on your grid, in Xaml, to the boolean property on your ViewModel.
<Grid Visibility="{Binding Path=VisiblePropertyOnViewModel}">
Now do whatever you need in your ViewModel and set the property. As long as it does INotifyPropertyChanged or is a DependencyProperty, it will work.
I'd have to do more digging to figure out how to kick off a Storyboard, but I have no doubt it would be almost as easy. Storyboards can be kicked off by PropertyTriggers as well I believe. I'll leave this to get you started.