How and where dispose a ViewModel? - silverlight

MVVM pattern is wonderful with MVVM Light better but sometimes I think I don't understand anything.
I've a business application in SL 4 where I've, by now, 18 VM.. and more to write! I'm applying the pattern Laurent Bugnion used in his session at MIX11 (with SimpleIoc class).
A viewmodel is bind to a view (name it "A") but the very same viewmodel is bind to another view too (name it "B"). Viewmodel bound with view "A" is called in a standard way in the ViewModelLocator. Viewmodel bound with view B is called with a different key so to be sure they are 2 different istances. Besides they are injected with different DomainService so entities bind with controls on view are different.
The view model registers for some messages to keep track of the variation in other viewmodels it interacts, say a selection changed means the user wants to display other things in order to retrive data on DB).
The problem is if I call view A and then view B I register for the same messages 2 times so I've 2 operations on DB.
What I think correct is to dispose viewmodel bound to view A when I call view B (generally when I close view A). But I don't really know where to dispose it, when and how! Ok.. I could imagine when and how.. but WHERE?
If you're thinking I'm confused, you're right!

If I understand you correctly, you're using the same ViewModel with two different Views. You want only one copy of the VM existing at a time.
In this case, I'd probably use whatever the VM's parent is and modify something like a Mode property on the VM.
<DataTemplate x:Key="ViewA" TargetType="{x:Type local:MyViewModel}">
<TextBlock Text="I'm View A" />
</DataTemplate>
<DataTemplate x:Key="ViewB" TargetType="{x:Type local:MyViewModel}">
<TextBlock Text="I'm View B" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource ViewA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
Then to switch views, I would simply set ParentViewModel.CurrentViewModel.Mode = 2 and the View simply gets switched without changing the ViewModel.
If you want two different copies of the same ViewModel, I'd still handle the switching in the ParentViewModel using something like ParentViewModel.CurrentViewModel = ViewModelInstanceB, and have the ViewModelInstanceB.Mode set to 2
I wrote some examples of switching between Views here if you're interested

Related

How to get always the same instance of UserControl loaded via DataTemplate?

One ContentControl is used to load one of two UsersControls in resources via datatemplate.
It works fine but the constructor of every usercontrol is always called when navigating between them. How can I load always the same instance of the usercontrol (one instance for UserControl1 and one instance for Usercontrol2). I know that the tabcontrol can achieve that but how I can do it in my scenario without tabcontrol.
Resources:
<UserControl.Resources>
<DataTemplate x:Key="UserControl1">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="UserControl2">
<local:UserControl2 />
</DataTemplate>
</UserControl.Resources>
ContentControl:
<ContentControl x:Name="ContentControl" Background="White">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentView,
ElementName=UserControl1}" Value="UserControl1">
<Setter Property="ContentTemplate" Value="{StaticResource UserControl1}" />
</DataTrigger>
<DataTrigger Binding="{Binding CurrentView, ElementName=UserControl2}" Value="UserControl2">
<Setter Property="ContentTemplate" Value="{StaticResource UserControl2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
How can I load always the same instance of the usercontrol (one instance for UserControl1 and one instance for Usercontrol2)
You can hold the instances as Resources and host them inside a ContentPresenter, instead of creating a new instance everytime the DataTemplate is used:
<UserControl.Resources>
<local:UserControl1 x:Key="UserControl1Instance" />
<local:UserControl2 x:Key="UserControl2Instance" />
<DataTemplate x:Key="UserControl1">
<ContentPresenter Content="{StaticResource UserControl1Instance}" />
</DataTemplate>
<DataTemplate x:Key="UserControl2">
<ContentPresenter Content="{StaticResource UserControl2Instance}" />
</DataTemplate>
</UserControl.Resources>
How can I load always the same instance of the usercontrol
You should not be concerned about that. There's nothing in your post that explains why you are concerned about the constructor being called multiple times, but that should be a complete non-issue. If it's an issue, then you have something wrong with your design.
"View" objects, such as windows, user controls, and indeed every UIElement that goes into presenting the program's state to the user, should not have any operations within them except that strictly needed to present the state to the user. WPF will instantiate and discard your specific instances as needed to handle the current state of the program. If it's in any way a problem for more than one instance of a user control to exist at a time, or for more than one instance to be created and/or discarded over the course of running the program, then the design of the program is fundamentally broken and should be fixed.
If you run into any problems trying to accomplish that, of course feel free to post a question about that. Be sure to include a good Minimal, Reproducible Example, along with any details about what specifically you need help with.
And of course, to start with, you should read everything you can find on the MVVM design pattern, if you haven't already. WPF has a steep learning curve when using the framework correctly, but it's even harder to use when you're not using it correctly.

How to apply convention-over-configuration with Caliburn.Micro instead of these DataTriggers?

