How to navigate between page and open window using MVVM in WPF? - c#

I have read several article, tutorial, example. But I'm still unable to make navigation between page and windows happens.
> Visual Studio Community 2019
> .NET Framework: 4.7.2
To make it simple, I have 3 pages and 1 window ALL VIEW IS FULL SCREEN FOR KIOSK APPLICATION. Details page as below:
MVVMApps.sln
│
├── PageInitialize.xaml
├── PageHome.xaml
└──── PageSelectLanguage.xaml
└── WinMessage.xaml
I have try MvvmLight by GalaSoft but stuck on navigate from PageInitialize.xaml to PageHome.xaml. And I just found article from GalaSoft that WPF is coming soon for INavigationService. Most tutorial I found is sampling for Xamarin.
Plus I got undefined assembly using Microsoft.Practices.ServiceLocation; which I see it's available on Enterprise. On Nuget, installing MvvmLight will install CommonServiceLocator too.
public class ViewModelLocator
{
private static bool initialized;
public ViewModelLocator()
{
//Fix to keep blend happy
if (initialized) { return; }
initialized = true;
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<PageInitializeViewModel>();
SimpleIoc.Default.Register<PageHomeViewModel>();
SimpleIoc.Default.Register<PageSelectLanguageViewModel>();
SimpleIoc.Default.Register<WinMessageViewModel>();
SetupNavigation();
}
public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>();
public PageInitializeViewModel PageInitializeViewModel => ServiceLocator.Current.GetInstance<PageInitializeViewModel>();
public PageHomeViewModel PageHomeViewModel => ServiceLocator.Current.GetInstance<PageHomeViewModel>();
public WinMessageViewModel WinMessageViewModel=> ServiceLocator.Current.GetInstance<WinMessageViewModel>();
public static void Cleanup()
{
// TODO Clear the ViewModels
}
private void SetupNavigation()
{
var navigationService = new Helpers.NavigationService<Helpers.NavigationPage>();
navigationService.ConfigurePages();
SimpleIoc.Default.Register<Helpers.INavigationService<Helpers.NavigationPage>>(() => navigationService);
}
}
I have go through some of tutorial without MvvmLight:-
Navigating between views in WPF / MVVM
Navigation with MVVM
MVVMTest
Each article use different approach and since I'm not familiar with it, once error line appear and no Intellisense suggestion, I cannot continue to find the solutions.
Is it hard to use MVVM in WPF if I have multiple Page and Window screen? Currently, I have a complete WPF App but it use code-behind. I want to move to MVVM since I've read that MVVM is better than code-behind somewhere. MVVM for single page is not a problem as I have done before and it is totally awesome when using MVVM.
Should I retain to use code-behind in WPF if navigation is almost impossible to have workable answer?

