WPF MVVM: Convention over Configuration for ResourceDictionary? - wpf

Update
In the wiki spirit of StackOverflow, here's an update:
I spiked Joe White's IValueConverter suggestion below. It works like a charm.
I've written a "quickstart" example of this that automates the mapping of ViewModels->Views using some cheap string replacement. If no View is found to represent the ViewModel, it defaults to an "Under Construction" page. I'm dubbing this approach "WPF MVVM White" since it was Joe White's idea. Here are a couple screenshots.
The first image is a case of "[SomeControlName]ViewModel" has a corresponding "[SomeControlName]View", based on pure naming convention. The second is a case where the ModelView doesn't have any views to represent it. No more ResourceDictionaries with long ViewModel to View mappings. It's pure naming convention now.
I posted a download of the project here:
Mvvm.White.Quickstart.zip
Original Post
I read Josh Smith's fantastic MSDN article on WPF MVVM over the weekend. It's destined to be a cult classic.
It took me a while to wrap my head around the magic of asking WPF to render the ViewModel.
It's like saying "Here's a class, WPF. Go figure out which UI to use to present it."
For those who missed this magic, WPF can do this by looking up the View for ModelView in the ResourceDictionary mapping and pulling out the corresponding View. (Scroll down to Figure 10 Supplying a View ).
The first thing that jumps out at me immediately is that there's already a strong naming convention of:
classNameView ("View" suffix)
classNameViewModel ("ViewModel" suffix)
My question is:
Since the ResourceDictionary can be manipulated programatically, I"m wondering if anyone has managed to Regex.Replace the whole thing away, so the lookup is automatic, and any new View/ViewModels get resolved by virtue of their naming convention?
[Edit] What I'm imagining is a hook/interception into ResourceDictionary.
... Also considering a method at startup that uses interop to pull out *View$ and *ViewModel$ class names to build the DataTemplate dictionary in code:
//build list
foreach ....
String.Format("<DataTemplate DataType=\"{x:Type vm:{0} }\"><v:{1} /></DataTemplate>", ...)

Rather than writing code to explicitly add things to the ResourceDictionary, how about just generating the right view on demand? You can do this with a ValueConverter.
Your resources would look like this:
<Views:ConventionOverConfigurationConverter x:Key="MyConverter"/>
<DataTemplate DataType="{x:Type ViewModels:ViewModelBase}">
<ContentControl Content="{Binding Converter={StaticResource MyConverter}}"/>
</DataTemplate>
You still need a DataTemplate resource, but as long as your ViewModels all have a common base class, you'll only need one DataTemplate to take care of all of them.
Then define the value converter class:
public class ConventionOverConfigurationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
// value is the ViewModel. Based on its GetType(), build a string
// with the namespace-qualified name of the view class, then:
return Activator.CreateInstance(Type.GetType(viewName));
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
All you'd need to do is write the logic inside Convert, which will depend on things like whether your Views and ViewModels are in the same namespace or not.

I decided to do pretty much hthe same thing so I load my DataTemplates directly into the ResourceDictionary using
private void RegisterResources()
{
ResourceDictionary dictionary = new ResourceDictionary();
dictionary.Source = new Uri("pack://application:,,,/StartupModule;Component/UIResources.xaml");
Application.Current.Resources.MergedDictionaries.Add(dictionary);
}
where the UIResources file is a ResourceDictionary xamls file containing all of our DataTemplates

Related

Best Approach of setting the Visibility in MVVM

