Setting the datacontext of all sub-views in parent view - c#

I have a XAML file of the form
CTC.XAML
<UserControl x:Class="KPI.CTC.UI.CTC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:KPI.CTC.UI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TabControl Grid.Row="1">
<TabItem Header="Industry">
<uc:Industry DataContext="{}"/>
</TabItem>
<TabItem Header="Templates">
<uc:Templates DataContext="{}"/>
</TabItem>
<TabItem Header="Item Calcs">
<uc:ItemCalcs DataContext="{}"/>
</TabItem>
</TabControl>
</UserControl>
where Industry.xaml,Templates.xaml and ItemCalcs.xaml have respective ViewModels in a separate namespace KPI.CTC.ViewModel. I wished to know is it possible to set the DataContext of all the 3 views in CTC.xaml only. I do not want to perform the tedious task of setting the DataContext separately for individual views.
Thanks in advance!!

If you look at the FrameworkElement.DataContext Property page on MSDN, you should see a section named Dependency Property Information:
This tells us that the DataContext DependencyProperty has a FrameworkPropertyMetadata value of Inherits, which means that the DataContext property will automatically be inherited by child controls. Therefore, if you had wanted to set the DataContext of all of the child controls to the same value as the parent, then this would happen automatically, but this is the only situation where this would happen.
So unless your situation is as above, you're going to have to write some code somewhere... the only question is where. Perhaps the simplest method is for you to use DataTemplates, although this will only work if you are data binding an instance of a different type of object for each user control. Try adding these DataTemplates into your Application.Resources:
<DataTemplate DataType="{x:Type ViewModels:IndustryViewModel}">
<Views:Industry />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:ItemCalcsViewModel}">
<Views:ItemCalcs />
</DataTemplate>
Using this method will free you from ever having to manually set a DataContext property. Just add a new DataTemplate for each view/view model pair that you have and then you can display your views/UserControls like this:
<ContentControl Content="{Binding ViewModelIntanceProperty}" />

There are plenty of ways to do it and it depends on your application architecture, complexity, etc. Some involve using so-called IoC containers.
The three simplest would be:
Declare VMs directly in XAML:
<TabItem Header="Industry">
<uc:Industry>
<uc:Industry.DataContext>
<vm:IndustryViewModel />
</uc:Industry.DataContext>
</uc:Industry>
</TabItem>
etc.
Declare VMs as 'main' ViewModel properties:
public IndustryViewModel IndustryViewModel
{
get;
set;
}
etc.
If TabControl ViewModels inherit from one base class, you may delcare a Dictionary
in 'main' VM containing the 3 TabControl ViewModels:
Dictionary<string, ViewModelBase> ViewModels
{
get;
set;
}
ViewModels = new Dictionary<string, ViewModelBase>();
ViewModels.Add("Industry", new IndustryViewModel());
// and so on...
and assign it in XAML:
<TabItem Header="Industry">
<uc:Industry DataContext="{Binding ViewModels[Industry]}"/>
</TabItem>

Related

Calibrun.Micro Sample/Feature/UWP : What is "cm:Bind.Model="{Binding}"

Here is Calibrun.Micro example with UWP.
in Bubbling sample, in this file,
there is this line
<Grid cm:Bind.Model="{Binding}">
What is mean ? Why does it necessary ?
I thought Next line is enough for send $dataContext
<Button x:Name="Message" cm:Message.Attach="SelectPhrase($dataContext)" Margin="0,12" />
Please advice me....
Let's analyze the code as quite some things are a bit obscure in Caliburn.Micro as it works through convention:
<Page
x:Class="Features.CrossPlatform.Views.BubblingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cm="using:Caliburn.Micro"
mc:Ignorable="d">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="actions" Style="{StaticResource SubheaderTextBlockStyle}" Margin="40,10,40,0"/>
<StackPanel Margin="40,20">
<ItemsControl x:Name="Phrases">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid cm:Bind.Model="{Binding}">
<Button x:Name="Message" cm:Message.Attach="SelectPhrase($dataContext)" Margin="0,12" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</Page>
First of all, the page's datacontext (viewmodel) is set through naming convetion. This viewmodel can be found here. As you can see, this viewmodel has a Phrases property and a SelectPhrase(MessageActivityViewModel phrase) method.
If we go down the tree, we have an <ItemsControl x:Name="Phrases"> which binds to the Phrases property, which is a collection of MessageActivityViewModel.
An ItemsControl needs a way to present the items, which is defined in the DataTemplate. This template has a Grid as root object. If the Grid would not use {Binding}, it would inherit the datacontext from the template, which is the current element being rendered (a single MessageActivityViewModel). However, we want to call the SelectPhrase method on the BubblingViewModel and not on the MessageActivityViewModel. To be able to do that, we use {Binding} to tell the Grid binds to the page's viewmodel instead of to the single rendered MessageActivityViewModel.
What the Button does, is sending the datacontext object (being the rendered MessageActivityViewModel item) back to your viewmodel.
$dataContext:
Passes the DataContext of the element that the ActionMessage is attached to. This is very useful in Master/Detail scenarios where the ActionMessage may bubble to a parent VM but needs to carry with it the child instance to be acted upon.
Source: http://caliburnmicro.com/documentation/cheat-sheet

