Wire multiple UserControls with separate ViewModels in MainWindow - c#

I’m tyring to create an WPF MVVM application to edit music albums (cue sheets) with a waveform display. The MainWindow should have the follwing design:
At the top of the MainWindow will be a MenuBar.
Below a display of the audio waveform
Below a display of the audio tracks of the album.
To make the MainWindow.xaml not so big and have some kind of functional separation I decided to create separate view models and views for the waveform display and the album/tracks display. The separate views I’ve created as WPF UserControls each.
In app.xaml the views and view models are connected together:
<Application.Resources>
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:Main/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CueAlbumViewModel}">
<Views:CueAlbum/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:WaveFormViewModel}">
<Views:Waveform/>
</DataTemplate>
</Application.Resources>
The MainWindow has set the MainViewModel as DataContext:
<Window.DataContext>
<ViewModels:MainViewModel/>
</Window.DataContext>
The bindings of MainWindow <-> MainViewModel are working so far.
But what's not working is the binding of the UserControls to their custom DataSources in MainWindow. E.g. for the view CueAlbum I tried it this way:
<Views:CueAlbum Grid.Row="1"
DataContext="{Binding CueAlbumViewModel}"/>
But the connection isn't done properly. The constructor of CueAlbumViewModel isn't called at all.
So, can this be achieved properly in WPF / MVVM without code-behind in the view?
Or is using user controls with own DataContexts in MainWindow bad design at all?
(Another question that might arise after this is solved is how to set the Buttons of the MainWindows ToolbarTray to the dedicated UserContols..)

Related

Catel framework - replace view

I'm building application with multiple modules. Each of module contains a viewmodel and view.
I would like to create solution, for automatically show SelectedModule in a part of my application.
Assume that I have MainWindow view like this:
<catel:UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com" xmlns:dxsch="http://schemas.devexpress.com/winfx/2008/xaml/scheduler"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
x:Class="OrchestraCatel.Views.MainWindow">
<DockPanel LastChildFill="True">
<ContentControl x:Name="Ribbon" DockPanel.Dock="Top">
<!-- My Ribbon-->
</ContentControl>
<ContentControl x:Name="MainView" DockPanel.Dock="Top">
<!-- My Main Page-->
</ContentControl>
</DockPanel>
</catel:UserControl>
And I would like to show a UserControl specified in another DLL inside MainView ContentControl after Ribbon item click.
Both, Ribbon Button and MainView view are in module DLL
I had an idea to include SelectedViewModel inside my MainPage ViewModel, and then, create DataTemplates for each ViewModel, but this solution will break my vision of Modules, which should be independent.
Is there a way in Catel to replace a view with another one? From anywhere?
I think Prism solves this issue for you. Please take a look at the integration of Prism with Catel:
https://catelproject.atlassian.net/wiki/display/CTL/Catel.Extensions.Prism

DataTemplates and DataContext inheritance

I have the following bits of XAML:
Window.DataContext>
<viewModel:ProductBuilderVm/>
</Window.DataContext>
<UNNECESSARY DETAILS SNIP>
<TabControl ItemsSource="{Binding TabViewModels}" SelectedItem="{Binding SelectedTabVm}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewModel:ProductDefinitionVm}">
<view:ProductDefinition></view:ProductDefinition>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:CouplingsViewVm}">
<view:CouplingsView></view:CouplingsView>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:RulesVm}">
<view:RulesView></view:RulesView>
</DataTemplate>
</TabControl.Resources>
</TabControl>
My idea here was to create 1 main ViewModel (ProductBuilderVm) to serve as the 'parent' to the three ViewModels in the TabControl. I thought I had this working, but now that I'm trying to communicate things between these child Vm's, I came to the realization that they're each a separate instance with no relation to each other.
How can I refactor this window to have 1 ProductBuilderVm, with 3 child Vm's beneath it? The goal is to pull data from the first child for use in the second.
Thanks!
The easiest way I can think of to achieve passing values between ViewModels is to use something like the MVVM Light Messenger class: https://msdn.microsoft.com/en-us/magazine/dn745866.aspx.
You register each ViewModel to receive messages:
//Register for custom typed message
Messenger.Default.Register<MyMessageType>(this, OnMyMessageTypeReceived);
//Register for string messages
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
Then you can send a Message to a ViewModel using:
//String message
Messenger.Default.Send(new NotificationMessage("SetupMyProductDefinitionVm"));
//Custom typed message sent to ViewModel of specific type
var myMsg = new MyMessageType();
Messenger.Default.Send<MyMessageType, ProductDefinitionVm>(myMsg);

WPF MVVM Navigation Technique