In my View I have three objects, of which one is Visible at any given time. In my Model I have an enumeration to represent the three states.
How should I implement my ViewModel?
a) Create a boolean for the visibility of each object, and bind each object to this (with a bool->visibility converter).
b) Bind to the enum, with a unique converter for each object.
c) Bind to the enum, with a single converter that takes a parameter.
d) Use a visual state manager with boolean key frames, and drive the state from VM with an attached property.
e) Bind to the VM enum from code behind, and set visibility thru code.
f) ?
I am seriously hoping the answer is f) (ie the obvious choice that escapes me), because I am not really overjoyed with a) through e).
Thoughts welcome and appreciated.
The Best approach in MVVM does not necessarily mean easy. I like the following approaches:
a) Create a boolean for the visibility of each object, and bind each object to this (with a bool->visibility converter).
This method is the most intuitive and classically for setting Visibility for Control.
b) Bind to the enum, with a unique converter for each object.
c) Bind to the enum, with a single converter that takes a parameter.
In the case of the Converter, Enum is the best keep not in the Model and in the side of View. Because the problem solves over to the side of View, which is quite logical and here to store the data structure. In principle, it is not critical.
Example:
public sealed class InvertableBooleanToVisibilityConverter : IValueConverter
{
enum Parameters
{
Normal,
Inverted
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var boolValue = (bool)value;
var direction = (Parameters)Enum.Parse(typeof(Parameters), (string)parameter);
if (direction == Parameters.Inverted)
return !boolValue ? Visibility.Visible : Visibility.Collapsed;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
A couple of comments about other approaches:
d) Use a visual state manager with boolean key frames, and drive the state from VM with an attached property.
For these situations, it looks complicated, so do not see the point in it. But, if the conditions of setting Visibility are not difficult, you can use VisualStateManager.
e) Bind to the VM enum from code behind, and set visibility thru code.
Code-behind in this case is not justified when you can solve the problem using typical tools of MVVM (Binding, Converters, etc). I think, in this case it would not be a violation of the principle of MVVM, if choosing the element to the Visibility is not involved business logic, such as may come setting Visibility by pressing of CheckBox, ToggleButton, etc.
c) Bind to the enum, with a single converter that takes a parameter.
This way I solved my problem with radio buttons where only one of the radio buttons is selected. Seem harder but you can reuse it later and if you get the idea it is very easy.
e) Bind to the VM enum from code behind, and set visibility thru code.
Bad idea. That's not a good practice.
in addition to my comment. when i have the task to show diffenrent content in a view depending on different objects. i use most time a contentcontrol and datatemplates.
<ContentControl Content="{Binding MyEnumObject}"/>
now in my viewmodel i have all the diffrent objects types and set the object i want to my Property bind to the view.
private Blup _blup; //eg. is wanted when enum in model = 1
public object MyEnumObject {get;set;}
//set the content in your viewmodel
this.MyEnumObject = _blup;
now you just need a datatemplate to visualize your objects
<DataTemplate DataType="{x:Type local:Blup}">
<view:MyBlupViewControl/>
</DataTemplate>
but maybe i m totally wrong. but your actuall question let so much room for interpretation

WPF / MVVM Design Suggestions

