Handling view state in Silverlight with MVVM
I am interested to know how you people out there handle the view state in a Silverlight application with the MVVM pattern. Let's say I have a simple search mask that as开发者_Python百科ynchronously calls a webservice. While the search is in progress, I'd like to change the gui accordingly: - Disable the Search button - Enable a Cancel button - etc
Using wpf I could create a datatrigger that binds to some property in the viewmodel and then makes the changes to the gui. Now since I don't have a datatrigger in Silverlight, what would be the most sensible way to achieve this similarly to the datatrigger (neat code, in one place if possible)?
(I posted a similar question, but it was worded poorly)
My standard way of doing this is to expose a "ViewState" property from the view model (normally an enum). The view then binds to the property and uses the visualstatemanager to switch to appropriate visual states depending on the enum.
The DataStateSwitchBehavior from the Expression Samples is a good example on how to do the switching to visual states.
EDIT In response to comment
First off, when dealing with VisualStates use Blend (no one should be forced to write that much XAML by hand). I believe it's even on all(most?) of the MSDN subscriptions.
Using Visual States starts with Visual State Manager
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="GroupOne">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Searching"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
You'd typically add this to the layoutroot.
The visual state manager consists of a collection of StateGroups which in turn consists of a collection of VisualStates.
The groups keep mutually exclusive states organised, since you can have multiple visual states active at any one time but only one state from each group. The standard pattern is to have a empty state called something like "Normal" or "Default" to be used turn off the other states. A Base state basically.
In your case you would then have a "Searching" visual state which would contain a storyboard which would disable various buttons, activate busy animations etc.
The handiest way is to use the BusyIndicator
from the Silverlight Toolkit I suppose. As it masks the whole area you apply it to, all buttons are automatically disabled.
For a cancel button you'd have to edit the BusyIndicator
's template to place it directly next to the load animation I think.
You would then simply bind the BusyIndicator's IsBusy
property to a corresponding property in your ViewModel which you set before loading and reset when you're done.
My solution is similar to Graeme Bradbury's, BUT I do not use DataStateSwitchBehavior, because if my X control is placed inside a tab panel (or something similar) and the state changes while I'm on another tab, then I'll get an exception ('element' not found..). Exception is thrown because my X control is unloaded while I'm on another tab and elements that need to be updated are not found.
So here's what I do:
In my view model I have a property VisualState that sends a notification message when a state changes (I use MVVM light toolkit):
private string visualState = XVisualStates.InitialState;
public string VisualState
{
get
{
return visualState;
}
set
{
visualState = value;
Messenger.Default.Send(new XStateChangedMessage(value));
}
}
and in My view's code behind I subscribe to a notification:
public partial class XControl : UserControl
{
private string visualState = XVisualStates.InitialState;
public XControl()
{
InitializeComponent();
//go to state when view is loaded
Loaded += (s, e) => ChangeState(); //every time a view is loaded go to current state
//change visual state when a notification is received
Messenger.Default.Register<XStateChangedMessage>(this,
state =>
{
visualState = state.CurrentState; //save current state
ChangeState();
});
}
void ChangeState()
{
try
{
VisualStateManager.GoToState(this, visualState, true); //will throw an exception if current view is unloaded
}
catch
{
//NOTE: supress 'element' not found errors if user navigated to another view and state changes
}
}
}
and XStateChangedMessage is a simple class:
public class XStateChangedMessage
{
public string CurrentState { get; private set; }
public XStateChangedMessage (string currentState)
{
CurrentState = currentState;
}
}
1) You can create something like IsEnabledSearch property in view model, and bind it to Button's IsEnabled or Visibility property(you will need a Bool to Visibility Converter). Creating new Visual States just for that is not very efficient, because your buttons already have all kind of visual states inside to support this behavior.
2) Jounce mvvm framework has a very nice implementation to support VisualStates from ViewModel; Jounce Visual State Manager
精彩评论