I know there are a lot of questions about WPF navigation, for application developed with MVVM pattern, and I have read tens and tens of answers but I'm missing probably something.
I started building an application following Rachel's article here. All works just fine, there's an ApplicationView Window with this XAML:
<Window x:Class="CashFlow.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CashFlow.ViewModels"
xmlns:v="clr-namespace:CashFlow.Views"
Title="ApplicationView" Height="350" Width="600" WindowStartupLocation="CenterScreen">
<Window.Resources>
<!--Here the associations between ViewModels and Views-->
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<v:HomeView />
</DataTemplate>
</Window.Resources>
<!--Define here the application UI structure-->
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"
Margin="2,5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
The ApplicationViewModel, that is set as DataContext for this window when the application starts, maintains an ObservableCollection of my ViewModels. Thanks to data templates, it's possible to associate every view with its viewmodel, using a ContentControl to render the views. Navigation in this case is accomplished with a "side bar" of buttons, binded to ApplicationViewModel commands that perform the changes of CurrentPageViewModel object.
I'm wondering how I can perform navigation without the presence of that sidebar of Buttons. Having only the Content control, I should be able to change the CurrentPageViewModel from the others viewmodel? Probably the answer will be very trivial, but I can't see that right now.
Your top level homeviewmodel can orchestrate navigation via an eventbus pattern. To use eventbus, you would inject an object that tracks objects that want to be notified of events. Then when a view model raises an event, the homeviewmodel receives it and performs the currentpageviewmodel assignment that will navigate you to the next viewmodel.
Ex:
Messenger defines two methods - RegisterForEvent<IEvent>(ViewModel aViewModel), and RaiseEvent(IEvent event).
So you would define a function to subscribe to the events -
HomeViewModel.cs
...
void SubscribeForEvents() {
Messenger.RegisterForEvent<NavigationEvent>(this);
}
Then you inject the Messenger into your other view models, and from those view models, raise the event:
Messenger.RaiseEvent(new NavigationEvent { TargetViewModel = new TargetViewModel() });
Where the event is something like
public class NavigationEvent : IEvent {
ViewModel TargetViewModel { get;set;}
}
C Bauer is right with what you are missing. I found in order to switch the data context, you'll need a messenger service to flag your "applicationviewmodel" to switch its data context. A good discussion with the steps you need are spelled out in a discussion here.
Register the message to be received in your applicationviewmodel, then handle the data context switch in your receive message function.
Also, this might be true or not, but I had to use 1 window, with multiple user controls as opposed to multiple windows if I wanted to have 1 window showing at all times. Lastly, I followed Sheridan's example and defined my data templates in my app.xaml as opposed to the window itself.

WPF set DataTemplate on Viewmodel class programmatically

I have a listbox in WPF that will contain a list of ResultsViewModel items, however the actual runtime type of these objects could be
CalculateResultsViewModel,
ScenarioResultsViewModel,
GraphResultsviewModel etc etc,
all of which extend the base abstract class ResultsViewModel.
Each of these view models should be rendered differently in the ListBox so needs a different DataTemplate. I can do that just with XAML easy enough. The difficulty is that when the viewmodels are either "processing" or when they have failed", I need them to display a DataTemplate for "processing" or "errored" which I can only so far do with Triggers. That however then means I can't use the DataTemplateSelector or a basic XAML style.
The only solution I can think of (not clean I know) is to set the DataTemplate programmatically in the SetResult() method of each viewmodel class, which is what gets called when the processing completes either successfully or with an error. In that DependencyProperty I can look at the return code and then programatically set the DataTemplate depending on the sucess/failure result. The only problem is I cannot figure out how to
Obtain a DataTemplate resource from a ResourceDictionary just using c# code - bearing in mind Im calling all of this from the viewmodel class, not the window code-behind .xaml.cs file so it doesn't have access to the properties of Window
having only a handle to the viewmodel class, somehow obtain a reference to the ListBoxItem that contains it and then programmatically set the DataTemplate on this container.
Can anyone point me in the right direction?
you can take the magic with implicit datatemplates
<ListBox ItemSource={Binding YourResults}>
<ListBox.Resources>
<DataTemplate DataType={x:Type CalculateResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type ScenarioResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type GraphResultsviewModel }>
<Grid></Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
for "processing" or "errored" viewmodels you can specify a adorner overlay in all yout datatemplates (ok but you must use the triggers)
hope this helps

Changing Main Viewing Area

I'm wondering how to go about creating different views in the main window when a button is pressed. I'm not sure of the correct terminology, so that has hampered my google fu.
I'm thinking that the main viewing area would be a content control, that I could change when a event happens. I made a small drawing to help illustrate my idea / thought.
Any input will be appreciated. Thanks!
It would be really easy to implement this senario using MVVM approach....
Make a ViewModel for you MainView. Then Define Properties of the ViewModels of your UserControls
For Example You have Two UserControl as FirstView and SecondView then make a properties in your viewmodels as ViewToLoadProperty of the type ViewModel (usually called as ViewModelBase)
Set bindings as
<!-- Panel For Hosting UserControls -->
<Border Grid.Column="2">
<ContentControl Name="userControlContentControl"
Content="{Binding Path=ViewToLoadProperty,
}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type ViewModelLayer:FirstViewModel}">
<ViewLayer:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelLayer:SecondViewModel}">
<ViewLayer:SecondView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
<!-- Panel For Hosting UserControls -->
Then when you click the button Use a command to set the respective ViewModel Intance to this(ViewToLoadProperty) property...(Use RelayCommannds or something like it)
DataTempates would do the rest of the job by selecting the right View according to the right type of ViewModel
YOu can use MVVMLight toolkit if you are implementing MVVM Pattern.. :)
On the right you could have a frame. Then the button would bind a different page or user control to the content of that frame.

Categories

Resources