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

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.

Related

How to get property values from ancestor controls far away in a complex WPF visual tree, in XAML?

For starters, I barely know what I'm doing.
I'd like to reach up to a control that's pretty far away in my visual tree to get a property value from it. It's a ContentControl and the property is IsTabStop. I'm in a user control which is heavily re-used in a variety of places with different tree structures. Since the tree structure is so variable, I don't think I can just use FindAncester with a type of "ContentControl" (can't predict how many intermediate ContentControls are in the tree).
The control I'm looking for is in the DataTemplate used to display an item in an ItemsControl. Even though there's a unique name, my attempt to snag the property value with an ElementName binding didn't work either. (It seems to have picked up the wrong value.)
There are also more than one ItemsControl in the picture (meaning, I may or may not be inside a secondary ItemsControl), so I don't think I can use some variant of "." (even if I was smart enough to think of one).
Is there some way I can do this in XAML, or is this just asking XAML to do too much? Should I be explicitly creating the appropriate Binding in the code-behind?
Edit, 27 Apr:
tl;dr: Looks like ElementName is the right approach. I guess it gets resolved exactly in the way I would have written my procedural code (and in a way similar to how JavaServer Faces does it): by walking up the tree until we find a scope containing the named element (which is close enough to "walking up the tree until we get a hit" for -government work- hackery). See http://blogs.msdn.com/b/mikehillberg/archive/2008/05/23/of-logical-and-visual-trees-in-wpf.aspx (Editorial note for Microsoft: why does this piece of info need to be in a blog entry? Am I supposed to be reading source code? At least Sun laid it out in a spec.)
I agree I don't need procedural code, but....
Stuff I've tried:
IconsToolsBar.xaml:
<UserControl>
<Grid><Grid><Border><Grid>
<!-- Fail. Defaults to true, ignores ancestor values (apparently). -->
<Button/>
<!-- Fail. Snoop reports a binding error (red highlight) -->
<Button IsTabStop="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsTabStop}"/>
<!-- Um, well... this seems to work. Why? "FormElement" occurs multiple times by virtue of the fact that we're
in an ItemsControl.
-->
<Button IsTabStop="{Binding IsTabStop, ElementName=FormElement}"/>
</Grid></Border></Grid></Grid>
</UserControl>
Separate resource file. Note that this "dictionary" object can occur inside another ItemsControl not shown.
<ResourceDictionary>
<DataTemplate x:Key="MultiSelectListDictionaryDataTemplate">
<Grid>
<toolBars:IconsToolBar DataContext="{Binding IconsToolBarController}"/>
<!-- other stuff -->
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:ObsSuggestedDictionaryViewModel}">
<ContentPresenter x:Name="DictionaryPresenter"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"
ContentTemplate="{StaticResource DropdownSingleDictionaryDataTemplate}" />
<DataTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding DisplayFormat}" Value="Vertical"/>
<Condition Binding="{Binding SelectionType}" Value="Multi Select"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="DictionaryPresenter" Property="ContentTemplate" Value="{StaticResource MultiSelectListDictionaryDataTemplate}"/>
</MultiDataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ResourceDictionary>
This is a 3rd separate file that has a user control that contains objects of various types, including the "dictionary" type above, that we want to display. Note that this LayoutPart is itself contained in yet another ItemsControl, but I'm not going to lay the tree out that far up, because here is where the data I want lives.
<ResourceDictionary>
<DataTemplate x:Key="MultiSelectListDictionaryDataTemplate">
<Grid>
<toolBars:IconsToolBar DataContext="{Binding IconsToolBarController}"/>
<!-- other stuff -->
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:ObsSuggestedDictionaryViewModel}">
<ContentPresenter x:Name="DictionaryPresenter"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"
ContentTemplate="{StaticResource DropdownSingleDictionaryDataTemplate}" />
<DataTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding DisplayFormat}" Value="Vertical"/>
<Condition Binding="{Binding SelectionType}" Value="Multi Select"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="DictionaryPresenter" Property="ContentTemplate" Value="{StaticResource MultiSelectListDictionaryDataTemplate}"/>
</MultiDataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ResourceDictionary>
(It's unfortunate that we're calling our objects "dictionaries". It's "for historical reasons".)
ObsSuggestedDictionaryViewModel is a subclass of a class that has a property that tells us whether we want the thing to be a tab stop or not.

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.

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);

How and where dispose a ViewModel?

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

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