I have a View/ViewModel pair properly working with Caliburn.Micro.
Inside the view, there is a ContentControl whose content is bound to the same viewmodel, and depending on the value of a given enumerated property in the viewmodel, I want a different template for the ContentControl:
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate x:Key="TurnedOffView">
<local:TurnedOffView/>
</DataTemplate>
<DataTemplate x:Key="DeviceReadyView">
<local:DeviceReadyView/>
</DataTemplate>
</ContentControl.Resources>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource TurnedOffView}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="{x:Static local:DeviceStates.Ready}">
<Setter Property="ContentTemplate" Value="{StaticResource DeviceReadyView}"/>
</DataTrigger>
<!-- More DataTriggers here, one for each state -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I know Caliburn.Micro can use cal:View.Model and cal:View.Context so that multiple views can be bound to each ViewModel, but I can't figure out how to use this to avoid all this verbosity.
So the question is:
How should I use View.Model and View.Context (and rename my views) in this scenario to take advantage of Caliburn.Micro convention-over-configuration approach?
<ContentControl cm:View.Model="{Binding}" cm:View.Context="{Binding ContextProp, Mode=TwoWay}" /> might be something to try... Then your context would be based off code in your viewmodel not having to structure datatemplates with view-first code. ContextProp would be basically a sub view of your view in question (based on namespace Project.Views.MainView.Main.TurnedOff) ContextProp = "TurnedOff";
yea sorry.. Its how I see the folder structure (namespaces)... So instead of TurnedOffView it could be TurnedOff.xaml under a folder named Main. This was assuming your primary view is MainView.xaml and your primary Viewmodel is MainViewModel.cs. SOrry for the confusion.
View.Model can be setup to do multiple viewmodels, at that rate you might want to think about Conductor in the framework. But I don't think it would be warranted since you are basically doing view switching in the presented case above.

DRY - Subclassing WPF windows

I have several WPF windows which will be very similar except for the columns on a DataGrid (the DataContext will be ObservableCollections of different objects), the text in some Labels and a Button click handler.
For each window, the <DataGrid.Columns> part of the DataGrid is different. It uses AutoGenerateColumns="False" and shows different columns for the different objects.
I wonder if it's possible to subclass a base WPF window so I can just write the <DataGrid.Columns> part on the XAML for each subclass instead of writing it in code.
Or what other techniques exist for abiding by the DRY principle on WPF while still using XAML?
How do I populate DataGrid Columns from the datasource...
Yes, you have come across a limitation here. The Columns property is not bindable; in fact it isn't even settable, you can only add and remove from the collection. There is a workaround at this question: How do I bind a WPF DataGrid to a variable number of columns?
So theoretically, you could add the columns to <Application.Resources>, then databind an attached property as in the question above, and write a value converter that builds a column collection based on the datasource value, pulling from Application.Current.Resources. But this seems more convoluted than it needs to be.
I think you could just use a style trigger that replaces some Content with different DataGrids:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Content">
<Setter.Value>
<DataGrid Style="{StaticResource CommonStyle}">
<DataGrid.Columns>
... default columns go here ...
</DataGrid.Columns>
</DataGrid>
<Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeCondition}" Value="True">
<Setter Property="Content">
<DataGrid Style="{StaticResource CommonStyle}">
<DataGrid.Columns>
... alternate columns ...
</DataGrid.Columns>
</DataGrid>
</Setter>
</DataTrigger>
... additional triggers as needed ...
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
This could be part of a larger common view template -- no need to create separate view classes.
I would do it with one window and different DataTemplates. However, if you want to use inheritance then you could override the DataTemplate in the Window.Resources using the key that is referenced by the base Window. The DataTemplate would have the Xaml for the whole datagrid.

Switching between views according to state

