MVVM Binding Orthogonal Aspects in Views e.g. Application Settings
I have an application which I am developing using WPF\Prism\MVVM. All is going well and I have some pleasing MVVM implementations. However, in some of my views I would like to be able to bind application settings e.g. when a user reloads an application, the checkbox for auto-scrolling a grid should be checked in the state it was last time the user used the application.
My view needs to bind to something that holds the "auto-scroll" setting state. I could put this on the view-model, but applications settings are orthogonal to the purpose of the view-model. The "auto-scroll" setting is controlling an aspect of the view. This setting is just an example. There will be quite a number of them and splattering my view-models with properties to represent application settings (so I can bind them) feels decidedly yucky.
One view-model per view seems to be de rigeuer...
What is best\usual practice here?
- Splatter my view-models with application settings?
- Have multiple view-models per view so settings can be represented in their own right?
- Split views so that controls can bind to an ApplicationSettingsViewModel? = too many views?
- Something else?
Edit 1
To add a little more context, I am developing a UI with a dynamic tabbed interface. Each tab will host a single widget and there are a variety of widgets. Each widget is a Prism composition of individual views. Some views are common amongst widgets e.g. a file picker view. Whilst each widget is composed of several views, conceptually a widget has a single set of user settings e.g. last file selected, auto-scroll enabled, etc. These need to be persisted and retrieved\applied when the application starts again, and the widget views are re-created.
My question is focused on the fact that conceptually a widget has a single set of user settings which is at right-angles to the fact that a widget consists of many views. Each view in the widget has it's own view-model (which works nicely and logic开发者_开发问答ally) but if I stick to a one view-model per view, I would have to splatter each view-model with user setting backed properties (so I can databind).
A single view-model per view doesn't sound right here if I have to splatter each view-model with user setting properties.
The basic problem here is using Prism to compose sub-views to make the widget - sub-views are too fine-grained.
A widget is a collection of sub-views (user controls) that work together to form a single view e.g. combining a "file picker" and a "grid list". The sub-views should be composed using straightforward Xaml to make a composite view. You still get the re-usability of the individual user controls, but the composition of the widget is fixed at design time.
Now that we have a single view: WidgetView (made of user controls), we can bind that view to a single viewmodel: WidgetViewModel. Handling the settings for the widget view is then handled by composing multiple viewmodels. Simply place a property on WidgetViewModel that exposes a WidgetSettingsViewModel. User controls bind WidgetViewModel for interacting with the underlying model, but bind to WidgetSettingsViewModel for widget settings.
In this way we can have a main viewmodel and a settings viewmodel bound to a widget.
I've found it easiest to bind directly to the application settings, like this:
<CheckBox IsChecked="{Binding SomeSetting,
Source={x:Static myAppProperties:Settings.Default}}" />
This is assuming you are using System.Configuration's application settings feature, as exposed by Visual Studio's ".settings" files. If you're using some other mechanism to save your settings, this technique will still work: Simply expose your user and application settings on a static object and bind to it.
If you have an Options dialog that has lots of application settings, you can simplify things by using the Settings object as your DataContext:
<DockPanel DataContext="{Binding Source={x:Static myAppProperties:Settings.Default}}">
...
<CheckBox IsChecked="{Binding SomeSetting}" />
...
Note that if you do it this way you get your settings saved for free when the application exits, and any change in settings will be instantly reflected in your UI.
I've also found it useful to bind settings to non-UI properties of my objects so the objects will receive PropertyChangedCallback events when application settings change, making their update simple and avoiding cluttering my code with lots of unnecessary event registrations.
Ah, your edit clarifies things enough to warrant a new answer. Here it is:
The whole reason a view model is called a "view model" is because it really is a model. Yes, it ususally doesn't get saved to disk (though it could), but it has all of the other aspects of a model: It stores user data, it is bound from a view, it has no references to the view, etc.
In your case you have a collection of settings that you want to associate with each {user, widget} combination. This collection of settings needs to be persisted and available in each of your views. Conceptually this is a separate model object: It is not a widget, it is not a user, and it is not view-specific. Whether you call it a "view model" or simply a "model" is primarily a matter of terminology. However you want to classify it, let's call the object itself UserWidgetSettings
.
Somewhere you will have a backing store to which you will persist UserWidgetSettings objects. This may be in a local file, in the registry, or in the same database where your User and Widget objects are stored. For sake of discussion let's say you have them stored separately from the User and Widget objects, and have a class that persists them:
public class UserWidgetSettings : DependencyObject // or INotifyPropertyChanged
{
public bool AutoScroll { get { return (bool)GetValue(AutoScrollProperty); } set { SetValue(AutoScrollPropery, value); } }
public static readonly DependencyProperty AutoScrollProperty = DependencyProperty.Register("AutoScroll", typeof(bool), typeof(UserWidgetSettings));
... more settings ...
}
public class UserWidgetSettingsStorage
{
public static readonly UserWidgetSettingsStorage Current = new UserWidgetSettingsStorage();
private Dictionary<Pair<User,Widget>, WeakReference<UserWidgetSettings>> _cache;
public UserWidgetSettings GetSettings(User user, Widget widget)
{
... code to retrieve settings from file, registry, etc ...
}
public void Savechanges()
{
... code to iterate the cache and save back changes to UserWidgetSettings objects ...
... called on Application.OnExit and perhaps other times ...
}
}
Now your ViewModels used by the views simply need a property to access the settings:
public class SomeViewModel
{
public Widget Widget { get; set; }
... regular view model code ...
public UserWidgetSettings UserSettings
{
get
{
return UserWidgetSettingsStorage.Current.GetSettings(
MyApp.CurrentUser, Widget);
}
}
}
In your view, you can use the settings thusly:
<SomeControl AutoScroll="{Binding UserSettings.AutoScroll}" />
And your checkbox to control it looks like this:
<CheckBox IsChecked="{Binding UserSettings.AutoScroll}" />
A side note: I've found that on average only about 20% of my views actually need their own view model. The rest can use common properties exposed by the model itself. If you find yourself creating a separate view model for every single view, something may be wrong. You may want to take a look and see if there are some things you're doing in the view model that would make as much or more sense to do in the model itself. Bottom line: Well-designed model objects can dramatically cut down on the amount of code in a WPF application.
精彩评论