On a project I have started using Caliburn.Micro.
Now I want to split a large UserControl into several smaller UserControls, so that I can use them inside of DataTemplates. EisenbergEffect suggests to do so in this answer
The ViewModels for those are already there, as I modelled the hierachical data as ObservableCollection<SubViewModel>.
Now I thought I just have to create the suiting Views as UserControls for those SubViewModels.
My view is a Master-Details view. I want to present a list of Computers and in the Details View I want to have the HardwareComponents of those.
<ListView x:Name="ComputerViewModels">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="HardwareComponentViewModel" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have included Debug output as suggested here: Debug Logging
And I do not get any output about my HardwareComponentViewModel except
Action Convention Not Applied: No actionable element for set_HardwareComponentViewModel
The HardwareComponentViewModels already get created by the time the ComputerViewModels get created.
I already use the ContentControl - way on other parts of the application and it works very well there (getting the Content using IOC injected into the outer ViewModel).
But it does not seem to fit for the DataTemplate part (which is not very surprising, though). The SubViewModels are derived from Screen, as the outer ViewModels.
How can I use the new UserControl as DataTemplate?
Take a close look at EisenbergEffect's answer. It's explicitly stated, that conventions don't work inside DataTemplate, so if you have a complicated template, it's better to move it out to a separate UserControl, inside which conventions will work again.
That means you have to explicitly bind the model inside the template:
<ListView x:Name="ComputerViewModels">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding HardwareComponentViewModel}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
That should do it. Either that or cal:Model.Bind="{...}", I can never remember the difference and use case. Now inside the bound UserControl (HardwareComponentView, I presume) the conventions between view and the view model should work as usual.
Related
I'm trying to create a memory game while strictly following the MVVM pattern to learn how to use it. Now I have a problem with creating views at run time.
I've created the following project structure:
Model project
-MemoryCardModel30
-Card
ViewModel project
-MainWindowViewModel
-CardViewModel
View project
-CardView
StartApplication project
-MainWindowView
The dependencies are as follows: StartApplication project -> View project -> ViewModel project -> Model project
After clicking a button on the MainWindowView the ICommand function for that button within the MainWindowViewModel will load a MemoryCardModel30 instance from the Model project. For each Card within the MemoryCardModel30 instance a CardViewModel will be created.
Now to the problems I face: How to create the CardView instances, how to link their DataContexts to the CardViewModels and how to arrange/assign the CardViews on the MainWindowView? The ViewModel project can't create Views as it has no dependency to the View project (would create a circular dependency and breaks the pattern). How to solve this issue while following the MVVM pattern?
P.S.: The CardViews need to be positioned exactly by x and y pos. which will require some complicated calculations which should go tho the corresponding CardViewModel. So some basic layouts like grid will not be sufficient I think.
Display them in an ItemsControl. I'm assuming that MainWindowViewModel.Cards is ObservableCollection<CardViewModel>.
<ItemsControl
ItemsSource="{Binding Cards}"
>
<!--
This creates UI for each item. There are other ways, if you've got a collection
of heterogeneous item types.
-->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:CardViewModel">
<views:CardView />
</DataTemplate>
</ItemsControl.ItemTemplate>
<!--
Make it use a Canvas to be the actual container for the items, so we can control
their position arbitrarily, instead of the default StackPanel that just stacks
them up vertically.
-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!--
The ItemsControl will put the instantiated item templates in ContentPresenters
that it creates. The positioning attributes have to go on the ContentPresenters,
because those are the direct children of the Canvas. The ContentPresenters are
the "item containers". You can customize them via the ItemContainerStyle property
of the ItemsControl.
-->
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<!--
The datacontext will be CardViewModel.
Bind Canvas.Left and Canvas.Top to appropriate properties
of CardViewModel. I'll assume it's got Point Position { get; }
A much better, more "pure MVVM" way to do this is for the items to
provide some kind of abstraction, maybe row/column or something else,
and either place them in a Grid or UniformGrid or some other kind of
dynamic layout control, or else convert that abstraction into Canvas
coordinates with a value converter on the Binding.
Then you can display the same item objects in different ways at the same
time without locking them into one layout.
Don't drive yourself crazy striving for ideological purity at the expense
of getting code out the door, but do consider redesigning that part.
-->
<Setter Property="Canvas.Left" Value="{Binding Position.X}" />
<Setter Property="Canvas.Top" Value="{Binding Position.Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
This is the canonical Way to Do It in WPF/MVVM. Use DataTemplates to create view instances of the appropriate type. The viewmodel is in charge of what objects should be presented to the user; the views are responsible for how they're shown. You don't need or want any MVVM framework for this. The built-in DataTemplate features of WPF are enormously powerful. Don't trust anybody who thinks you need anything else for a project within two orders of magnitude of this size.
I think I misunderstood your question. I originally thought you were asking how to display a new window for specific view models. While this answer won't specifically apply to you, I'll leave it up, as it is tangentially related. It may help others confused about what to search for.
I have a ViewManager class that links view types to viewmodel types. One of the methods on it is ShowViewFor that handles this task, it takes a viewmodel instance and:
Looks up the view for that viewmodel type.
Creates an instance of that view.
Sets the DataContext of that view instance to the viewmodel that was passed in.
Shows the view.
It also does a bunch of other tasks like tracking open views, displaying message boxes and dialogs, etc.
The ViewManager is available though an IOC container via an interface, so it can be mocked up for unit tests.
I'm sure there are many existing frameworks out there that do this, but like you, I wanted to learn MVVM from "the roots up".
I have a window which has several different DataTemplate that are load to a ContentControl based on a RadioButton (The RadioButton sends a command to the ModelView which sets the Content property of the ContentControl.
It works well, but now several views contain a "heavy" object (Eyeshot CAD viewer).
Switching to any of these view causes a delay (at this moment there's absolutely zero logic in the whole software other than the view/view model)
Is there a way to load the view and the heavy control to memory once and then reuse it when switching to its view? (The ViewModel of that view is currently a singleton but that doesn't help)
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Height="160" Margin="0,0,0,12">
... Removed for clarity
</StackPanel>
<ContentControl x:Name="Tabs" Content="{Binding SelectedTabViewModel}" Margin="0,12,0,12"/>
</DockPanel>
On your DataTemplate, you can set the attribute x:Shared="True", this will allow the framework to reuse the visual control (inside the datatemplate) for another ContentPresenter.
This doesn't load the component at starting, but, this reuse it once instantiated.
There are multiple places in my application whehe I have ContentControl placed in xaml and I do not know beforehand what its Content is going to be. What is the best practice to implement this scenario?
Right now I am considering two approaches:
Bind ContentControl.Content to view model and use a dictionary of DataTemplates to find an appropriate view. My issue with this approach is that even if I were to list all the possible combinations in a dicitonary, in some cases I simply do not know an exact type of view (or viewmodel if there is any) at compilation time. I think, I am also going to have troubles using this approach for hosting non-WPF content.
Create some sort of an interface:
interface IContentPlugin : IDisposable
{
object View { get; }
}
and bind ContentControl.Content to IContentPlugin.View directly. I could then have multiple implementations of this interface and swap them when I need to. But this solution does not strike me as something that goes well with MVVM application, as it forces me to have references to IContentPlugins in my view models.
What do you think is the best option and why? Perhaps there is a better approach?
this is a very interesting scenario and for these cases I usually introduce a ViewResolverService or a ViewModelResolverService (or both). So something that can either give you the ViewModel based on a view (class,type or name) match them to host them in the ContentControl. Or a Service which can give you a view based on the ViewModel (type, or string name). With this powerful concept you can use ContentControls and/or DataTemplates and you have full control.
I answered some questions explaining the concepts here:
Register all viewmodel and services in ViewModelLocator
and here:
Get the View & ViewModel from a plugin
more here: https://stackoverflow.com/search?q=ViewModelResolver
So if you look at it from the birds eye view you need to apply MVVM to your ContentControls with your views. (And the views have also MVVM applied within themselves).
HTH
You should use implicit View determination via DataTemplates.
This is achieved by having type-specific DataTemplates (i.e. DataTemplates without a key reference) for your ViewModel types in a ResourceDictionary local to the scope of the ContentControl.
Be aware though that you will need to scope the ResourceDictionary quite carefully in the case where a single ViewModel can have multiple Views associated with it.
Update:
The reasons to use implicit View determination are:
In general, the View resolution look-ups are faster than if you were to write a View resolving service.
You're not duplicating effort by writing your own View resolver which you then need to plug into the WPF runtime.
You should be telling the external source what you support and in this case, keep it always to WPF ResourceDictionary so that regardless of the content/resources, you are able to merge it into your runtime ResourceDictionaries - this means, your external sources will need to provide the WinForms control wrappers for you.
As someone who has created a plugin framework before using this pattern, working with a conceptually "pure MVVM" implementation simplifies things considerably - external sources supply a ViewModel class and a ResourceDictionary of the resources for the VM and you let WPF do the heavy-lifting of View determination for you.
Use DataTemplate for ContentControls:
<DataTemplate DataType="{x:Type vm:DataSourceViewModel}">
<view:DataSourceView></view:DataSourceView>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SelectTemplateViewModel}">
<view:SelectTemplateView></view:SelectTemplateView>
</DataTemplate>
.........
........
<ContentControl Margin="5" HorizontalAlignment="Stretch" Content="{Binding CurrentPage}" Name="ImportControls"></ContentControl>
VM:is the object type that is content of your contentcontrol
View: is specific view you want to see if object of a specific type is set as content of ContentControl
Eventually, I went with second approach. I was able to solve my main problem, which was:
But this solution does not strike me as something that goes well with MVVM application, as it forces me to have references to IContentPlugins in my view models.
Passing those "plugins" into viewmodels was a mistake, you should not do it. What you can and should do is find a way to partition your view into smaller independent segments, and set their content in non-MVVM way. So basically I ended up with a view, which acted as container and looked like this:
<UserControl x:Name=this>
<Grid>
<Grid.RowDefinitions>
<RowDefiniton>
<RowDefiniton>
<RowDefiniton>
</Grid.RowDefinition>
<ContentControl Grid.Row="0" Content="{Binding PluginA.View, ElementName=this}"/>
<ContentControl Grid.Row="1" Content="{Binding PluginB.View, ElementName=this}"/>
<ContentControl Grid.Row="2" Content="{Binding PluginC.View, ElementName=this}"/>
</Grid>
</UserControl>
where PluginA, PluginB and PluginC are dependency properties in code-behind, that are set by DI container using property injection. I am happy with the end-result, and it gives me the flexibility I need.
You can also use PRISM, which roughly speaking does the same thing, but in more general and flexible manner. It was somewhat too complex for my application though, so I decided to keep it simple. But you should give it a try, if you are trying to solve similar issue.
I explain my issue as I'm quite new to UI design :
I have a main View which displays a TreeView on its left part. When an element is selected I'd like to show a description of the Item on the right on the same window. The design of this description depends on the nature of the Item.
So I created a View per Item Type corresponding to the different possible design.
Now When I click on the TreeView I have no idea how to show the corresponding view on the right of the same window. (I'm not asking about catching the event, just displaying a view within another view, like if I dynamically plotted a control).
Is it possible ? If not what kind of approach would you suggest ?
Many Thanks.
This seems like a great candidate for a Data Template.
Basically, create a content presenter and bind its content property to the TreeView's SelectedItem property. Now, create data templates for each of your types (using the DataType property) in the ContentTemplate property.
Now, the correct data template will be chosen with the correct data whenever you select something in your tree view.
As far as a separate dispatcher goes, I'm not sure, but I'm also not sure what scenario would require one.
More information can be found in this SO question.
Sample:
<ContentPresenter Content="{Binding Path=SelectedItem, ElementName=TreeView}">
<ContentPresenter.ContentTemplate>
<DataTemplate DataType="{x:Type Type1}">
<!-- Bunch of stuff-->
</DataTemplate>
<DataTemplate DataType="{x:Type Type2}">
<!-- Bunch of stuff-->
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
I have a listbox in WPF that will contain a list of ResultsViewModel items, however the actual runtime type of these objects could be
CalculateResultsViewModel,
ScenarioResultsViewModel,
GraphResultsviewModel etc etc,
all of which extend the base abstract class ResultsViewModel.
Each of these view models should be rendered differently in the ListBox so needs a different DataTemplate. I can do that just with XAML easy enough. The difficulty is that when the viewmodels are either "processing" or when they have failed", I need them to display a DataTemplate for "processing" or "errored" which I can only so far do with Triggers. That however then means I can't use the DataTemplateSelector or a basic XAML style.
The only solution I can think of (not clean I know) is to set the DataTemplate programmatically in the SetResult() method of each viewmodel class, which is what gets called when the processing completes either successfully or with an error. In that DependencyProperty I can look at the return code and then programatically set the DataTemplate depending on the sucess/failure result. The only problem is I cannot figure out how to
Obtain a DataTemplate resource from a ResourceDictionary just using c# code - bearing in mind Im calling all of this from the viewmodel class, not the window code-behind .xaml.cs file so it doesn't have access to the properties of Window
having only a handle to the viewmodel class, somehow obtain a reference to the ListBoxItem that contains it and then programmatically set the DataTemplate on this container.
Can anyone point me in the right direction?
you can take the magic with implicit datatemplates
<ListBox ItemSource={Binding YourResults}>
<ListBox.Resources>
<DataTemplate DataType={x:Type CalculateResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type ScenarioResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type GraphResultsviewModel }>
<Grid></Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
for "processing" or "errored" viewmodels you can specify a adorner overlay in all yout datatemplates (ok but you must use the triggers)
hope this helps