I'm pretty new to WPF, and am looking for some guidance here.
I'm working on an application that will be used to print out work orders for our fulfillment department.
Right now I have 2 windows: The first is the main screen, the second is a window with a gridview that will hold the work orders to print.
The first page will have several buttons on there. Every button will open up the second window; however, depending on which button is clicked, the parameters passed into the service that will load data will be different.
What would be the best practices way of doing this?
Is there way to define these parameters somewhere on the Button control, and then pass them through via ICommand/RelayCommand?
Should I create a UserControl/ServerControl that will let me build in these additional properties?
Something else I'm not thinking of?
Edit:
To give a rough example (and this is very oversimplified}, say i have 2 sets of criteria: OrderTypes: {Rush, Today, Future} and Locations {Warehouse 1, Warehouse 2, Warehouse 3}
The main window would have a 3x3 grid of buttons, one for each combination. I'd like to be able to specify on a single button "Expedite & Warehouse 1"; and then pass those parameters back to a single method, which would open the second window.
Lets say you have MainWindow and buttons are placed in it.
Create a MainWindowViewModel and set it as DataContext for MainWindow.
Have an ICommand on your ViewModel and bind button Command with this ICommand so that entry point for opening another window will be single. For ICommand you can use either RelayCommand or DelegateCommand whichever suits you best.
Now, comes the point where you need to open window and pass on parameter to it based on button type click. I would suggest to have Enum depicting action need to perform based on different buttons.
Enum
public enum ActionType
{
Action1,
Action2,
Action3 and so on...
}
And bind from button like this:
<Button Command="{Binding CommandInstance}"
CommandParameter="{x:Type local:ActionType.Action1}"/>
<Button Command="{Binding CommandInstance}"
CommandParameter="{x:Type local:ActionType.Action2}"/>
where local will be namespace where enum is declare.
And in command execute delegate pass the enum value to another window constructor:
private void CommandMethod(ActionType action)
{
AnotherWindow anotherWindow = new AnotherWindow(action);
anotherWindow.Show();
}
and from action passed in constructor, you can check what parameter need to pass to service responsible for loading data.
Also, in case creating window from ViewModel doesn't seems right you can have Service wrapper over window Controls which is responsible for showing/closing window.
UPDATE
Since you want to pass multiple parameters from Views so maintaining enum for this will be cumbersome. You can pass multiple values from View using IMultiValueConverter.
Let me explain with small example:
<Button Command="{Binding TestCommand}">
<Button.Resources>
<sys:String x:Key="Rush">Rush</sys:String>
<sys:String x:Key="Warehouse">Warehouse</sys:String>
</Button.Resources>
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiValuesReturnConverter}">
<Binding Source="{StaticResource Rush}"/>
<Binding Source="{StaticResource Warehouse}"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
where sys will be namespace for System in XAML:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
So, now you have liberty in XAML to pass many objects from XAML to your command parameter. All you have to do is to declare the resource under Button resources section and pass it as binding to converter.
Converter code to convert it into list of parameters which can be passed to command as a single parameter object:
public class MultiValuesReturnConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
return values.ToList<object>();
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
Command Method:
private void CommandMethod(object parameter)
{
// Now you have all the parameters here to take decision.
IList<object> values = parameter as List<object>;
AnotherWindow anotherWindow = new AnotherWindow(action);
anotherWindow.Show();
}
If you don't want to use some third party library, there really is no problem in simply passing the parameters through a click event into your other window's constructor. If your data is represented by a viewmodel you may also pass that viewmodel instead of the parameters themselves.
A point of MVVM is not "no code-behind". Many times you will end up without code-behind, but trying to develop applications this way leads you into convoluted anti-patterns that often are more work and more lines of code than simple click events and "the old way".
Treat your data as data, try to do all your work in testable viewmodels and never follow a pattern too rigidly lest you end up with mounds of unreadable abstractions.
Before detailing any thing, I would advice you to use the third party library MVVMLight, it has many helpful features such as Messenger, its own RelayCommands etc ...
For passing parameters from a button to consume them in Commands, you can use the Tag property if you want to pass a parameter regardless of the type of the event, if you want to pass a parameter that is related to a certain Command(event) then CommandParameter is what you need :
Tag : Gets or sets an arbitrary object value that can be used to store custom information about this element. (Inherited from FrameworkElement.)
CommandParameter :
<Button Content="Parameterized Command"
Command="{Binding ParameterizedCommand}" CommandParameter={Binding SomeObject} />
I don't think at the level of your question that you need to create a UserControl unless you have more complicated scenarios.
You can use the Messenger class to pass information from ViewModel to another (this just a helper it's independent from the MVVM Pattern).
The MVVMLight has code templates that help you create ViewModels with ease.
The MVVMLight has many helpful snippets which you will find helpful.
Beware of Commands because they are not originally supported with all UIElements, they are only available with ButtonBase and its children and they work only to replace the Click event, to use Commands and CommandParameters with other UIElements and with other kinds of events you should use a sort of EventToCommand behaviours, MVVMLight has got that already ready for you
Hope I covered most important parts you may need.
The most easy and intuitive way (Using INotifyPropertyChanged to update the UI instead of DependencyProperty):
You create a property that'll be your DataContext for your OrderViewModel in MainWindowViewModel
class MainWindowViewModel : ViewModelBase // ViewModelBase should implement INotifyPropertychanged, unless you're using dependency properties
{
private OrderViewModel _OrderViewModelInstance;
public OrderViewModel OrderViewModelInstance
{
get{ return _OrderViewModel;}
set { _OrderViewModel = value;
OnPropertyChanged("OrderViewModel")} // Method from ViewModelBase
}
Now, whichever way You're creating Your Order View:
You instantiate OrderViewModel in MainWindowViewModel (Let's say when a button gets clicked) with desired parameters.
you bind the Order view's DataContext to OrderViewModelInstance in XAML. You might want to create an additional variable that tells you when the window is visible.

Using MvxVisibilityValueConverter in WPF

I'm just getting started with MVVMCross, so forgive me if this seems like a simple question. I'm trying use the MVVMCross Visibility plugin in WPF, mentioned here:
https://github.com/MvvmCross/MvvmCross/wiki/Value-Converters
I installed the plugin, and am trying to follow these steps:
Windows - use Native wrappers or Tibet Binding as described above:
Visibility="{Binding VMProperty, Converter={StaticResource
Visibility}}"
When I try to do so, it can't find the resource "Visibility."
So I figured, I can add the namespace:
xmlns:visibility="clr-namespace:Cirrious.MvvmCross.Plugins.Visibility;assembly=Cirrious.MvvmCross.Plugins.Visibility"
...and then add the converter to my resources:
<visibility:MvxVisibilityValueConverter x:Key="Visibility"></visibility:MvxVisibilityValueConverter>
...but now I get:
An object of the type "Cirrious.MvvmCross.Plugins.Visibility.MvxVisibilityValueConverter" cannot be applied to a property that expects the type "System.Windows.Data.IValueConverter".
Do I have to make my own Converter for this, like this:
class MyVisibilityConverter : MvxNativeValueConverter<MvxVisibilityValueConverter>
{
}
...or am I missing something? The docs seem to indicate there's less work involved.
IValueConverter isn't currently a portable interface, and this was a deliberate decision from Microsoft. I've talked to one of the guys from the PCL team about this - he seemed very clear that they expected most value converters to be platform specific and so not to sit in shared code.
Because of this - and because MvvmCross believes many value converters will be shared - we had to introduce our own IMvxValueConverter interface inside MvvmCross. This IMvx interface can't be used directly by XAML and the Microsoft bindings - so that's the reason you need the "native" wrapping currently.
You can work around this - if you want to - by using the MvvmCross "Tibet" binding framework instead of the Microsoft one, but I think most MS-based devs are still using the MS-binding.
am I missing something? The docs seem to indicate there's less work involved.
For using value converters on Windows, the wiki says the text below - if you think this can be improved, please do contribute changes back - we're keen to keep on improving.
Using Value Converters in Windows (conventional Xaml binding)
The IMvxValueConverter interface is closely based on the IValueConverter interface used in Windows WPF and Silverlight Xaml binding. This interface is also similar (but slightly different) to the IValueConverter interface used in Windows WinRT Xaml binding.
Because these Xaml IValueConverter interfaces are not 100% identical to each other, nor to the IMvxValueConverter version, shared Mvx ValueConverters cannot be used directly in Windows Xaml binding - they must instead be wrapped for use in Xaml.
The steps to do this are similar on each Windows platform:
for each IMvxValueConverter class, e.g. for
public class TheTruthValueConverter
: MvxValueConverter<bool, string>
{
public string Convert(bool value, Type targetType, CultureInfo cultureInfo, object parameter)
{
return value ? "Yay" : "Nay";
}
}
in your UI project, create a 'native' wrapper using the MvxNativeValueConverter class:
public class TheNativeTruthValueConverter
: MvxNativeValueConverter<TheTruthValueConverter>
{
}
in your Xaml, include an instance of your ValueConverter as a static resource - this can be done in the Resources at App, Page or Control Xaml level, e.g.:
<converters:TheNativeTruthValueConverter x:Key="TheTruth" />
now your converter can be used - e.g.:
<TextBlock Text="{Binding HasAccepted, Converter={StaticResource TheTruth}}" />
Using Value Converters in Windows (Tibet binding)
In addition to 'traditional' Xaml bindings, MvvmCross also allows 'Tibet' binding within Windows - for more on this see wiki/Databinding.
When Tibet binding is used, then Value Converters can be accessed by name - exactly as in Droid and Touch binding - without the above native Xaml wrapping.
Further, if using 'Tibet' binding then an entire assembly's worth of value converters can be registered using the Reflection sweep technique and this can be specified at the Xaml level - meaning it can be used in both design and run-time.
To include all value converters within an Assembly at the Xaml level, then use an mvx:Import block with an inner From attribute which contains an instance of a class from that Assembly.
This may sound complicated… but actually it is quite simple.
Suppose you have an Assembly MyTools containing FooValueConverter, BarValueConverter, etc
Within this Assembly add a simple, instanciable public Class which we will use only for the import - e.g. public class MarkerClass {}
Then within the xaml, you can include a static resource import block like:
<mvx:Import x:Key="MvxAssemblyImport0">
<mvx:Import.From>
<myTools:MarkerClass />
<mvx:Import.From>
</mvx:Import>
After this is done, then the ValueConverters Foo and Bar will be available for use within 'Tibet' bindings - e.g. as:
<TextBlock mvx:Bi.nd="Text Foo(Name)" />

Master/Detail in separate viewmodels?

I am designing a master/detail view. Currently, I have a user control (detail) in my main view, and both have thier own vm. On the one hand, I think there should only be one vm because the detail will never exist without the master. It would also be easier to handle the CRUD process in one vm because of their tight dependency on one another. On the other hand, they are separate entities, and having two smaller vm vs one large one seems more manageable. Any thoughts?
For the given scenario I would've created two different views and bound it to the same viewmodel.
If you lazy load stuff from a database, it might be cleaner to implement two viewmodels
.
The view model is the model of the view. If you have two views, each has a view model. If the views are interdependent, the models will be too.
The wisdom of having a separate view model for detail items becomes apparent as the complexity of your detail items grows. For a simple example, imagine a hyperlink presenting a command in the detail view, which should be enabled if the detail item meets some kind of criteria. Where are you going to put the source of the hyperlink's command binding?
One of my apps has this scenario. I have a master ListView with items and a detail view with extended information about the selected one. Detail view has its own view model.
I binded the detail view DataContext with the SelectedItem from the master by using a Converter:
<view:MyDetailView Grid.Row="2"
DataContext="{Binding Path=SelectedItem, ElementName=masterList, Converter={StaticResource EntityToDetailViewModelConverter}}">
</view:FontDetailView>
And the converter,
class EntityToDetailViewModelConverter: System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
MyEntity entity = value as MyEntity;
return new ViewModel.MyDetailViewModel(entity);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

WPF+MVVM: How to display VM via resource dictionary

At the moment I am creating new instance of both View and ViewModel and assign view.DataContext = vm in the application in the handler of 'Startup' application event
In the http://msdn.microsoft.com/en-us/magazine/dd419663.aspx in the "Applying a View to a ViewModel" chapter I've read that it would be a good idea to bind ViewModel object to view via resources.
Am I correctly understand that using suggested approach I should:
Create a resource in the "MainPage" that will have a "DataTemplate" section for each pair of View/ViewModel;
Bind instance of the ViewModel object to the MainPage?
Am I right?
P.S. Actually, I've tried to do that but got few issues and want to know if I am going by the proper way. If no, please point me how that should be done in the right way.
How this technique works is that instead of finding and creating views directly let wpf find the view through data templates. so when you have the following in your application resources. This drives the UI based on what ViewModel you want to display and don't have to worry about coding up the view.
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<ui:MyView />
</DataTemplate>
Note: vm: and ui: are just xml namespaces declared in the top element of the resource file.
you can then just create a generic window that will 'find' the view via a ContentControl
<Window ...>
<ContentControl Content="{Binding}" />
</Window>
var window = new Window();
window.DataContext = new MyViewModel();
window.Show();
This will display the window embedding MyView as the content of the window. Provided you have your bindings set in your view pointing to properties in your viewmodel the wire up succeed. No need to 'new' up a view. The main window can be reused simply by reassigning a different view model to the data context.
Also if you let us know what specific issues you are having we will be able to provide a more specific answer if the above is not what you are looking for.
HTH
I used to do a key/value pair for all of my ViewModel/View like aqwert suggests, but once you get a couple dozen,or more than one :), ViewModels it starts getting pretty tedious and prone to typos.
I personally like an IValueConverter doing the work for me and using Convention for the location of the View.
For example let's say I have my view models in namespace MyApp.ViewModels
and all of my Views in namespace MyApp.Views
and I have a suffix of ViewModel behind all of my VMs and a suffix of View behind all of my Views
All I have to do is:
1) Have all of my ViewModels inherit from a base class ViewModelBase
2) Put this in my application resource dictionary
<m:ViewModelConverter x:Key="ViewModelConverter"/>
<DataTemplate DataType="{x:Type vm:ViewModelBase}">
<ContentControl Content="{Binding Converter={StaticResource ViewModelConverter}}"/>
</DataTemplate>
3) Create my converter, the following is just an example, you can modify to meet your convention.
public class ViewModelConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
Type ViewModelType = value.GetType();
string ViewNameSpace = ViewModelType.Namespace.Replace("ViewModel", "View");
string ClassName = ViewModelType.Name.Replace("Model", string.Empty);
Type ViewType = Type.GetType(string.Format("{0}.{1}", ViewNameSpace, ClassName));
if (ViewType != null)
return Activator.CreateInstance(ViewType);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
The above will try and find the view, if it doesn't it will just return the ViewModel that it was trying to convert (which WPF will just call .ToString() on)
You don't have to worry about actual wiring of ViewModel to the View's DataContext because WPF does that automatically.
And then I'm done. I don't have to touch my resource file any more. :)

Resources