tags:

views:

179

answers:

2

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?

+2  A: 

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

Benny Jobigan
I haven't gotten into the command system stuff yet... I'll definitely give it another look as you suggest.
Timothy Khouri
It's really really awesome. =)
Benny Jobigan
I think for my current code issue, I'm just going to add another property... but I'll be looking into / probably using commands as I move forward. Thanks a bunch!
Timothy Khouri
Glad I could help.
Benny Jobigan
+2  A: 

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.

itowlson
This is the solution that I was thinking of doing (seeing as I already have the one property that I made for the purpose of shaping a ton of controls in my XAML)... I'll add the other, and use it as well.
Timothy Khouri