Start simple and set navigationservice, viewmodellocators and everything but the basics to one side for now.
I would avoid all those ....locator classes anyhow. They necessarily rely on an anti pattern IMO.
Just use viewmodel first and a single window app.
The basic pattern involves a MainWindow, the Datacontext of which is MainWindowViewModel.
You might want a menu or some such but the part you will switch out is the content of a contentcontrol.
Bind the Content property of your ContentControl to a public object property on mainwindowviewmodel. Call that CurrentViewModel for the sake of discussion.
Define a viewmodel and usercontrol per view you will switch between. Thus HomeView and HomeViewModel, LoginView and LoginViewModel. And so on.
In a resource dictionary, create a datatemplate for each view associating your usercontrol with the type of it's viewmodel.
Merge this resource dictionary in app.xaml.
To navigate.
Instantiate a new viewmodel of the sort you need.
Set CurrentViewModel to that instance.
It will then be templated into UI.
There are numerous variations of this - it's called viewmodel first and you should be able to easily google a few examples.
Here's one I wrote for a slightly different purpose:
https://social.technet.microsoft.com/wiki/contents/articles/52485.wpf-tips-and-tricks-using-contentcontrol-instead-of-frame-and-page-for-navigation.aspx
You can use a similar approach with pages if you really want pages:
A simplified view:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:Page1ViewModel}">
<local:Page1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Page2ViewModel}">
<local:Page2/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox Name="ViewSelect"
ItemsSource="{Binding ViewChoices}"
SelectedItem="{Binding SelectedViewChoice, Mode=TwoWay}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Frame Grid.Column="1"
Content="{Binding SelectedViewChoice.VM}"
NavigationUIVisibility="Hidden"
/>
</Grid>
</Window>
The very much simplified viewmodel.
public class MainWindowViewModel : INotifyPropertyChanged
{
public ObservableCollection<ViewChoice> ViewChoices { get; set; }
= new ObservableCollection<ViewChoice>
{
new ViewChoice{ Name="Page One", VM=new Page1ViewModel()},
new ViewChoice{ Name="Page Two", VM=new Page2ViewModel()},
};
private ViewChoice selectedViewChoice;
public ViewChoice SelectedViewChoice
{
get { return selectedViewChoice; }
set { selectedViewChoice = value; RaisePropertyChanged(); }
}
ps
If you decide to learn prism I would start with delegatecommand and stop there until you have written at least one wpf app.
There's a HUGE slew of functionality in PRISM and most apps don't actually benefit from regions and dynamic composition.
If you prefer mvvmlight ( I do ) then for core you're best getting the source code and using that. You want commandWPF namespace and this has a reliance on net old in the nuget package. The version that does not will not support command canexecute requery well.
I hope that's sufficient info without being overwhelming. Tricky to both be clear and not drown someone with info.

Related

How can I implement "View Model First" using Prism and Unity?

Clarification
I am working with an MVVM solution. I have a 1 to 1 mapping between ViewModels and Views. All solutions I have seen follow a view first approach where the View type is resolved by an IoC container and has a ViewModel as a dependency. I need to reverse that somehow.
Original post:
I am currently trying to refactor a simple database viewing application from Caliburn Micro to Prism (which I am very new to). The application currently utilizes a ViewModel-First approach and the ShellViewModel maintains a list of ViewModels that is bound to a TabControl.
I can not find how to implement a similar approach in Prism. All solutions I have seen use a view first approach, but I have multiple states all mapping to one type of view and need to keep those states separate.
Is there a way I can configure prism to automatically inject a view when a viewmodel is assigned to a region?
Thank you.
Rachel pointed me to a solution in her comment to the original question.
Instead of trying to implement special prism functionality and prism regions, I have gone with a more straight forward MVVM implementation using DataTemplates.
ViewModel outline:
public abstract class ContainerViewModel : BindableBase
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public ItemViewModel ActiveItem { get; set; }
protected virtual void Add(ItemViewModel item) { ... }
protected virtual void Remove(ItemViewModel item) { ... }
protected virtual void Activate(ItemViewModel item) { ... }
}
And XAML:
<TabControl Grid.Column="1" ItemsSource="{Binding Items}" SelectedItem="{Binding ActiveItem}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Table.TableName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:QueryViewModel}">
<local:QueryView />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Have a look at this code project article (ignoring the part about child containers): http://www.codeproject.com/Articles/640573/ViewModel-st-Child-Container-PRISM-Navigation

Using WPF xaml in MVP-VM Events or button click

