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).
Related
I have a WPF application where I dynamically load document view instances into a TabControl. The view has a ToolBar with some ToggleButtons which I use to control the visibility of certain elements in that view like so (only relevant elements shown):
<UserControl x:Class="MyProject.View.Views.DocumentView" ...>
...
<ToolBar>
<ToggleButton x:Name="togglePropInspector" ... />
...
</ToolBar>
...
<Border Visibility={Binding ElementName=togglePropInspector, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}">
...
</Border>
</UserControl>
I found this kinda neat as everything is handled inside the view and didn't require adding code to the view model (or code behind). However, the problem is that checking the toggle button on one tab now checks it in all instances of the view, not just the current tab. This basically applies to all elements whose state is not bound to the view model in any way. Is there a way around this without having to add code to the view model?
For completeness' sake here's the relevant part of how I'm loading the views:
<TabControl ItemsSource="{Binding Documents}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewModels:DocumentViewModel}">
<local:DocumentView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
TabControl has a single content host that is used for all TabItem instances. When the data models assigned to the TabItem.Content property are of the same data type, then the TabControl will reuse the same DataTemplate, which means same element instances, only updated with the changed data from data bindings.
To change the state of the reused controls, you must either access the control explicitly, or force the TabControl to reapply the ContentTemplate by temporarily changing the data type of the Content:
<TabControl SelectionChanged="TabControl_SelectionChanged" />
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var tabControl = sender as TabControl;
var tabItemContainer = tabControl.ItemContainerGenerator.ContainerFromItem(tabControl.SelectedItem) as TabItem;
object currentContent = tabItemContainer.Content;
tabItemContainer.Content = null;
// Defer and leave the context to allow the TabControl to handle the new data type (null).
// The content switch shouldn't be noticable in the GUI.
Dispatcher.InvokeAsync(() => tabItemContainer.Content = currentContent);
}
You can also use a dedicated data type for each tab. This way the TabControl is automatically forced to switch the DataTemplate.
The cleanest solution would be to bind the ToggleButton to the data model.
I have read some thread about how to work on WPF ListView command binding.
Passing a parameter using RelayCommand defined in the ViewModel
Binding Button click to a method
Button Command in WPF MVVM Model
How to bind buttons in ListView DataTemplate to Commands in ViewModel?
All of them suggest write the logic code inside ViewModel class, for example:
public RelayCommand ACommandWithAParameter
{
get
{
if (_aCommandWithAParameter == null)
{
_aCommandWithAParameter = new RelayCommand(
param => this.CommandWithAParameter("Apple")
);
}
return _aCommandWithAParameter;
}
}
public void CommandWithAParameter(String aParameter)
{
String theParameter = aParameter;
}
It is good practice or anyway so I can move the CommandWithAParameter() out of the ViewModel?
In principle, MVVM application should be able to run to its full potential without creating the views. That's impossible, if some parts of your logic are in View classes.
On top of that, ICommand has CanExecute, which will autamagically disable buttons, menu items etc. if the command should not be run.
I understand why with basic RelayCommand implementation it can be hard to see the benefits, but take a look at ReactiveCommand samples.
ReactiveCommand handles async work very well, even disabling the button for the time work is done and enabling it afterwards.
Short example: you have a login form. You want to disable the login button if the username and password are empty.
Using commands, you just set CanExecute to false and it's done.
Using events, you have manualy disable/enable the button, remember that it has to be done in Dispatcher thread and so on - it gets very messy if you have 5 buttons depending on different properties.
As for ListView, commands are also usefull - you can bind current item as command parameter:
<ListView ItemsSource="{Binding MyObjects}">
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel>
<!-- change the context to parent ViewModel and pass current element to the command -->
<Button DockPanel.Dock="Right" Command="{Binding ElementName=Root, Path=ViewModel.Delete}" CommandParameter="{Binding}">Delete</Button>
<TextBlock Text="{Binding Name}"/>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
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
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?
I am currently writing an application to which the composite methodology fits like a glove.... almost!
I also need a way to navigate between views, including maintaining a journal for navigation backward and forward.
What is the best way to combine these two methodologies, on one hand the single Window based CAG shell with its UserControl derived views, and on the other hand the convenient NavigationWindow shell with its Page derived views and journal?
Thanks!
You can display anything in a NavigationWindow, not just Pages. A simple way to make it work is to define in the NavigationWindow's resources a DataTemplate for each ViewModel you want to display. Bind the Content property of the NavigationWindow to a property of your main ViewModel, and you're done : changing that property will update the NavigationWindow content, and the appropriate DataTemplate will be picked automatically
UPDATE
I just looked at the code of a project of mine where I used a NavigationWindow. Actually I was mistaken, it doesn't work by binding the Content (or maybe it works, but that's not what I did). Instead I created a INavigationService interface, implemented by my App class, which handles the navigation by calling the NavigationWindow.Navigate method. That way, the navigation history is maintained by the NavigationWindow.
Here's an extract from my project
MainWindow.xaml :
<NavigationWindow x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MyApp.ViewModel"
xmlns:view="clr-namespace:MyApp.View"
Title="{Binding Content.DisplayName, RelativeSource={RelativeSource Self}, FallbackValue=The Title}"
Height="600" Width="800">
<NavigationWindow.Resources>
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<view:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
<view:CustomerView />
</DataTemplate>
</NavigationWindow.Resources>
</NavigationWindow>
App.xaml.cs :
...
private void Application_Startup(object sender, StartupEventArgs e)
{
LoadConfig();
MyApp.MainWindow window = new MainWindow();
INavigationService navigationService = this;
HomeViewModel viewModel = new HomeViewModel(navigationService);
this.MainWindow = window;
window.Navigate(viewModel);
window.Show();
}
When I need to navigate to another view, I just call the Navigate method with the ViewModel as a parameter, and WPF automatically picks the appropriate DataTemplate from the resources.