Is it possible to cache the view when using model first approach? - wpf

In our product, we use MVVM model first approach and it works nicely but with one caveat. When view becomes complex it takes time to create it from the data template. If the view is shown and hidden frequently, it becomes slightly irritating. If using view first, it would be easy enough to cache a view if needed - but when using DataTemplate and model first, we do not have much control of view creation.
Anybody solved this problem already without switching to the view first method?

Works beautifully if using the #blindmeis idea.
The overall recipe:
Create a ContentControl or UserControl named ViewCache:
public partial class ViewCache
{
public ViewCache()
{
InitializeComponent();
Unloaded += ViewCache_Unloaded;
}
void ViewCache_Unloaded(object sender, RoutedEventArgs e)
{
Content = null;
}
private Type _contentType;
public Type ContentType
{
get { return _contentType; }
set
{
_contentType = value;
Content = ViewFactory.View(value); // use you favorite factory
}
}
}
In the DataTemplate, use the ViewCache, pass the type of the real view you want to use:
<Window.Resources>
<DataTemplate DataType="{x:Type TestViewCache:Foo}">
<TestViewCache:ViewCache ContentType="{x:Type TestViewCache:View }"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentPresenter Height="200" Width="300"
Content="{Binding ViewModel}"/>
<Button Content="Set VM" Click="SetVMClick"/>
<Button Content="UnSet VM" Click="UnSetVMClick"/>
</StackPanel>

with viewmodel first approach i think you have no chance to "cache" the view. so you may consider to use view first and a viewmodel locator for the heavyweight datatemplates workflows. here is a solution when using datatemplates with lists.
but maybe there is any solution with overriding the wpf datatemplate mechanism?
edit: what if you create just a "markerview" for your viewmodel, so wpf datatemplate can find it. and then within this marker view you create/rehydrate the real view? something like an view service locator?

Related

WPF - how to bind control by MVVM

i have view:
<Grid>
<!--Some Stuff-->
<Control XXX="{Binding ButtomControl}"/>
<!--Some Stuff-->
</Grid>
i have VM:
public sealed class SelectionDialogV3VM : PropertyChanges
{
// Some Stuff
public Control ButtomControl
{
get{return _buttomControl;}
set
{
_buttomControl = value;
OnPropertyChanged("ButtomControl");
}
}
// Some Stuff
}
My objective: in run time change some view (ButtomControl) inside of my main view.
But, i can not do proper binding because i do not know the XXX property.
thanks
Try something like this :
<ContentControl Content="{Binding ButtomControl}"/>
But honestly, having a property in your ViewModel of type Control is not a good omen :D
I just wanted to add something else:
Referencing a UI control in the view model should be avoided at all cost.
If you want to switch the view via the view model, try using DataTemplates and a ContentControl instead.
See:
http://rachel53461.wordpress.com/2011/05/28/switching-between-viewsusercontrols-using-mvvm/
Use a ContentPresenter:
<ContentPresenter Content="{Binding ButtomControl}"/>
Anyway, it's odd to bind to a control!
Thanks you all, all answers was valuable
Finally, i used DataTriggers, as described here:
MVVM : how to switch between views using DataTemplate + Triggers
Thanks

Silverlight Switching views in a view using MVVM Light framework

