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 trie开发者_运维知识库d 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.
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.
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)
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.
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.
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.
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:
- Observe the command and enable or disable the button as it requests.
- 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.
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.
You need to create a command and bind it to the button. All of what you're looking for is baked into the runtime for you. Frameworks like MVVM Light and Prism make it easy to create new commands, but you could do it yourself like this:
Create a class that implements ICommand. Give it a private bool named SearchInProgress. When the search starts, set SearchInProgress to true. When the search completes (either successfully or because it timed out, was aborted, etc.) set SearchInProgress to false. Have the implementation of ICommand.CanExecute return !SearchInProgress. Expose a Search ICommand on your view-model and then bind the button's command property to the command on your view-model.
Psuedo-code:
public class MySearchCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private bool _searching;
private bool SearchInProgress
{
get { return _searching; }
set
{
if (_searching == value) return;
_searching = value;
if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty);
}
public bool CanExecute(object param)
{ return !SearchInProgress }
public void Execute(object param)
{
try
{
SearchInProgress = true;
// search code here including callback to OnSearchCompleted method
}
catch(Exception ex)
{
SearchInProgress = false;
}
}
private void OnSearchCompleted(SomeCallbackResult result)
{
SearchInProgress = false;
}
}
public class ViewModel : INotifyPropertyChange
{
public ICommand SearchCommand { get; private set; }
public ViewModel()
{
SearchCommand = new MySearchCommand();
}
}
XAML:
<UserControl ....>
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<Grid>
<Button Command={Binding SearchCommand} />
</Grid>
</UserControl>
精彩评论