Say I've an application that displays user friends list. The friends list is displayed in as a TabItem. The user has to Log in first to the server, in order to get the list of friends.
I've created two user controls, one for when the user is logged in, the the other when he is unlogged. something alone this line:
UnloggedView.xaml
<UserControl x:Class="UnloggedView" ...>
<TextBlock ...>You need to <Hyperlink Command="{Binding LoginCmd}">
Login</Hyperlink>too see your friends list</TextBlock>
</UserControl>
LoggedView.xaml:
<UserControl x:Class="LoggedView" ...>
...
<ListView ItemSource={Binding Path=friends}">...
</UserControl>
The main window has the following code:
....
<TabItem Header="Friends">
<vw:UnloggedView />
</TabItem>
I believe everything is according to the MVVM principal. The LoginCmd is a simplified variation of the DelegateCommand (from prism), implemented in the ViewModel. Both Views works fine, and as the list is populated (asynchronously), notification are fired and the View is updated. I'm happy.
So I've two questions: First question is how to I fire the LoginWindow (where the user is prompted to enter his credentials? For now, I simply create the LoginWindow (a view object) and presents it with ShowDialog. It appears like I'm breaking the rules of MVVM here by directly manipulating the UI from the ViewModel.
main question is after I log-in with the server, what is the correct way to replace the content of the TabItem with the LoggedView. According to the MVVM principals, the ViewModel shouldn't not have knowledge to the internals of the View. I expose IsLogged property in the ViewModel (which will fire PropertyChanged notification) but what should I bind to what in order to make everything happens? I really don't want the ViewModel manipulating the View.
Thanks
I see this question come up a lot, and have written something about switching between Views/UserControls here. Usually I use a ContentControl and switch out the ContentTemplate based on a DataTrigger, however the same principal works to switch a TabControl's ItemTemplate
<DataTemplate x:Key="LoggedOutTemplate">
<local:UnloggedView />
</DataTemplate>
<DataTemplate x:Key="LoggedInTemplate">
<local:LoggedView />
</DataTemplate>
<TabControl>
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="ItemTemplate" Value="{StaticResource LoggedOutTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoggedIn}" Value="True">
<Setter Property="ItemTemplate" Value="{StaticResource LoggedInTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
</TabControl>
You may have to use ElementName or RelativeSource in your DataTrigger binding to find the IsLoggedIn property in your DataContext
As for firing a Login Command from your Logged Out view, there are multiple ways of doing it.
My preferred method is to use some kind of messaging system such as MVVM Light's Messenger, or Microsoft Prism's EventAggregator, and firing some kind of ShowLoginDialog message when the button is clicked, then let whatever ViewModel is taking care of showing the login dialog subscribe to receive those messages and handle them.
Another way is simply use a RelativeSource binding to find the object in the Visual Tree that has the LoginCommand in it's DataContext, and bind to that.
You can see examples of both here
First I'll answer you second question... Just create an Enum as SessonState
enum SesionState
{
LoggedOut=0,
LoggedIn
}
After that create a property in your ViewModel for window called SessionState and Update that Property with the required value when you Login and LogOut.
Xaml required for switching views
<Window>
<Window.Resources>
<DataTemplate x:Key="LoggedOutView">
<ViewLayer: LoggedOutView/>
</DataTemplate>
<DataTemplate x:Key="LoggedInView">
<ViewLayer:LoggedInView/>
</DataTemplate>
<Style x:Key="mainContentControlStyle"
TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SessionState}"
Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=LoggedOutView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Mode}"
Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource ResourceKey=LoggedInView}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TabControl>
<TabItem>
<ContentControl Grid.Row="0"
Content="{Binding}"
Style="{StaticResource ResourceKey=mainContentControlStyle}">
</TabItem>
</TabControl>
</Grid>
</Window>
for your 1st question: you can simply use a ILogindialogservice from your viewmodel. i use the following for dialogs and mvvm. its "Unit Test"able and not breaking mvvm.
EDIT. in your viewmodel you would have then a line something like this:
var result = this.loginservice.ShowDialog("Login", loginvm);

What is the best way to switch views/usercontrols in MVVM-light and WPF?

I'm relatively new to WPF and MVVM and the hardest thing I have found is how to simply switch a usercontrol or a view in an application.
In winforms, to have a control remove itself you would simple say this.Parent.Controls.Remove(this);
In WPF there is no generic Parent control, you would have to typecast it to the specific type (i.e. Grid) and then remove it.
This also seems to break the MVVM architecture. I have also tried data templates and content presenters, which work well, except for the fact that I can't change the datacontext from code, since the datacontext is always the viewmodellocator.
Are pages the acceptable way to do this in WPF now? What if I had a grid with a custom usecontrol and I wanted to switch it based on some variable in the viewModel? It seems like the simplest tasks cannot be accomplished easily in WPF.
You would do so in your parent ViewModel.
For example, if your page (call it PageViewModel) had two views (ViewModelA and ViewModelB), you would have a property on PageViewModel called CurrentView, and this would determine which View is visible. When PageViewModel.CurrentView is set to an instance of ViewModelA, then ViewA's DataTemplate is used to draw the content. When it's set to an instance of ViewModelB, ViewB's DataTemplate is displayed.
<DataTemplate DataType="{x:Type local:PageViewModel}">
<ContentControl Content="{Binding CurrentView}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<TextBlock Text="I'm ViewModelA" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<TextBlock Text="I'm ViewModelB" />
</DataTemplate>
It would be ideal to call the switch views command from the parent view (in this case the DataTemplate for the PageViewModel), however if you wanted to switch views from within ViewModelA/B, you can either hook up the event manually when the objects get created (CurrentView.ChangeViewCommand = this.ChangeViewCommand) or look into a messaging system. MVVM Light has a simple Messenger class which I found was fairly easy to use, or Prism has a more advanced EventAggregator
If you want to switch Views for the same ViewModel, I would recommend a Mode property that gets used to determine which view to use. For example:
<DataTemplate x:Key="ViewA" DataType="{x:Type local:MyViewModel}">
<TextBlock Text="I'm ViewModelA" />
</DataTemplate>
<DataTemplate x:Key="ViewB" DataType="{x:Type local:MyViewModel}">
<TextBlock Text="I'm ViewModelB" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource ViewA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Mode}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
EDIT
I actually see this kind of question come up a lot, so posted something about it here if anyone is interested

Resources