I have searched and tried for days and finally must ask the question here.
I have a Silverlight 5 application, Using MVVM Light, where I want to be able to dynamically switch views in the main view.
For the sake of simplicity, lets say I have 2 buttons.
Button1 will switch to TestView1.
Button2 will switch to TestView2.
<Button Content="TestView1" Grid.Column="1" Command="{Binding CallTestView1Command}" HorizontalAlignment="Left" Margin="185,17,0,0" VerticalAlignment="Top" Width="75"/>
<Button Content="TestView2" Grid.Column="1" Command="{Binding CallTestView2Command}" HorizontalAlignment="Left" Margin="280,17,0,0" VerticalAlignment="Top" Width="75"/>
The way I have done it is by binding a relaycommand to the button and then instanciating a new viewmodel of the corresponding view.
ie:
private RelayCommand _callTestView1Command;
public RelayCommand CallTestView1Command
{
get
{
return _callTestView1Command ??
(_callTestView1Command = new RelayCommand(() =>
{
CurrentView = ViewModelLocator.NinjectKernel.Get<TestViewModel1>();
}));
}
}
The CurrentViewmodel is then set to the new viewmodel.
In the MainView I have bound the CurrentView to a ContentControl:
<Border x:Name="displayedView" Grid.Row="2">
<ContentControl Content="{Binding CurrentView}" />
</Border>
This will actually work to some extend, since the CurrentView will change but instead of actually showing the content of the view it simply shows the Namespace of the ViewModel that is instanciated.
So far I have primarily used the knowledge taken from these sources:
http://rachel53461.wordpress.com/2011/05/28/switching-between-viewsusercontrols-using-mvvm/
Loading Views into ContentControl and changing their properties by clicking buttons
but they do not solve my problem, or I do not quite understand how to actually show the views.:-(
So does anyone have a good explanation on how to switch the views correct in Silverlight 5 using MVVM Light from GalaSoft.
Thanks
The part you are missing is the DataTemplates that tell WPF how to render your ViewModels
<Window.Resources>
<DataTemplate TargetType="{x:Type local:TestViewModel1}">
<local:TestView1 />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:TestViewModel2}">
<local:TestView2 />
</DataTemplate>
</Window.Resources>
When you insert an object in the Visual Tree, such as placing a ViewModel object in ContentControl.Content, it will get drawn by default using a TextBlock bound to the .ToString() of the object, which is why you are only seeing the namespace.classname of the ViewModel in your ContentControl
By defining an implicit DataTemplate in your Resources somewhere (that's a DataTemplate with only a TargetType defined - no x:Key), you are telling WPF to draw the specified object using the specified DataTemplate anytime it tries to draw that object, instead of using the default TextBlock bound to the .ToString() of the object.
It should be noted that implicit DataTemplates are not supported in earlier versions of Silverlight, however they are supported in 5.0+. For earlier versions of Silverlight, I usually use a DataTemplateSelector instead.
Id first suggest that you do not display your views via a ContentControl but look into using the navigation Frame in the silverlight toolkit. Also, we dont want our ViewModel creating Views... that'd not be so good. We don't mind, however, if our ViewModel does business logic and DETERMINES which view to show. Get the toolkit here: http://silverlight.codeplex.com/
Now setup your XAML as so in your main page:
<Border x:Name="displayedView" Grid.Row="2">
<navigation:Frame x:Name="ContentFrame" />
</Border>
Since you are using MVVM Light, we will use messaging. Your View model will get the command to change views, determine which view to change, then send a message to the main page to instruct it to change views.
Setup a listener in your main page for a navigate request as so:
public MainPage()
{
InitializeComponent();
Messenger.Default.Register<Uri>(this, "NavigationRequest", (uri) => ContentFrame.Navigate(uri));
}
Next, setup your command in your view model.
private RelayCommand _callTestView1Command;
public RelayCommand CallTestView1Command
{
get
{
return _callTestView1Command ??
(_callTestView1Command = new RelayCommand(() =>
{
Messenger.Default.Send<Uri>(new Uri("/Views/.../Page.xaml", UriKind.Relative), "NavigationRequest");
}));
}
}
These are the basics that work for me. You can expand on this and get real "architecty". For example, you can create a base class for you view models that sends the navigation requests, create a helper class that generates URIs (so they are not hard coded everywhere in your app, etc etc. Good luck!
So i actually solved this problem, in a way where there is no need to create datatemplates in the MainView, which i did not like. imo the MainView should know nothing about the views it is displaying, when we are talking about switching the views.
Prerequisite: You must use MVVM Light from GalaSoft for this solution
This is my test solution:
Two buttons are added to my MainView, Each button will open a new view. The clickevent are bound to Commands.
<Button Content="TestView1" Grid.Column="1" Command="{Binding CallTestView1Command}" HorizontalAlignment="Left" Margin="185,17,0,0" VerticalAlignment="Top" Width="75"/>
<Button Content="TestView2" Grid.Column="1" Command="{Binding CallTestView2Command}" HorizontalAlignment="Left" Margin="280,17,0,0" VerticalAlignment="Top" Width="75"/>
In the MainView i have a Border that should contain the views than can switch.
Since all views inherit from UserControl i bind the content to the property CurrentView of the MainViewModel
<Border x:Name="displayedView" Grid.Row="2">
<UserControl Content="{Binding CurrentView}" />
</Border>
In the MainViewModel i have the property CurrentView.
public const string CurrentViewPropertyName = "CurrentView";
private UserControl _currentView;
/// <summary>
/// Sets and gets the "CurrentView property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (_currentView == value)
{
return;
}
RaisePropertyChanging(CurrentViewPropertyName);
_currentView = value;
RaisePropertyChanged(CurrentViewPropertyName);
}
}
When a button is clicked the corresponding Command is called in the MainViewModel:
private RelayCommand _callTestView1Command;
public RelayCommand CallTestView1Command
{
get
{
return _callTestView1Command ??
(_callTestView1Command = new RelayCommand(() =>
{
CurrentView = new TestView1();
}));
}
}
private RelayCommand _callTestView2Command;
public RelayCommand CallTestView2Command
{
get
{
return _callTestView2Command ??
(_callTestView2Command = new RelayCommand(() =>
{
CurrentView = new TestView2();
}));
}
}
As seen each command will set CurrentView to a new view, and the views will switch in the MainView, because CurrentView will raise a ProperTyChanged Event.
This will actually work to some extend, since the CurrentView will
change but instead of actually showing the content of the view it
simply shows the Namespace of the ViewModel that is instanciated.
Because you are changing the CurrentView property to a viewmodel instance and bind that as the Content. This is wrong as the Content should be a view and you should set the DataContext of that view to a viewmodel.
The simplest thing you can do here is to create a View instance inside the command and set the viewmodel as its DataContext and then you can set the view to the CurrentView property. Of course this would violate the MVVM pattern so you should move this responsibility to a separate component. Instead of writing your own navigating logic I suggest you to pick up an existing solution as this kind of task is not as straightforward as it seems.
I suggest to use the Prism library

