DataTemplates and DataContext inheritance - c#

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

Related

Wire multiple UserControls with separate ViewModels in MainWindow

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

Wpf call function of ContentControl view

I am pretty new to C# so I am just starting to learn the basics. Right now I have a ContentControl inside a Window like this:
<ContentControl Content="{Binding}" x:Name="SubView"/>
And I configured my resources of the Windows like this:
<Window.Resources>
<DataTemplate x:Name="StammdatenViewTemplate" DataType="{x:Type viewmodels:StammdatenViewModel}">
<views:StammdatenView DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="AdministrationViewTemplate" DataType="{x:Type viewmodels:AdministrationViewModel}">
<views:AdministrationView DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
In my Window class I am setting the DataContext like this:
DataContext = new StammdatenViewModel();
Here is the thing I would like to do. I want to disable all the TextBoxes inside the ContentControl. I thought about adding a function to my StammdatenView.xaml.cs class (which is the class of my subview), then firing the event from the Window somehow. Though I would need access to the function inside the subview. Is that somehow possible and if yes how? Or would anyone suggest a different approach?
Thanks in advance.
Or would anyone suggest a different approach?
Yes. You should bind the IsEnabled property of each TextBox in the StammdatenView to a boolean property of the StammdatenViewModel.
You can then disable the TextBoxes by setting the source property in the view model class. This is one of the key aspects of the MVVM design pattern, i.e. that you handle your application logic in the view model.
Make sure that the view model class implements the INotifyPropertyChanged interface and provide change notifications as explained on MSDN.
You can create a INotifyPropertyChanged event on your view model, then bind it to the 'IsEnabled={Binding IsTextBoxEnabled}' attribute in your view template for the textbox.
public class ViewModel : BaseViewModel
{
private bool _isTextBoxEnabled;
public bool IsTextBoxEnabled
{
get { return _isTextBoxEnabled; }
set
{
if (value != _isTextBoxEnabled)
_isTextBoxEnabled = value;
this.RaisePropertyChanged("IsTextBoxEnabled");
}
}
}
XAML
<DataTemplate x:Key="template">
<StackPanel Orientation="Horizontal" DataContext="{Binding}">
<TextBox IsEnabled="{Binding IsTextBoxEnabled}" />
</StackPanel>
</DataTemplate>

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.

Don't create new view each time with DataTemplate/DataType

I have something like this:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<!-- View 1 Here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<!-- View 2 here -->
</DataTemplate>
<Window.Resources>
<ContentPresenter Content="{Binding}"/>
</Window>
This will automatically swap out the view as I bind different viewmodels, which is very handy.
However, I have one view with a tabcontrol and many subviews. Each subview has several visual parts that are configured by a custom xml file (complex business case). Each time this view is created, the xml file is parsed which causes a small (1-2 second) delay. It's enough of a delay to be annoying and make the UI feel sluggish.
Is there a way to use the DataTemplate pattern without destroying and recreating the view each time a viewmodel is bound? I'd rather not change the viewmodel if possible.
For this case the easiest solution is to have the two views always there and change which one is visible. You can use a converter to change the visibility based on the type of the data context
<View1 Visibility="{Binding Converter={StaticResource TypeToVisibilityConverter, ConverterParameter=VM1}" />
<View2 Visibility="{Binding Converter={StaticResource TypeToVisibilityConverter, ConverterParameter=VM2}" />
And the converter will check if the type matches with the parameter to return Visible, or Collapsed otherwise.
You could wrap your VM into an additional class. Your DataTemplates will decide on the type of the Wrapper class but the real implementation will be exposer through a property of this Wrapper. When this property will change the DataTemplate wont be reloaded but all the bindings will be refreshed.
Wrapper class:
public class WrapperVM1:INotifyPropertyChanged
{
public Content VM1 { get{...} set{...} }
}
public class WrapperVM2:INotifyPropertyChanged
{
public Content VM2 { get{...} set{...} }
}
Now your data templates will describe wrapper class representations:
<DataTemplate DataType="{x:Type local:WrapperVM1}">
<TextBlock Text={Binding Content.SomPropertyInVM1}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:WrapperVM2}">
<TextBlock Text={Binding Content.SomPropertyInVM2}"/>
</DataTemplate>
As you can see if you substitute the Content property of the wrapper with a new instance of VM this won't recreate the view but all bindings will update. However if you need to switch to other type of VM you will have to substitute the Wrapper class by the appropriate Wrapper.

WPF MVVM ItemsControl with Multiple ViewModels depending on the object type

I have a collection that holds multiple types of items that all inherit from the same interface. This is bound to an ItemsControl. The DataContext of the window is set to the ViewModel that holds the collection.
What I would like to do is have each item of a different type in the collection use a different ViewModel.
So if my templates in the itemscontrol are setup like below I would like to have the first template have a DataContext of ViewModel1 and the second have a DataContext of ViewModel2. I can't set the DataContext directly on them because the ItemsControl will set the DataContext to the item.
Anyone know a solution to this, or a better way to do it using MVVM?
<DataTemplate DataType="{x:Type Models:ItemType1}">
<Controls:MyControl Text="{Binding Body}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type Models:ItemType2}">
<Controls:MyControl2 Text="{Binding Body}"/>
</DataTemplate>
Why don't you just expose a collection of ViewModels ?
var viewModels = new ObservableCollection<ViewModelBase>(items.Select(CreateViewModelFromItem));
private ViewModelBase CreateViewModelFromItem(Item item)
{
if (item is ItemType1) return new ViewModel1((ItemType1)item);
if (item is ItemType2) return new ViewModel2((ItemType2)item);
...
throw new ArgumentException("Unknown model type");
}
And change your datatemplates to bind to the viewmodels, not the items...
Maybe you could use a ValueConverter that would generate the correct ViewModel :
<DataTemplate DataType="{x:Type Models:ItemType1}">
<Controls:MyControl DataContext="{Binding Converter=ViewModelLocator}" Text="{Binding Body}"/>
</DataTemplate>
Not sure if it could possibly work but it's worth a try.

Categories

Resources