OK so I am (very) new to WPF but have 13 years with Win-forms, so no newbie.
I am writing a new system and decided to break the ice with WPF using the MVP-VM pattern as I am familiar with MVP.
Now I am also reusing my own (composite) architecture which has the UI or presentation layer in a project separate from the Presenters and View models. One of the main benefits to this approach is that the Presenter layer or Base layer has all the presentation/command/controller logic and no reference to UI matters. The Main IDEA is that this layer has no reference to any UI assemblies like Winforms or WPF.
The ISSUE:
IN Xaml, I have a menu item 'Exit' which I want to bind to the View model. Every example of this that I have seen uses ICommand.. which is housed in Presentation.core... the view model is in the presentation layer and therefore does not have a reference to the Presentation.Core..
The Xaml thus far is
<Window x:Class="Homestead.Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="244" Width="490" xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="23"/>
<RowDefinition Height="*"/>
<RowDefinition Height="23" />
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="Exit" />
</MenuItem>
</Menu>
<StatusBar Grid.Row="2" >
<StatusBarItem >
<TextBlock Text="{Binding Path=StatusMessage}" />
</StatusBarItem>
<StatusBarItem HorizontalAlignment="Right" MinWidth="120">
<TextBlock Text="{Binding Path=UserName}" />
</StatusBarItem>
</StatusBar>
</Grid>
the view model thus far is
public class ShellViewModel : ViewModelBase
{
#region Private Variables
private string _status = string.Empty;
private string _userName;
#endregion
#region Public Properties
public string StatusMessage
{
get { return _status; }
set
{
_status = value;
OnPropertyChanged("StatusMessage");
}
}
public string UserName
{
get { return _userName; }
set
{
_userName = value;
OnPropertyChanged("UserName");
}
}
#endregion
#region Commands
// No reference to ICommand... WTF
#endregion
}
is there any alternative to this pattern..(without using code behind)
How does this work with Menu items added at runtime, amusing there is a way.
Must I redesign or break the design pattern to use WPF? please guide me.
There is one possibility that I can see and that is to Interface out the ViewModel as well as the View.. I have a Controller that creates the Presenters that usually passes in to the presenter the implementation of the view. I.e a typical Presenter constructor would be
public ErrorMessagePresenter(IErrorMessageView view)
{
View = view;
View.Presenter = this;
}
The controller code is thus
public ErrorMessagePresenter CreateErrorPresenter()
{
return new ErrorMessagePresenter(_viewFactory.CreateErrorMessageView());
}
now I know that this is reversed but literally the UI layer is only concerned with UI matters, everything else including navigation is handled in the Base layer or below..
The most common pattern in WPF is Model-View-ViewModel, a pattern in which the presenter has been removed in favor of a more knowledgeable view-model, which is indeed aware of things like ICommand (which is a contract, shared by the view layer as a way to communicate actions, and this justifies the view-model knowing about it).
You can try to force the application not use ICommand by implementing behaviors and data triggers which execute actions in the view-model based on the actions in the UI, but this requires much more coding, not just binding and it is not scalable or sustainable if you intend to continue developing WPF applications in the future (or Windows Store applications for that matter, which commonly use MVVM as well).
I recommend looking into incorporating a framework like MVVM-light in your project using NuGet, so that you can start on top of the foundation of a well-established set of classes, patterns and utilities which have adapted to WPF as a technology for many years instead of trying to hammer the "spherical" framework you are familiar with into the "square" cavity which is WPF (figuratively speaking).
I learned the hard way when I started with it, that trying to bring my experience and habits from WinForms to WPF brings nothing but trouble, so the sooner you start making your peace with the idea that most of what you have done won't be applicable in WPF, the better for your mental sanity.
Of course, patterns are patterns and they can be applied in different ways in a multitude of environments, but the reality is that WPF already has the plugs for certain things to work a certain way and trying to go against that is only going to give you more work as a developer and architect, so it is better to just go with the flow and adapt to the most common way of doing things in the XAML world.
To give you some quick solution options:
1 - System.Windows.Input.ICommand is available if you're targeting a Portable Class Library. This might be possible or not depending on other requirements for your project, but if it is an option, it's really recommendable that you put all your ViewModels and "UI targeting" code in PCLs, since these are by definition the most reusable and platform agnostic type of assemblies you can create in .Net, supporting not only Windows .Net scenarios, but also mobile thru Xamarin.
2 - Abstract the ICommand interface away via dependency injection or Service Locator patterns.
3: Add a reference to PresentationCore.dll but make sure you aren't using anything other than ICommand from there.
To answer your question #2, Menus: ListBoxes, ComboBoxes, TreeViews, and any other items-based UI elements in WPF are derived from the ItemsControl class. It provides an abstract way to dynamically generate UI elements from a collection of data items.
See this blog series for a very comprehensive explanation. The ItemsControl is one of the most powerful WPF features and mastering it can be very rewarding. I have created all sorts of things using it from breadcrumb bars to "Tagging controls" (similar to StackOverflow's tags selection), to chess boards to diagram designers
Basically you will create a class with simple properties representing the Menu Items (with text, an image, and an ICommand to be executed when the menu item is clicked) and put instances of this class into an ObservableCollection<T>, which supports WPF databinding to collections.
On a side note, your Controller creating the presenter and the view and associating the view with the ViewModel and the Presenter approach and all that is not needed. It creates an overly complex, totally unmaintainable scenario where you need to manually do this every time you need to show a View.
WPF resolves that with the use of DataTemplates. See this answer for a very simple usage example.
In fact, the whole concept of a Presenter is not even needed at all in WPF. Since your Views are "glued" to their underlying ViewModels using DataBinding, and there's no need for manual "piping" code or passing data back and forth.

Modal Popup and views communication under MVVM pattern on C#

I have a large project coded with VB6 which I've been trying to upgrade to new technologies for a few months. My project consist on 6 administrative modules reunited under a client-server application. Coming from VB, my logical choice was to upgrade to .NET. After a lot of research I decide to use C#, WPF and the MVVM pattern (with Caliburn Micro).
At the beggining I had some problems, but I managed to resolve them. But now I've come to a point where I need (like every complex application) to communicate with different views and their corresponding viewModel through modal popups (or some other technique). And in this matter the MVVM pattern seems to be very restrictive or complex. A simple "Are you sure you want to delete this record (yes/no)" is a very complex task. So I'm looking for advice as how communicate views without complex artifacts as EventAgregators.
So far the only possible alternative I've found is to use the ModalContentPresenter class from this blog. The problems with this solution are:
I need to write the father view XAML and modal XAML on the same view.
I cannot have multiple popus from same view.
Some examples of where I'd like to use modal popups is:
Put a button on a view to select a Client. It should open a popup with all posible clients and let the user chose one.
Add a product popup to a customer order.
Any ideas or suggestions? Some sample code would be appreciated? Thanks!
I am the author of the linked ModalContentPresenter control so I will attempt to address some of your questions and concerns.
I need to write the father view XAML and modal XAML on the same view.
You can actually write both views in separate files. The views can then be loaded dynamically using DataTemplates which will depend on the ViewModel that is bound to either the Content or ModalContent properties.
See this which describes the general way in which this view switching can be achieved.
You could have a MainViewModel which has two properties, PrimaryViewModel and SecondaryViewModel which return appropriate view models which provide the properties and commands for the main and modal content.
You could have the following setup in XAML:
<DataTemplate DataType="{x:Type FooViewModel}">
<Controls:FooView />
</DataTemplate>
<DataTemplate DataType="{x:Type BarViewModel}">
<Controls:BarView />
</DataTemplate>
<controls:ModalContentPresenter
Name="modalPresenter"
Content={Binding DataContext.PrimaryViewModel}
ModalContent={Binding DataContext.SecondaryViewModel} />
When the IsModalproperty is false, only your PrimaryView will be displayed. As soon as you set the IsModal property to true the ModalContentPresenter will display your SecondaryView.
I cannot have multiple popus from same view.
I take it you mean you want to be able to display different modal content at different times from the same main view.
Using the above technique this is as simple as switching the ViewModel that is bound to the ModalContent property (before displaying it by setting IsModal to true). As long as you have a DataTemplate for the ViewModel that is bound (and your MainViewModel implements INotifyPropertyChanged correctly), the correct content will be displayed.
Some example on where i'd like to use modal popups is:
Put a button on a view to select a Client. It should open a popup with
all possible clients and let the user chose one.
Add a product popup to a customer order.
Once you understand the technique described above you should be able to see that the as long as you have a View and ViewModel pair you can cover any scenario you can think of.
As an example, consider viewModels that have the following interfaces:
public interface SelectCustomerViewModel : INotifyPropertyChanged {
event EventHandler CustomerSelected;
public ObservableCollection<Customer> Customers { get; }
public Customer Customer { get; set; }
public Command CustomerSelectedCommand { get; }
}
public interface MainViewModel : INotifyPropertyChanged {
public SelectCustomerViewModel ModalContent { get; }
public Command SelectCustomerCommand { get; }
public bool IsSelectingCustomer { get; }
}
You could have XAML that looks something like this:
<Window x:Class="ModalContentTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Select a customer">
<DataContext>
<vm:MainViewModel />
</DataContext>
<DataTemplate DataType="{x:Type SelectCustomerViewModel}">
<Controls:SelectCustomerView />
</DataTemplate>
<c:ModalContentPresenter Name="modalPresenter"
ModalContent={Binding ModalContent}
IsModal={Binding IsSelectingCustomer}>
<!-- This is the primary content! -->
<Grid>
<Button Content="Select a customer"
Command={Binding SelectCustomerCommand} />
</Grid>
</c:ModalContentPresenter>
</Window>
Here's how it works:
The IsSelectingCustomer property of the MainViewModel would start off as false.
Clicking the button in the main view would invoke the SelectCustomerCommand object. The command would then tell the MainViewModel to change the IsSelectingCustomer property to true.
The ModalContentPresenter would display the view specified by the data template. The user can now only interact with the 'select customer view'.
Once a customer has been selected, a button can be clicked (which is bound to the CustomerSelectedCommand of the SelectCustomerViewModel) which in turn would raise the CustomerSelected event.
The MainViewModel would have an event handler that would respond to the CustomerSelected event. The handler would then read the SelectedCustomer property from the SelectCustomerViewModel and finally, it would set the IsSelectingCustomer property back to false, causing the modal content to be closed.

WPF Navigate through views using MVVM pattern

I'm building my first WPF using MVVM pattern. With the help of this community, I manage to create my Model, my first ViewModel and view. Now I want to add some complexity to the app designing the basic application layout interface. My idea is to have at least 2 child views and one main view and separate them on several XAML:
Main.XAML
Products.XAML
Clients.XAML
Main will have a menu and a space to load child views (Products and Clients). Now following MVVM pattern all the navigation logic between views should be write on a ViewModel. So mi idea is to have 4 ViewModels:
MainViewModel
ProductsViewModel
ClientsViewModel
NavigationViewModel
So NavigationViewModel should contain a collection of child viewmodels? and an active viewmodel is that right?
So my questions are:
1) How can I load different views (Products, Clients) on Main view using MVVM pattern?
2) How do I implement navigation viewModel?
3) How can I control the max number of open or active views?
4) How can I switch between open views?
I have been doing a lot of search and reading and couldn't find any simple working example of MVVM navigation with WPF that loads multiple views inside a main view. Many of then:
1) Use external toolkit, which I don't want to use right now.
2) Put all the code for creating all the views in a single XAML file, which doesn't seems like a good idea because I need to implement near 80 views!
I'm in the right path here? Any help, especially with some code will be appreciated.
UPDATE
So, I build a test project following #LordTakkera advices, but get stuck. This is how my solution looks like:
I create:
Two Models (Clients and Products)
One MainWindow and two wpf user controls(Clients and Products) XAML.
Three ViewModels (Clients, Products and Main ViewModel)
Then I set dataContext on each view to corresponding viewModel. After that I create MainWindow with the ContentPresenter like this and bind it to a property of the viewmodel.
MainWindow.XAML
<Window x:Class="PruevaMVVMNavNew.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="519" Width="890">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.ColumnSpan="2" Background="AntiqueWhite" ></Border>
<Border Grid.Row="1" Grid.RowSpan="2" Background="AliceBlue"></Border>
<Border Grid.Row="1" Grid.Column="1" Background="CadetBlue"></Border>
<ContentPresenter Grid.Row="1" Grid.Column="1" x:Name="ContentArea" Content="{Binding CurrentView}"/>
<StackPanel Margin="5" Grid.Column="0" Grid.Row="1">
<Button>Clients</Button>
<Button>Products</Button>
</StackPanel>
</Grid>
And also this is viewmodel from MainWindow:
class Main_ViewModel : BaseViewModel
{
public Main_ViewModel()
{
CurrentView = new Clients();
}
private UserControl _currentView;
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
So this load by default clients view and looks like this (which is just right!):
So I suppose I need a way to relate the buttons on the left, with a certain viemodel and then bind them with CurrentView Property of Main viewModel. How can I do that?
UPDATE2
According to #LordTakkera advice I modify my main viewModel this way:
class Main_ViewModel : BaseViewModel
{
public ICommand SwitchViewsCommand { get; private set; }
public Main_ViewModel()
{
//CurrentView = new Clients();
SwitchViewsCommand = new RelayCommand((parameter) => CurrentView = (UserControl)Activator.CreateInstance(parameter as Type));
}
private UserControl _currentView;
public UserControl CurrentView
{
get
{
return _currentView;
}
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
I use RelayCommand instead of DelegateCommand but I think it works the same way. The command is executed when I hit the buttons and the type parameter string its ok but i get this error:
Translation: Value cannot be null. Parameter name: type. Suggestion use New keyword to create object instance
I don't know where to put the New keyword. I have try on CommandParameter but it wont work. Any idea? Thanks
UPDATE 3
After all the advices and help received here, and a lot of work, here is my final navigation menu and the base for my application interface.
I'm not sure you need a separate "navigation" view model, you could easily put it into the main. Either way:
To separate your "child" views, I would use a simple ContentPresenter on your "main" view:
<ContentPresenter Content="{Binding CurrentView}"/>
The easiest way to implement the backing property is to make it a UserControl, though some would argue that doing so violates MVVM (since the ViewModel is now dependent on a "View" class). You could make it an object, but you lose some type safety. Each view would be a UserControl in this case.
To switch between them, you are going to need some sort of selection control. I've done this with radio buttons before, you bind them like so:
<RadioButton Content="View 1" IsChecked="{Binding Path=CurrentView, Converter={StaticResource InstanceEqualsConverter}, ConverterParameter={x:Type views:View1}"/>
The converter is pretty simple, in "Convert" it just checks if the current control is a type of the parameter, in "ConvertBack" it returns a new instance of the parameter.
public class InstanceEqualsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (parameter as Type).IsInstanceOfType(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value ? Activator.CreateInstance(parameter as Type) : Binding.DoNothing;
}
}
Binding to a combobox or other selection control would follow a similar pattern.
Of course you could also use DataTemplates (with a selector, unfortunately not something I have done before) and load them into your resources using merged dictionaries (allowing separate XAML). I personally prefer the user control route, pick which is best for you!
This approach is "one view at a time". It would be relatively easy to convert to multiple views (your UserControl becomes a collection of user controls, use .Contains in the converter etc.).
To do this with buttons, I would use commands and take advantage of the CommandParameter.
The button XAML would look like:
<Button ... Command={Binding SwitchViewsCommand} CommandParameter={x:Type local:ClientsView}/>
Then you have a delegate command (tutorial here) that runs the activator code from the converter:
public ICommand SwitchViewsCommand {get; private set;}
public MainViewModel()
{
SwitchViewsCommand = new DelegateCommand((parameter) => CurrentView = Activator.CreateInstance(parameter as Type));
}
That is off the top of my head, but should be pretty close. Let me know how it goes!
Let me know if I provide any further information!
Update:
To answer your concerns:
Yes, each time you push the button a new instance of the view is created. You could easily fix this by holding a Dictionary<Type, UserControl> that has pre-created views and index into it. For that matter, you could use a Dictonary<String, UserControl> and use simple strings as the converter parameters. The disadvantage is that your ViewModel becomes tightly coupled to the kinds of views it can present (since it has to populate said Dictionary).
The class should get disposed, as long as no one else holds a reference to it (think event handlers that it registered for).
As you point out, only one view is created at a time so you shouldn't need to worry about memory. You are, of course, calling a constructor but that isn't THAT expensive, particularly on modern computers where we tend to have plenty of CPU time to spare. As always, the answer to performance questions is "Benchmark it" because only you have access to the intended deployment targets and entire source to see what actually performs the best.
IMHO the best choose for you is to use MVVM framework (PRISM, MMVM Light, Chinch, etc) because navigation is already implemented. If you want to create your own navigation - try DataTemplate.

UI design using MVVM pattern

I'm trying to choose the best way to implement this UI in MVVM manner. I'm new to WPF (like 2 month's) but I have huge WinForms experience.
The ListBox here act's like a TabControl (so it switches the view to the right), and contains basically the Type of item's displayed in tables. All UI is dynamic (ListBox items, TabItems and Columns are determined during run-time). The application is targeting WPF and Silverlight.
Classes we need for ViewModel:
public abstract class ViewModel : INotifyPropertyChanged {}
public abstract class ContainerViewModel : ViewModel
{
public IList<ViewModel> Workspaces {get;set;}
public ViewModel ActiveWorkspace {get;set;}
}
public class ListViewModel<TItem> where TItem : class
{
public IList<TItem> ItemList { get; set; }
public TItem ActiveItem { get; set; }
public IList<TItem> SelectedItems { get; set; }
}
public class TableViewModel<TItem> : ListViewModel<TItem> where TItem : class
{
public Ilist<ColumnDescription> ColumnList { get; set; }
}
Now the question is how to wire this to View.
There are 2 base approaches I can see here:
With XAML: due to lack of Generics support in XAML, I will lose strong typing.
Without XAML: I can reuse same ListView<T> : UserControl.
Next, how to wire data, I see 3 methods here (with XAML or without doesn't matter here). As there is no simple DataBinding to DataGrid's Columns or TabControl's TabItems the methods I see, are:
Use DataBinding with IValueConverter: I think this will not work with WPF|Silverlight out of the box control's, as some properties I need are read-only or unbindable in duplex way. (I'm not sure about this, but I feel like it will not work).
Use manual logic by subscribing to INotifyPropertyChanged in View: ViewModel.PropertyChanged+= ....ViewModel.ColumnList.CollectionChanged+= ....
Use custom controll's that support this binding: Code by myself or find 3d party controls that support this binding's (I don't like this option, my WPF skill is too low to code this myself, and I doubt I will find free controls)
Update: 28.02.2011
Things get worser and worser, I decided to use TreeView instead of ListBox, and it was a nightmare. As you probably guess TreeView.SelectedItems is a readonly property so no data binding for it. Ummm all right, let's do it the old way and subscribe to event's to sync view with viewmodel. At this point a suddenly discovered that DisplayMemberPath does nothing for TreeView (ummmm all right let's make it old way ToString()). Then in View's method I try to sync ViewModel.SelectedItem with TreeView's:
private void UpdateTreeViewSelectedItem()
{
//uiCategorySelector.SelectedItem = ReadOnly....
//((TreeViewItem) uiCategorySelector.Items[uiCategorySelector.Items.IndexOf(Model.ActiveCategory)]).IsSelected = true;
// Will not work Items's are not TreeViewItem but Category object......
//((TreeViewItem) uiCategorySelector.ItemContainerGenerator.ContainerFromItem(Model.ActiveCategory)).IsSelected = true;
//Doesn't work too.... NULL // Changind DataContext=Model and Model = new MainViewModel line order doesn't matter.
//Allright.. figure this out later...
}
And none of methods I was able to think of worked....
And here is the link to my sample project demonstrating Control Library Hell with MVVM: http://cid-b73623db14413608.office.live.com/self.aspx/.Public/MVVMDemo.zip
Maciek's answer is actually even more complicated than it needs to be. You don't need template selectors at all. To create a heterogeneous tab control:
Create a view model class for each type of view that you want to appear as tab items. Make sure each class implements a Text property that contains the text that you want to appear in the tab for its item.
Create a DataTemplate for each view model class, with DataType set to the class's type, and put the template in the resource dictionary.
Populate a collection with instances of your view models.
Create a TabControl and bind its ItemsSource to this collection, and add an ItemTemplate that displays the Text property for each item.
Here's an example that doesn't use view models (and that doesn't implement a Text property either, because the objects I'm binding to are simple CLR types), but shows how template selection works in this context:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:coll="clr-namespace:System.Collections;assembly=mscorlib">
<DockPanel>
<DockPanel.Resources>
<coll:ArrayList x:Key="Data">
<sys:String>This is a string.</sys:String>
<sys:Int32>12345</sys:Int32>
<sys:Decimal>23456.78</sys:Decimal>
</coll:ArrayList>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Int32}">
<StackPanel Orientation="Horizontal">
<TextBlock>This is an Int32:</TextBlock>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Decimal}">
<StackPanel Orientation="Horizontal">
<TextBlock>This is a Decimal: </TextBlock>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</DockPanel.Resources>
<TabControl ItemsSource="{StaticResource Data}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</DockPanel>
</Page>
Of course in a real MVVM application those DataTemplates would use UserControls to map each type to its view:
<DataTemplate DataType="{x:Type my:ViewModel}">
<my:View DataContext="{Binding}"/>
</DataTemplate>
Maciek and Robert already gave you some ideas on how to implement this.
For the specifics of binding the columns of the DataGrid I strongly recommend Meleak's answer to that question.
Similar to that you can use attached properties (or Behaviors) and still maintain a clean ViewModel in MVVM.
I know the learning curve for WPF is quite steep and you're struggling already. I also know that the following suggestion doesn't help that and even makes that curve steeper. But your scenario is complex enough that I'd recommend to use PRISM.
I wrote an article and a sample application with source code available, where I discuss and show the problems I have mentioned here and how to solve them.
http://alexburtsev.wordpress.com/2011/03/05/mvvm-pattern-in-silverlight-and-wpf/
In order to connect your ViewModel to your View you need to assign the View's DataContext. This is normally done in the View's Constructor.
public View()
{
DataContext = new ViewModel();
}
If you'd like to see your view model's effect at design time, you need to declare it in XAML, in the View's resources, assign a key to it, and then set the target's DataContext via a StaticResource.
<UserControl
xmlns:vm="clr-namespace:MyViewModels
>
<UserControl.Resources>
<vm:MyViewModel x:Key="MyVM"/>
</UserControl.Resources>
<MyControl DataContext={StaticResource MyVM}/>
</UserControl>
(The above is to demonstrate the design-time trick works)
Since you're dealing with a scenario that includes a container such as the TabControl I'd advocate considering the following things :
Hold your child ViewModels in a Property of type ObservableCollection
Bind the TabControls ItemsSource to that property
Create a new View that derives from TabItem
Use a template selector to automatically pick the type of the view based on the type of the view model.
Add IDisposable to yoour child ViewModels and create functionality to close the views.
Hope that helps a bit, if you have further questions let me know.

Categories

Resources