how can I set the xaml content in viewmodel

I've had a hyperlink button where i set in the button click in code behind the content to a new view if the login success.
private void OkButtonClick(object sender, RoutedEventArgs e)
{
LoginOperation loginOp = FLS.Utilities.RIAWebContext.Current.Authentication.Login(
new LoginParameters(usernameTextBox.Text, passwordTextBox.Text));
loginOp.Completed += (s2, e2) =>
{
if (loginOp.HasError)
{
errorTextBlock.Text = loginOp.Error.Message;
loginOp.MarkErrorAsHandled();
return;
}
else if (!loginOp.LoginSuccess)
{
errorTextBlock.Text = "Login failed.";
return;
}
else
{
errorTextBlock.Text = string.Empty;
Content = new WelcomeView();
}
};
}
I've now moved the code behind for MVVM in a view model and use a delegateCommand on the hyperlink button.
<UserControl ... >
<Grid ... >
...
<HyperlinkButton Content="Login" Height="23" HorizontalAlignment="Left" Margin="313,265,0,0" Name="loginButton" Command="{Binding Path=LoginCommand}" VerticalAlignment="Top" Width="75"/>
...
</Grid>
</UserControl>
But I don't know, how I make the Content = new WelcomeView(); from the code behind in the viewmodel?
A good design pattern would be to have two different Data Template, one to present the data before the login, and the second Data Template to be used after the login.
There are several ways to achieve that. The one that I typically use simply put the ViewModel (directly of using binding) the only children of Window.
In your ViewModel implement a content selector class. This is a class derived from DataTemplateSelector and uses the FindResource API to get the appropriate data template.
<Window ...>
<Window.Resources>
<DataTemplate x:key="beforeLogin">
...
</DataTemplate>
<DataTemplate x:Key="afterLogin">
...
</DataTemplate>
</Window.Resources>
<Window.ContentTemplateSelector>
<code:MyTemplateSelector />
</Window.ContentTemplateSelector>
<-- Here is the content of Window. It's the view model (data). The View will be
bind by the TemplateSelector
<code:YourViewModel />
</Window>
Check out this page: http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplateselector.aspx for a related example.
There are other design pattern. Another common idiom is simply firing a "UiRequest" event, which will be picked up by the code-behind of the view. Remember that MVVM dictates the ViewModel is "view agnostic", but it really doesn't mean "no code behind". This means the VM cannot reference anything in the view. Communication this way happens view events (e.g. data binding is just a wrapper around property changed events). So have an event UiRequest in your View Model, and design a protocol. In the constructor of the View - register a handler. In the handler, change the content (people use this idiom mainly to start a popup window, but it can be used anywhere).

