WPF: Once I set a property in code, it ignores XAML binding forever more... how do I prevent that?
I have a button that has a datatrigger that is used to disable the button if a certain property is not set to true:
<Button Name="ExtendButton" Click="ExtendButton_Click" Margin="0,0,0,8">
<Button.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
That's some very simple binding, and it works perfectly. I can set "IsConnected" true and false and true and false and true and false, and I lov开发者_StackOverflow社区e to see my button just auto-magically become disabled, then enabled, etc. etc.
However, in my Button_Click event... I want to:
- Disable the button (by using ExtendButton.IsEnabled = false;)
- Run some asynchronous code (that hits a server... takes about 1 second).
- Re-enable the button (by using ExtendButton.IsEnabled = true;)
The problem is, the very instant that I manually set IsEnabled to either true or false... my XAML binding will never fire again. This makes me very sad :(
I wish that IsEnabled was tri-state... and that true meant true, false meant false and null meant inherit. But that is not the case, so what do I do?
Benny's answer provides one approach (and an excellent one). Another is to extend your model to support the "in async operation" state and to add another trigger to respond to that:
XAML:
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding IsInAsyncOperation}" Value="True">
<Setter Property="Button.IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
Code-behind:
private void ExtendButton_Click(...)
{
IsInAsyncOperation = true;
// begin async operation
}
private void OnAsyncOperationComplete(...)
{
// retrieve results etc.
IsInAsyncOperation = false;
}
Note: You'd define the IsInAsyncOperation property on the same class as IsConnected; if that's a view model rather than the Window class, then you'll need to tweak the code-behind accordingly.
There's a much better way to get this functionality in WPF. It's the commanding system, and it's awesome. Any button, menu item, hot key, etc can be linked to a single command which automatically handles enabling/disabling (as you desire in your program). It's also clean and reusable, and sooooo easy to use.
For example, in an application of mine, I have an "about dialog" that shows up when the user hits F1. I created a command called AboutCommand
by implementing ICommand
.
public class AboutCommand:System.Windows.Input.ICommand
{
public bool CanExecute(object parameter)
{
return true; // in this case the command is never disabled
}
public event EventHandler CanExecuteChanged
{
// not needed in this case, but in commands when CanExecute actually
// changes, this performs the "magic" of disabling/enabling your controls
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
new AboutWindow().ShowDialog(); //happens when the command is executed
}
}
Then, in my window's xaml, I added*:
<Window.Resources>
<local:AboutCommand x:Key="About"/>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Command="{StaticResource About}" Gesture="F1"/>
</Window.InputBindings>
You could also set the command to a button like so.
<Button Command="{StaticResource About}" Content="About this program"/>
Both the F1 key and the button would be disabled if AboutCommand.CanExecute()
returned false.
*(I actually did it differently, because I'm using the MVVM pattern, but this works if you aren't using that pattern.)
精彩评论