MVVM - datatemplate creates a new view

I have two DataTemplates that gets switched depending on the current ViewModel. However whenever I switch my ViewModel, it seems to call the respective View's constructor and calls the InitializeComponent() call within the constructor, which means that whenever I switch the DataTemplate, it generates a new view that is bound to the respective DataTemplate. I am not sure why this is happening but is there a way to prevent the creation of a new View when switching ViewModels?
Below is the DataTemplates located at my MainView.
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstPanelViewModel}">
<views:FirstPanelView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondPanelViewModel}">
<views:SecondPanelView />
</DataTemplate>
</Window.Resources>
The template is being displayed in a ContentControl.
<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" />
This is my SecondPanelView which is the same as my FirstPanelView, it's very simple.
public partial class FirstPanelView
{
public FirstPanelView()
{
InitializeComponent();
}
}
public partial class SecondPanelView
{
public SecondPanelView()
{
InitializeComponent();
}
}
My Ioc makes sure that I generate only one instance of the SecondPanelView
container.Register<IFirstPanelViewModel, FirstPanelViewModel>(new PerContainerLifetime())
container.Register<ISecondPanelViewModel, SecondPanelViewModel>(new PerContainerLifetime());
DataContext is being bounded in each view by a custom markup extension.
DataContext="{Binding Source={common:Locate}, Path=FirstPanelViewModel}"
DataContext="{Binding Source={common:Locate}, Path=SecondPanelViewModel}"
Which is just calling GetInstance of the respective ViewModel.
public IFirstViewModel FirstViewModel
{
get { return _container.GetInstance<IFirstPanelViewModel>(); }
}
public ISecondViewModel SecondViewModel
{
get { return _container.GetInstance<ISecondPanelViewModel>(); }
}
This is an old issue, but I was also struggling with this issue. The answer is to place the view instances directly in the resources and bind them to content controls in the data templates. If you do so, the view is instantiated only once.
<Window.Resources>
<views:FirstPanelView x:Key="FirstPanelViewKey"/>
<views:SecondPanelView x:Key="SecondPanelViewKey"/>
<DataTemplate x:Key="DT1">
<ContentControl Content="{StaticResource FirstPanelViewKey}" />
</DataTemplate>
<DataTemplate x:Key="DT2">
<ContentControl Content="{StaticResource SecondPanelViewKey}" />
</DataTemplate>
</Window.Resources>
I wasn't able to solve my problem even after extending ContentControl. The issue I ran into using that approach is that ContentControl's dependency property was not directly interfacable/overridable which forced me to hack on the existing dependency property. Also the intialization of a DataTemplate seems to fall deeper than the simple ContentControl.
So I decided to change the way that my views are being displayed by simply toggling their visibility. This approach worked for me since I essentially want my views to stay in the background doing its own thing and ready to be interfaced at its previous state in any moment.

Set different datacontext in one view