How do i design a composite view and view model using Silverlight and MVVM?

I want to create a "Wizard" in my Silverlight MVVM application. The Wizard should contain multiple steps between which you can navigate using "next" and "previous".
The problem I am facing is the relationship between views and view models.
I want there to be a view and view model for the Wizard itself. My instinct tells me that there should be one view/view model pair for each step in the wizard.
What is a good approach for maintaining these kinds of relationships, where one view model holds several other view models and the view actually consists of several smaller views?
Are there any patterns or practices that I can use?
I know this question might be subjective, but give me the rough cuts of an approach and I'll award you an answer!
I'd propose main wizard viewModel which has a collection of steps view models and handles navigation between them. While navigating it should call validation methods in step viewModels:
WizardVM:
public class WizardVM
{
// this commands should support CanExecute
public ICommand GotoNextCommand { get; private set; } // should open next step VM
public ICommand GotoBackCommand { get; private set; } // should open previous step VM
// this prop should be set by 'GotoNext', 'GotoBack' commands
public object CurrentStep { get; private set; }
// probably internally you will have a list of all steps:
private ICollection<object> _stepViewModels = ...;
}
WizardView:
<StackPanel>
<ContentPresenter Content="{Binding CurrentStep}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding GotoBackCommand}">Back</Button>
<Button Command="{Binding GotoNextCommand}">Next</Button>
</StackPanel>
</StackPanel>
UPDATE:
Views can be coupled with ViewModels via Datatemplating. For example add this into resources in App.Xaml:
<DataTemplate DataType="{x:Type local:Step1ViewModel}">
<Step1View />
</DateTemplate>
<DataTemplate DataType="{x:Type local:Step2ViewModel}">
<Step2View />
</DateTemplate>
Your viewModels should know absolutely nothing about views. It means that WizardVM should
expose only other viewModels but not views. It's a rule of thumb for MVVM.
UPDATE2 Oops, I forgot that Silverlight doesn't have DataTemplating yet. In silverlight I would still expose ViewModels but bind them to ContentPresenters using a converter which will convert a viewModel into corresponding view.

What would be a clever design for layered panels?

I would like to design a light "members listing/editing" app in one and only window.
My guess was that the best and simplest way to achieve this would be to have the "listing" part (mostly a datagridview and some search stuff) on a panel, and the "editing" (new member or modify member) on another, each panel hiding the other depending on what User wants to do.
That's what I have to end up with, visually speaking.
I've thought of many ways to design this but no one sounded actually good to me, mainly when it comes to instantiate the viewmodel of the editing panel passing the selected member in the dgv of the listing panel or stuff like that.
I still consider myself a beginner at WPF and I'm sure the most clever solution is something that didn't come to my mind.
Can't wait to read expert's suggestions ;)
You should be thinking more in terms of DataTemplate.
Split your two different views up, eg. MemberListingView.XAML and MemberEditView.XAML. Create view-models for each view.
To put it all together, follow the data templating technique:
<DataTemplate DataType="{x:Type vm:MemberListingVM}">
<AdornerDecorator>
<views:MemberListingView />
</AdornerDecorator>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MemberEditVM}">
<AdornerDecorator>
<views:MemberEditView />
</AdornerDecorator>
</DataTemplate>
// Now use a content presenter
<ContentPresenter Content="{Binding CurrentView}" />
You should have somewhere in your context a property that specifies the current view that you need to show.
private ViewModelBase _currentView;
public ViewModelBase CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
RaisePropertyChanged("CurrentView");
}
}
// ...
public void OnSelectedMemberChanged(Member member)
{
// Depending on your logic
// If some condition...
CurrentView = new MemberEditVM(member);
// else
CurrentView = MemberListingVM;
}

Resources