WPF View Data Template Questions
-
I have a MaterialsViewModel. There are 7 different types of materials. I am defining the views->VM's in my resource file like this:
In the VM I set an enum called 'MaterialType' based on the type of material. How do I set up the resource file to select the right view? Thanks
If it's not broken, fix it until it is
-
I have a MaterialsViewModel. There are 7 different types of materials. I am defining the views->VM's in my resource file like this:
In the VM I set an enum called 'MaterialType' based on the type of material. How do I set up the resource file to select the right view? Thanks
If it's not broken, fix it until it is
You are doing it bass akwards IMO :) Remember, one of the mantras of MVVM is that the VM knows nothing about the view. The view obviously knows about the VM its rendering. If you setting up DataTemplates as you have, you are implying that you are creating a VM first and then assigning it a view based on the data type. This is implying that in some VM, you are newing up other random VMs (BAD -- VMs should not know about each other) and making "random decisions" for the VM / View relationship (also BAD). What makes more sense (to me at least lol) is that as a command handler, I want to load a particular view. That view should then know what VM it needs and new it up and wire everything up. As I said in the paragraph above, if I am newing up VMs in code, I have to also new up the window, set the DataContext, etc. This is too much intimate knowledge. What I like to do is just say "I want this particular view to popup": Window logonWindow = LoadComponent(new Uri("Views/LogonWindow.xaml", UriKind.RelativeOrAbsolute)) as Window; logonWindow.ShowDialog(); // or .Show() or whatever... using ViewLocator & DI, Views/LogonWindow.xaml will automatically new up the appropriate VM, set the DataContext, call InitializeComponent(), etc. In answer to your other question... if you have different look & feel, you might break out the two look & feels into two different templates then have a tiny "loader template"... the loader template would just use a DataTrigger to swap out to the other template if needed. So your loader template will by default apply the PC Template and if your global/static IsTablet DP == true, it will apply the Tablet Template instead. This is how custom controls are generally written. You have two styles for example: an XP style and an Aero style. Apply the XP style by default and then swap out for the Aero template if IsAero == true. POH... if you are reading this... wouldn't mind hearing your comments on this approach. To me, this makes more sense then going the other way...
-
You are doing it bass akwards IMO :) Remember, one of the mantras of MVVM is that the VM knows nothing about the view. The view obviously knows about the VM its rendering. If you setting up DataTemplates as you have, you are implying that you are creating a VM first and then assigning it a view based on the data type. This is implying that in some VM, you are newing up other random VMs (BAD -- VMs should not know about each other) and making "random decisions" for the VM / View relationship (also BAD). What makes more sense (to me at least lol) is that as a command handler, I want to load a particular view. That view should then know what VM it needs and new it up and wire everything up. As I said in the paragraph above, if I am newing up VMs in code, I have to also new up the window, set the DataContext, etc. This is too much intimate knowledge. What I like to do is just say "I want this particular view to popup": Window logonWindow = LoadComponent(new Uri("Views/LogonWindow.xaml", UriKind.RelativeOrAbsolute)) as Window; logonWindow.ShowDialog(); // or .Show() or whatever... using ViewLocator & DI, Views/LogonWindow.xaml will automatically new up the appropriate VM, set the DataContext, call InitializeComponent(), etc. In answer to your other question... if you have different look & feel, you might break out the two look & feels into two different templates then have a tiny "loader template"... the loader template would just use a DataTrigger to swap out to the other template if needed. So your loader template will by default apply the PC Template and if your global/static IsTablet DP == true, it will apply the Tablet Template instead. This is how custom controls are generally written. You have two styles for example: an XP style and an Aero style. Apply the XP style by default and then swap out for the Aero template if IsAero == true. POH... if you are reading this... wouldn't mind hearing your comments on this approach. To me, this makes more sense then going the other way...
Where the first approach really makes sense is if you have a HierarchicalDataTemplate that you are applying to a deep hierarchy where a top level VM is composed up of multiple different child VM's. This is a great way to separate out the look and allows you to easily build a complex hierarchy, allowing WPF to do its thing. However, I have to agree that 99% of the time, I would go with the approach you recommend. Indeed, to complement this approach, I'd highly recommend that Kevin needs to investigate MefedMVVM[^].
Chill _Maxxx_
CodeStash - Online Snippet Management | My blog | MoXAML PowerToys | Mole 2010 - debugging made easier -
I have a MaterialsViewModel. There are 7 different types of materials. I am defining the views->VM's in my resource file like this:
In the VM I set an enum called 'MaterialType' based on the type of material. How do I set up the resource file to select the right view? Thanks
If it's not broken, fix it until it is
You can write custom DataTemplateSelector and then use it in e.g. ContentControl as a ContentTemplateSelector. For example the following selector looks for desired datatemplate in control's resources by MaterialType:
public class MaterialDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
MaterialsViewModel materials = item as MaterialsViewModel;string resKey = "materialsTemplateFor" + Enum.GetName(typeof(MaterialType), materials.MaterialType); return element.FindResource(resKey) as DataTemplate; }
}
Here are the resources you need in your MaterialsView control:
And this is how you use it in your control:
Hope it helps.
Gabriel Szabo
-
You can write custom DataTemplateSelector and then use it in e.g. ContentControl as a ContentTemplateSelector. For example the following selector looks for desired datatemplate in control's resources by MaterialType:
public class MaterialDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
MaterialsViewModel materials = item as MaterialsViewModel;string resKey = "materialsTemplateFor" + Enum.GetName(typeof(MaterialType), materials.MaterialType); return element.FindResource(resKey) as DataTemplate; }
}
Here are the resources you need in your MaterialsView control:
And this is how you use it in your control:
Hope it helps.
Gabriel Szabo
Your solution is technically "correct", however, it is doing it the hard way. For a full blown app, you'd have to write hundreds of these template selectors. Too much work :).
-
You are doing it bass akwards IMO :) Remember, one of the mantras of MVVM is that the VM knows nothing about the view. The view obviously knows about the VM its rendering. If you setting up DataTemplates as you have, you are implying that you are creating a VM first and then assigning it a view based on the data type. This is implying that in some VM, you are newing up other random VMs (BAD -- VMs should not know about each other) and making "random decisions" for the VM / View relationship (also BAD). What makes more sense (to me at least lol) is that as a command handler, I want to load a particular view. That view should then know what VM it needs and new it up and wire everything up. As I said in the paragraph above, if I am newing up VMs in code, I have to also new up the window, set the DataContext, etc. This is too much intimate knowledge. What I like to do is just say "I want this particular view to popup": Window logonWindow = LoadComponent(new Uri("Views/LogonWindow.xaml", UriKind.RelativeOrAbsolute)) as Window; logonWindow.ShowDialog(); // or .Show() or whatever... using ViewLocator & DI, Views/LogonWindow.xaml will automatically new up the appropriate VM, set the DataContext, call InitializeComponent(), etc. In answer to your other question... if you have different look & feel, you might break out the two look & feels into two different templates then have a tiny "loader template"... the loader template would just use a DataTrigger to swap out to the other template if needed. So your loader template will by default apply the PC Template and if your global/static IsTablet DP == true, it will apply the Tablet Template instead. This is how custom controls are generally written. You have two styles for example: an XP style and an Aero style. Apply the XP style by default and then swap out for the Aero template if IsAero == true. POH... if you are reading this... wouldn't mind hearing your comments on this approach. To me, this makes more sense then going the other way...
Interesting - and pls. excuse me from hijacking the thread. I'd always thought differently; Use does something on a view (clicks a button, selects a row on a gird, whatever) The ViewModel corresponding to the view has a command executed (e.g. UserSelectedACustomerCommand) ViewModel now knows that, as a result of that command, the ShowCustomerViewModel needs to be shown Whether through DI, hard coding, via a Controller or magic, the VM instntiates a ShowCustomerViewModel and tells it it needs to 'do its stuff' 'Doing its stuff' involves (again, 'somehow', the tech isn't too important) instantiating the View and setting its DataContext. The idea being that except for the actual button click (or whatever) everything else is VM-based - because we're modelling the view. If I understand you above post correctly, you are saying that the click on teh View is telling the View to show the CustomerDetailView - but isn't it better to keep views entirely separate from one another, as the VM is a model - the View is just the eye candy? Am I missing something, misreading your post or just plain wrong? ;)
MVVM # - I did it My Way ___________________________________________ Man, you're a god. - walterhevedeich 26/05/2011 .\\axxx (That's an 'M')
-
Your solution is technically "correct", however, it is doing it the hard way. For a full blown app, you'd have to write hundreds of these template selectors. Too much work :).
The template selector can be made more general. Data templates and material type could be e.g. injected from the hosting xaml. So you don't really need write that many selectors ;). I see the selector and your loader template approach philosophically equivalent. However there is a difference from the testing point of view. A unit test for selector is pretty straightforward. But how would you test the data triggers in loader template?
Gabriel Szabo
-
The template selector can be made more general. Data templates and material type could be e.g. injected from the hosting xaml. So you don't really need write that many selectors ;). I see the selector and your loader template approach philosophically equivalent. However there is a difference from the testing point of view. A unit test for selector is pretty straightforward. But how would you test the data triggers in loader template?
Gabriel Szabo
Yeah, same thing technically like I said. You'd have to write a template selector for every data type / decision. If you are just deciding whether to run the PC or tablet view, then yeah, you'd just need one selector. You'd have to copy & paste more XAML around though :). Let's be honest about unit tests in the real world... nice concept, but very few companies in this day and age want to invest in that. Often times lots of unit tests are a huge hinderance. I worked at a place that did invest in lots of unit tests and lots of bugs got through because there was no / little manual testing and the mgr believed he could get 100% coverage by getting you to do lots of unit tests. Lots of companies are realizing that they can hire 50 QA guys in india to click buttons vs. hiring one software dev onshore to write unit tests and end up with better quality software the manual way cuz there are more eyes on the product and the devs spend more time writing / testing new features vs. writing unit tests. Does your place do automated testing? Is all your code covered by unit tests? Do you have mock services in place to test all your dialog return code paths? :).
-
Interesting - and pls. excuse me from hijacking the thread. I'd always thought differently; Use does something on a view (clicks a button, selects a row on a gird, whatever) The ViewModel corresponding to the view has a command executed (e.g. UserSelectedACustomerCommand) ViewModel now knows that, as a result of that command, the ShowCustomerViewModel needs to be shown Whether through DI, hard coding, via a Controller or magic, the VM instntiates a ShowCustomerViewModel and tells it it needs to 'do its stuff' 'Doing its stuff' involves (again, 'somehow', the tech isn't too important) instantiating the View and setting its DataContext. The idea being that except for the actual button click (or whatever) everything else is VM-based - because we're modelling the view. If I understand you above post correctly, you are saying that the click on teh View is telling the View to show the CustomerDetailView - but isn't it better to keep views entirely separate from one another, as the VM is a model - the View is just the eye candy? Am I missing something, misreading your post or just plain wrong? ;)
MVVM # - I did it My Way ___________________________________________ Man, you're a god. - walterhevedeich 26/05/2011 .\\axxx (That's an 'M')
_Maxxx_ wrote:
ViewModel now knows that, as a result of that command, the ShowCustomerViewModel needs to be shown
Whether through DI, hard coding, via a Controller or magic, the VM instntiates a ShowCustomerViewModel and tells it it needs to 'do its stuff'That makes no sense to me lol. VMs are not shown, Views are.
_Maxxx_ wrote:
If I understand you above post correctly, you are saying that the click on teh View is telling the View to show the CustomerDetailView - but isn't it better to keep views entirely separate from one another, as the VM is a model - the View is just the eye candy?
Slight misread. The view doesn't do anything. It doesn't have any code behind. In fact, I always delete the .cs code behind file just so some douche won't come around and stick stuff in there :). Clicking on a button in the view executes a command in the VM. The VM knows it needs to show a dialog or window or slide something into view via an animation, or whatever. Doing it the way you suggested means VM A knows about VM B and intimately knows VM B wants View B attached to it, the DataContext set, etc. Going the way I suggested is more consistent with how the web works. I don't do web stuff, so its kind of ironic... but on the web, you redirect to the .html (the "view"), not the .cs (the "view model").
-
_Maxxx_ wrote:
ViewModel now knows that, as a result of that command, the ShowCustomerViewModel needs to be shown
Whether through DI, hard coding, via a Controller or magic, the VM instntiates a ShowCustomerViewModel and tells it it needs to 'do its stuff'That makes no sense to me lol. VMs are not shown, Views are.
_Maxxx_ wrote:
If I understand you above post correctly, you are saying that the click on teh View is telling the View to show the CustomerDetailView - but isn't it better to keep views entirely separate from one another, as the VM is a model - the View is just the eye candy?
Slight misread. The view doesn't do anything. It doesn't have any code behind. In fact, I always delete the .cs code behind file just so some douche won't come around and stick stuff in there :). Clicking on a button in the view executes a command in the VM. The VM knows it needs to show a dialog or window or slide something into view via an animation, or whatever. Doing it the way you suggested means VM A knows about VM B and intimately knows VM B wants View B attached to it, the DataContext set, etc. Going the way I suggested is more consistent with how the web works. I don't do web stuff, so its kind of ironic... but on the web, you redirect to the .html (the "view"), not the .cs (the "view model").
I'm curious, how do you handle a double click event on a grid for instance?
Never underestimate the power of human stupidity RAH
-
_Maxxx_ wrote:
ViewModel now knows that, as a result of that command, the ShowCustomerViewModel needs to be shown
Whether through DI, hard coding, via a Controller or magic, the VM instntiates a ShowCustomerViewModel and tells it it needs to 'do its stuff'That makes no sense to me lol. VMs are not shown, Views are.
_Maxxx_ wrote:
If I understand you above post correctly, you are saying that the click on teh View is telling the View to show the CustomerDetailView - but isn't it better to keep views entirely separate from one another, as the VM is a model - the View is just the eye candy?
Slight misread. The view doesn't do anything. It doesn't have any code behind. In fact, I always delete the .cs code behind file just so some douche won't come around and stick stuff in there :). Clicking on a button in the view executes a command in the VM. The VM knows it needs to show a dialog or window or slide something into view via an animation, or whatever. Doing it the way you suggested means VM A knows about VM B and intimately knows VM B wants View B attached to it, the DataContext set, etc. Going the way I suggested is more consistent with how the web works. I don't do web stuff, so its kind of ironic... but on the web, you redirect to the .html (the "view"), not the .cs (the "view model").
SledgeHammer01 wrote:
The VM knows it needs to show a dialog or window or slide something into view via an animation, or whatever.
That's where I differ. The VM (in my world) only knows that another VM needs to execute - everything works (in principal) even if there is no view - the VM has no right to know that a view is to be shown modally, slid in or animated - that's the View's responsibility. I keep ALL GUI stuff out of the Views
SledgeHammer01 wrote:
Doing it the way you suggested means VM A knows about VM B and intimately knows VM B wants View B attached to it, the DataContext set, etc.
Actually I use a controller to communicate between View Models - so generally one VM doesn't know about another. e.g. User clicks button on View1 to edit selected customer. Command "Edit Custeomr" on VM1 executes - it knows which is the selected customer by binding. VM1 sends message "Customer Selected For Edit" Controller is subscribed to message - receives it, sources the relevant VM (the 'edit customer' VM) (and possibly checks to see if there already is one editing that customer) and instantiates it (and its associated View.
SledgeHammer01 wrote:
Going the way I suggested is more consistent with how the web works. I don't do web stuff, so its kind of ironic... but on the web, you redirect to the .html (the "view"), not the .cs (the "view model").
The way the web works isn't necessarily a 'good' way, though :) And on the web, the View is the HTML and the VM is more accurately Javascript - the .cs the controller/model It is interesting that there are so many different ways to implement similar patterns.
MVVM # - I did it My Way ___________________________________________ Man, you're a god. - walterhevedeich 26/05/2011 .\\axxx (That's an 'M')
-
I'm curious, how do you handle a double click event on a grid for instance?
Never underestimate the power of human stupidity RAH
Personally I rely on individual developers to be sensible. So the code behind is used for GUI stuff - why have 50 lines of XAML and associated converters etc. etc. when three lines of code behind will do the same job? I use code behind for things like grid double-clicks and sometimes for focus positiioning (we have some views where the focus bounces around the screen like a mad thing, the next focus depending upon various business rules - the code behind accesses properties on the Vm to determine what to focus next - but (most importantly in my book_) the properties on the VM are business properties not GUI properties; e.g. Property PriceIsGreaterThanThreshold not property NextControlToFocus
MVVM # - I did it My Way ___________________________________________ Man, you're a god. - walterhevedeich 26/05/2011 .\\axxx (That's an 'M')
-
Personally I rely on individual developers to be sensible. So the code behind is used for GUI stuff - why have 50 lines of XAML and associated converters etc. etc. when three lines of code behind will do the same job? I use code behind for things like grid double-clicks and sometimes for focus positiioning (we have some views where the focus bounces around the screen like a mad thing, the next focus depending upon various business rules - the code behind accesses properties on the Vm to determine what to focus next - but (most importantly in my book_) the properties on the VM are business properties not GUI properties; e.g. Property PriceIsGreaterThanThreshold not property NextControlToFocus
MVVM # - I did it My Way ___________________________________________ Man, you're a god. - walterhevedeich 26/05/2011 .\\axxx (That's an 'M')
I tend to be the same, I'm not religious about anything. Almost the only code behind is a navigate to/from and the double click event, If I could get a nice clean SMALL solution to the double click in either xaml or the VM I'd be well pleased. I use Page instead of UserControl so I have the opportunity to rest a VM if the user navigates away from the view.
Never underestimate the power of human stupidity RAH
-
I'm curious, how do you handle a double click event on a grid for instance?
Never underestimate the power of human stupidity RAH
I use the Blend SDK interactivity + a pretty standard event to command mapper. Ends up being 1 line added to the XAML namespaces, 2 or 3 lines added to each control I want to trap events for + 1 line for each event. No big deal. Pretty standard way to handle events in MVVM... convert 'em to a command. Thats the POH & Sascha way anyways :).
-
Personally I rely on individual developers to be sensible. So the code behind is used for GUI stuff - why have 50 lines of XAML and associated converters etc. etc. when three lines of code behind will do the same job? I use code behind for things like grid double-clicks and sometimes for focus positiioning (we have some views where the focus bounces around the screen like a mad thing, the next focus depending upon various business rules - the code behind accesses properties on the Vm to determine what to focus next - but (most importantly in my book_) the properties on the VM are business properties not GUI properties; e.g. Property PriceIsGreaterThanThreshold not property NextControlToFocus
MVVM # - I did it My Way ___________________________________________ Man, you're a god. - walterhevedeich 26/05/2011 .\\axxx (That's an 'M')
It's 3 lines of XAML too using the Blend SDK and event to command. I disagree about what you claim a VM is for. It's to package data and support the view. GUI properties are allowed as are business rules. If I have a complex UI where its starting to be too many properties, it needs to be refactored. Into a user control or multiple user controls perhaps. Controlling the focus is not something I've had to implement from the VM / MVVM world, so I can't say off the top of my head how I'd approach it, but I certainly wouldn't use code behind. I'd probably make it a UserControl. FYI, code behind is allowed in user controls, it's not a good move for a view. For one thing, you split the code for the view into 2 places. Not good.