I have 2 Windows. From the first window I'm calling second :
var window = new WindowButtonClick("Graphic") {DataContext = new GraphicViewModel()};
window.ShowDialog();
Here is XAML of second window:
<Window x:Class="WindowButtonClick"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:graphic="clr-namespace:Windows.Graphic"
WindowStartupLocation="CenterScreen" >
<Window.Resources>
<DataTemplate DataType="{x:Type graphic:GraphicViewModel}">
<graphic:Graphic />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}"/>
</Grid>
and constructor:
public WindowButtonClicks(string title)
{
InitializeComponent();
Title = Application.Current.Resources[title].ToString();
}
So how can I set DataContext, that it will show title that I pass in constructor as window Title and ContentControl will show one of the viewModels(GraphicViewModel in this case) ?
This is a common problem in WPF. Luckily, it has a simple solution. You'll need to use a RelativeSource Binding. So you'll need to set the DataContext to one object, whose properties you can data bind simply to like this:
<TextBox Text="{Binding PropertyOfDataContext}" />
And for any properties that are declared in the Window or UserControl, you can use the RelativeSource Binding like this:
<TextBox Text="{Binding PropertyOfWindow, RelativeSource={RelativeSource AncestorType={
x:Type YourXamlPrefix:YourWindow}}}" />
UPDATE >>>
You said:
I will have 20 ViewModels, that I want to load to ContentControl
If you had only provided all of the relevant information when asking your question, then you would have had a better answer by now. This is a different problem, but can be fixed just as easily. In this case, you can just set your view models to their views using DataTemplates... just define one for each view model/view pair like this:
<DataTemplate DataType="{x:Type ViewModels:ViewModel1}">
<Views:View1 />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:ViewModelN}">
<Views:ViewN />
</DataTemplate>
Note that I did not set the x:Key values... this means that the specified views will be implicitly rendered by the Framework whenever it comes across objects of the relevant type. Then to display the View1 from this example, you'd just need to do this:
<ContentControl Content="{PropertyOfTypeViewModel1}" />
UPDATE 2 >>>
Wow... I really hope that you've explained your problem properly this time because this is my last update. So I can't really see a problem with what you are asking for... you want to set a property in the constructor to be displayed as the Window.Title. This would definitely work:
public WindowButtonClicks(string title)
{
InitializeComponent();
Title = "Some Title";
}
So if your code doesn't work, then you must have a problem with your call to Application.Current.Resources[title]... have you actually checked whether that returns a value or not? If it does, then you have a real problem, because it is perfectly acceptable to set the Window.Title like this.
If Application.Current is returning null, then just make sure that you set it to an instance of MainWindow.xaml.cs in the code behind:
// In MainWindow.xaml.cs constructor
Application.Current = this;
Other than that, your problem is impossible to determine from the information that you have provided.

WPF MVVM Why use ContentControl + DataTemplate Views rather than straight XAML Window Views?

Why This?
MainWindow.xaml:
<Window x:Class="MVVMProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ContentControl Content="{Binding}"/>
</Grid>
</Window>
Have your ExampleView.xaml set up as:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vms="clr-namespace:MVVMProject.ViewModels">
<DataTemplate DataType="{x:Type vms:ExampleVM}" >
<Grid>
<ActualContent/>
</Grid>
</DataTemplate>
</ResourceDictionary>
And create the window like this:
public partial class App : Application {
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
MainWindow app = new MainWindow();
ExampleVM context = new ExampleVM();
app.DataContext = context;
app.Show();
}
}
When it can be done like this?
App.xaml: (Set startup window/View)
<Application x:Class="MVVMProject.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="ExampleView.xaml">
</Application>
ExampleView.xaml: (a Window not a ResourceDictionary)
<Window x:Class="MVVMProject.ExampleView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vms="clr-namespace:MVVMProject.ViewModels">
>
<Window.DataContext>
<vms:ExampleVM />
</Window.DataContext>
<Grid>
<ActualContent/>
</Grid>
</Window>
Essentially it's "View as DataTemplate" (VaD) vs. "View as Window" (VaW)
Here is my understanding of the comparison:
VaD: Lets you switch Views without closing the window. (This is not desirable for my project)
VaD: VM knows absolutely nothing about the View, whereas in VaW it (only) has to be able to instantiate it when opening another window
VaW: I can actually see my xaml rendered in the Designer (I can't
with VaD, at least in my current setup)
VaW: Works intuitively with
opening and closing windows; each window has (is) a corresponding View
(and ViewModel)
VaD: ViewModel can pass along initial window width, height, resizability etc. through properties (whereas in VaW they are directly set in the Window)
VaW: Can set FocusManager.FocusedElement (not sure how in VaD)
VaW: Less files, since my window types (e.g. Ribbon, Dialog) are incorporated into their Views
So what's going on here? Can't I just build my windows in XAML, access their data cleanly through properties of the VM, and be done with it? The code-behind is the same (virtually nil).
I'm struggling to understand why I should shuffle all the View stuff into a ResourceDictionary.
People use DataTemplates that way when they want to dynamically switch Views depending on the ViewModel:
<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>
So,
if Window.DataContext is an instance of VM1, then View1 will be displayed,
and if
Window.DataContext is an instance of VM2, then View2 will be displayed.
Granted, it makes no sense at all if only 1 View is expected, and never changed.
Since in VaD the view models know nothing about the views, you can build a fully functioning application entirely made up of view models only and no views. This leads to the possibility of writing an application that can be driven entirely by code. This in turn leads to the possibility of performing integration testing without the GUI. Integration testing through the GUI is notoriously fragile - while testing through view models should be more robust.
From my personal experience:
Both work models are aviables, depending of what you want, and depending of the application requirements. The idea behind VaD is decopling the content, and the container. If you implement VaD you can use this template (by default) when ever you show any item of this type. You can use it in ItemsControls (lists, listviews, grids, etc) and in ContentControls only making bindings. Like you said, VaD works for switching the window's content with out closing and opening a new. Also you can define the view using UserControls, then you take control if focused elements, and also you can manage code behind. So, your data template may be like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vms="clr-namespace:MVVMProject.ViewModels">
<DataTemplate DataType="{x:Type vms:ExampleVM}" >
<CustomUserControl A="{Binding A}" B="{Binding B}" DataContext="{Binding}" .../>
</DataTemplate>
You also in an UserControl may set dependency properties, thats make easier the job, because allow bindings and decoupling the app.
But of course, if you app doesn't require dynamically content switching, it is fine to use VaW for the main window, or any other window. In fact, you can use both VaW and VaD. This last one can be used for inner items in the app, that doesn't require windows. You shoose what is better for you, depending of application requirements, and the time aviable for developing the app.
Hope this personal experience helps...

