tags:

views:

912

answers:

7

I'm building a Silverlight app and I would like to disable a button while a long running operation is going on in the background. I'm using MVVM, so in the ViewModel I have a property called SearchInProgress. Now I would like to disable the search button whenever SearchInProgress in true. In WPF I would simply write a DataTrigger which sets IsEnabled on the search button to false.

Sadly DataTriggers are not available in Silverlight, so I'm looking for another solution. I've tried toying around with the VisualStateManager but I haven't gotten anywhere. The VSM seems like overkill for this simple thing I'm trying to achieve.

Any help is appreciated.

A: 

use a DependencyProperty or the INotifyPropertyChanged interface for your data-model, then bind to the public property SerachInProgress. I am thinking you might have to make a Converter as well, to convert to the opposite of your boolean.

Muad'Dib
A: 

Bind to the SearchInProgress property, but run it through a converter to reverse the boolean.

In the Binding, something like

IsEnabled="{Binding Path=SearchInProgress,Converter={StaticResource YOURCONVERTERHERE}}"

In the converter Convert function, I think it would be

return !(value as bool)

With whatever sanity checks you want to put around that for null items, etc.

pete the pagan-gerbil
I think that would work, however if I'd want to change several things at the same time in the GUI this could get a little messy.
Manuel R.
+1  A: 

An arguably better way is to use a DelegateCommand from Prism2 by attaching it to a search button and implementing its CanExecute method in your ViewModel so it returns !SearchInProgress.

Then when a ViewModel initiates a search operation it would change SearchInProgress to true (so that CanExecute returns false) and then would call RaiseCanExecuteChanged on a command (which will result in a button being disabled) Once a search operation is over a ViewModel would change SearchInProgress back to false( so that CanExecute returns true) and then would call RaiseCanExecuteChanged again (which will result in a button being enabled)

PL
I'd like to avoid using a framework for this project.
Manuel R.
A: 

The only sensible solution I can think of at the moment, is to have an Event "SearchCompleted" in the ViewModel that the view subscribes to and then changes the view accordingly when the event fires.

Manuel R.
+3  A: 

Rather than jump through hoops to have SearchInProgress=true set IsEnabled=false, why not just create a CanSearch property and bind to that. The property can be readonly (or have a private setter), and another properties can fire the PropertyChanged event on its behalf.

Ultimately, the point of a view model is that you remove logic from the view. Having the view bind to SearchInProgress (and thus, using a converter to negate it for IsEnabled) implies that the view understands when it should or shouldn't be able to search. Binding to a CanSearch property, however, means that the view model has complete control over when searching is enabled and the view can remain dumb.

Alternatively, you could use the Blend behaviors APIs, installed with Blend, as they have something similar to data triggers.

Richard Szalay
Good Point. I'll look into both things you mentioned.
Manuel R.
What I would like to avoid though, is binding to the CanSearch property on every control that I want to disable or change when a search is in progress. I'd rather have it in one place if possible.
Manuel R.
I'd personally prefer XAML binding in each location, but if you don't you could always track the `PropertyChanged` event in the code behind and update the IsEnabled property on each applicable control. (Keep in mind that if you change the value of a property with OneWay binding on it, you'll remove the binding)
Richard Szalay
A: 

There is absolutely no reason to add the PRISM dll to your project for this. You only need 10 lines of code and 2 unit tests.

You still want to use the commanding pattern though. Add a "Command" attached property that takes an 'ICommand' and when the property is set you:

  1. Observe the command and enable or disable the button as it requests.
  2. Adds a handler to the button's "Click" event that calls an 'Execute' method on the command.

A note about PRISM: The library sucks! But the Composite Application Guidance Book is an essential read for anybody writing an MVVM application, and provides more information on the commanding pattern.

Alun Harford
A: 

I've found a solution that I am quite happy with. First I defined two VSM states on my LayoutRoot Grid 'SearchInProgress' and 'Normal'.

        <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="Standard">
            <VisualState x:Name="SearchInProgress">
                <Storyboard>
                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="SearchButton" Storyboard.TargetProperty="(Control.IsEnabled)">
                        <DiscreteObjectKeyFrame KeyTime="00:00:00">
                            <DiscreteObjectKeyFrame.Value>
                                <system:Boolean>False</system:Boolean>
                            </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="SearchCancelButton" Storyboard.TargetProperty="(Control.IsEnabled)">
                        <DiscreteObjectKeyFrame KeyTime="00:00:00">
                            <DiscreteObjectKeyFrame.Value>
                                <system:Boolean>True</system:Boolean>
                            </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
            <VisualState x:Name="Normal"/>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

Pretty simplistic and I might adapt them but it works.

To switch between the states I use the DataStateBehavior from here which lets me bind to a Property on the datacontext (viewmodel) and switch between two states accordingly:

        <interactivity:Interaction.Behaviors>
        <exprsamples:DataStateBehavior Binding="{Binding Path=SearchIsInProgress, Mode=TwoWay}"
                                       Value="True"
                                       TrueState="SearchInProgress"
                                       FalseState="Normal">
        </exprsamples:DataStateBehavior>
    </interactivity:Interaction.Behaviors>

I think I'm now able to use the power of the VSM, designability in Blend and the flexibility of the 'DataTrigger' mechanism to full effect.

Manuel R.