Timothy Khouri
Timothy Khouri

Reputation: 31845

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 love to see my button just auto-magically become disabled, then enabled, etc. etc.

However, in my Button_Click event... I want to:

  1. Disable the button (by using ExtendButton.IsEnabled = false;)
  2. Run some asynchronous code (that hits a server... takes about 1 second).
  3. 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?

Upvotes: 1

Views: 987

Answers (2)

Benny Jobigan
Benny Jobigan

Reputation: 5305

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.)

Upvotes: 2

itowlson
itowlson

Reputation: 74802

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.

Upvotes: 2

Related Questions