WPF Communication between User Controls

I'm trying to find the best way to communicate between two User Controls. I have a main XAML window which contains two User Controls which in turn contain various controls. The Code behind of each User Control simply sets the DataContext to a View Model that is associated to it. The View Model contains objects that are bound to the controls.
What I'd like to do is capture when a list box in User Control 1 changes selection, the new selected item be displayed in an edit box in User Control 2. As I'm using View Models I can't declare Dependency Properties so I was wondering what is the accepted way to perform this?
I've attached some basic code to show how I'm setting the controls.
Main Window XAML
<Window x:Class="CommsTest.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CommsTest.View"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 />
<local:UserControl2 />
</Grid>
UserControl1 XAML
<UserControl x:Class="CommsTest.View.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="50,110,0,0" Name="comboBox1" VerticalAlignment="Top" Width="199" ItemsSource="{Binding Combo1}" />
</Grid>
UserControl1ViewModel.cs
class UserControl1ViewModel
{
private ObservableCollection<string> combo1 = new ObservableCollection<string>();
public ObservableCollection<string> Combo1
{
get { return combo1; }
}
}
UserControl2.XAML
<UserControl x:Class="CommsTest.View.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="63,84,0,0" Name="textBox1" VerticalAlignment="Top" Width="170" Text="{Binding Path=Text1}" />
</Grid>
UserControl2ViewModel.cs
class UserControl2ViewModel
{
private string text1;
public string Text1
{
get { return text1; }
set { text1 = value; }
}
}
How do I get UserControl2.Text1 to be the selected value of UserControl2.Combo1?
Thanks
While I understand that you are asking how to communicate between UserControls, I would suggest that the answer is to communicate between the view models. This can be easily achieved using delegate objects. In general, you'd need to have a parent view model that is common to the two child view models.
I recently answered a similar question, so I won't duplicate answers. Instead, I would ask you to take a look at the answer from the Passing parameters between viewmodels post here on StackOverflow which explains the solution with code examples.
UPDATE >>>
When I said that you need a common parent to your child view models, I don't mean anything to do with inheritance. I just mean that the parent holds a variable instance of each of the child view models... the parent instantiates the child view models.
Instead of creating the view model instance in the view code behind, you can do it in the parent view model and connect the view models to the views like this:
In Resources:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:UsersViewModel}">
<Views:UsersView />
</DataTemplate>
Then you just need to display an instance of the view model and the appropriate view will be displayed:
In ParentView:
<ContentControl Content="{Binding ViewModel}" />
In ParentViewModel:
public BaseViewModel ViewModel { get; set; } // Implement INotifyPropertyChanged
Then when you want to display a new view:
ViewModel = new UsersViewModel();
If your child views do not have a BaseViewModel and/or are not interchangable, then you could just add a property for each of them:
public MainViewmodel MainViewModel { get; set; } // Implement INotifyPropertyChanged
public UsersViewmodel UsersViewModel { get; set; } // properly for these properties
Either way, you'll need access to these view models from the parent view if you are going to be able to 'connect them together' with handlers.
I would suggest you, to have only one ViewModel and bind the DataContext to MainWindow.xaml, instead of doing it to each UserControl.
You should also implement INotifyPropertyChanged in your ViewModel to notify the UI whenever you change the value from the code or ViewModel.
Maybe you should think about your self-imposed restriction of not having dependency properties in user controls. MVVM is nice for the overal architecture, but you can overdo it if you put it into every class and control you plan to do.
If your user controls are just controls for the user, they should behave as such. I have never had to communicate with a TextBoxViewModel or ButtonViewModel, those are controls I simply use. Maybe yours is simple, too and does not need it's own viewmodel. Then you could communicate by using dependency properties as all other controls do.

Categories