How to use more than one DataContext - c#

Im using MVVM Light, and in my Locator I have two ViewModels. However in a page I want to use more than one ViewModels to use their properties in the page's ui elements, but how?
Here is the XAML of my Page:
<Page
x:Class="my_app.MainMenuPage"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:my_app"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Foreground="Red"
DataContext = "{Binding Source={StaticResource Locator}, Path=SettingsVM }">
Here is the code of my Locator:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<StudentsViewModel>();
SimpleIoc.Default.Register<SettingsViewModel>();
}
public StudentsViewModel StudentsVM
{
get
{
return ServiceLocator.Current.GetInstance<StudentsViewModel>();
}
}
public SettingsViewModel SettingsVM
{
get
{
return ServiceLocator.Current.GetInstance<SettingsViewModel>();
}
}
public static void Cleanup() {}
}
So i can't do something like this, obviously:
DataContext = "{Binding Source={StaticResource Locator}, Path=SettingsVM, Path=StudentsVM}">

As far as I can see you don't use 2 DataContext. You use 2 objects of one DataContext. Set DataContext to Locator (without any Path) and then specify Path=StudentsVM.PropertyA or Path=SettingsVM.PropertyC per binding
<Page ... DataContext="{Binding Source={StaticResource Locator}}">
<!-- .... -->
<TextBlock Text="{Binding StudentsVM.PropertyA}"/>
<TextBlock Text="{Binding StudentsVM.PropertyB}"/>
<TextBlock Text="{Binding SettingsVM.PropertyC}"/>
<TextBlock Text="{Binding SettingsVM.PropertyD}"/>
<!-- .... -->
</Page>
or if you have more properties to bind you can locally change DataContext for group of controls
<Page ... DataContext="{Binding Source={StaticResource Locator}}">
<!-- .... -->
<StackPanel DataContext="{Binding StudentsVM}">
<TextBlock Text="{Binding PropertyA}"/>
<TextBlock Text="{Binding PropertyB}"/>
</StackPanel>
<!-- .... -->
<StackPanel DataContext="{Binding SettingsVM}">
<TextBlock Text="{Binding PropertyC}"/>
<TextBlock Text="{Binding PropertyD}"/>
</StackPanel>
<!-- .... -->
</Page>

Related

How to fill each tab of the tab control's itemslist with one user control dynamically from the mainviewmodel

My MainView contains a TabControl with an ItemTemplate and a ContentTemplate.
The TabControl's ItemsSource is bound to the Property ObservableCollection<TabViewModel> TabCollection in my MainViewModel.
TabViewModel:
namespace LuxUs.ViewModels
{
public class TabViewModel
{
public string Name { get; set; }
public object VM {get; set;}
public TabViewModel(string name)
{
Name = name;
}
public TabViewModel(string name, object vm)
{
Name = name;
VM = vm;
}
}
}
I want to create the tabs with their tab's header AND content dynamically from the MainViewModel like this...:
MainViewModel:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public class MainViewModel : ObservableObject, IPageViewModel
{
public ObservableCollection<TabViewModel> TabCollection { get; set; }
public MainViewModel()
{
TabCollection = new ObservableCollection<TabViewModel>();
TabCollection.Add(new TabViewModel("Dachdefinition", new DachdefinitionViewModel()));
TabCollection.Add(new TabViewModel("Baukörperdefinition"));
TabCollection.Add(new TabViewModel("Fassade"));
TabCollection.Add(new TabViewModel("Raumdefinition"));
TabCollection.Add(new TabViewModel("Treppenloch | Galerieöffnung"));
}
}
}
View:
<UserControl x:Class="LuxUs.Views.MainView"
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"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:models="clr-namespace:LuxUs.Models"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<Grid>
<Grid>
<TabControl Style="{DynamicResource TabControlStyle}" ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Grid>
</UserControl>
... but the content of each tab won't show. Instead, I get this text. The ViewModel ist the correct one but it should load the view instead of showing this text, of course:
The first tab's ViewModel DachdefinitionViewModel has only an empty constructor:
using System.Collections.ObjectModel;
namespace LuxUs.ViewModels
{
public sealed class DachdefinitionViewModel : ObservableObject
{
public DachdefinitionViewModel()
{
}
}
}
And here is its view Dachdefinition.xaml:
<UserControl x:Class="LuxUs.Views.Dachdefinition"
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"
xmlns:local="clr-namespace:LuxUs.Views"
xmlns:vm="clr-namespace:LuxUs.ViewModels"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:DachdefinitionViewModel></vm:DachdefinitionViewModel>
</UserControl.DataContext>
<Grid Margin="50">
...
...
...
</Grid>
</UserControl>
Is the binding correct here or do I need to bind differently? Why is the view not showing up inside the first tab?
you need to connect view model with correct view via DataTemplate.
DataTemplate provides visual representation for data type which doesn't have it (your view model). If you don't specify DataTemplate, you will get default one: TextBlock with string representation of object (result of ToString() method, default to type name).
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<views:Dachdefinition />
</DataTemplate>
</Grid.Resources>
<TabControl Style="{DynamicResource TabControlStyle}"
ItemsSource="{Binding TabCollection}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<UserControl>
<ContentControl Content="{Binding VM}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Your TabControl declaration should look like this:
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:DachdefinitionViewModel}">
<local:Dachdefinition/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding VM}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
And the Dachdefinition UserControl must not set its own DataContext property, because the DataContext value is supposed to be inherit from the control's parent element, i.e. the TabItem.
<UserControl x:Class="LuxUs.Views.Dachdefinition" ...>
<!--
do not set UserControl.DataContext here
-->
<Grid Margin="50">
...
</Grid>
</UserControl>
Yes there is a problem in databinding.
at
<ContentControl Content="{Binding VM}" />
This line would just display ToString() value of the object bound to
it. (VM in this case).
Instead you can try using ContentTemplateSelection where you can choose the type of ContentTemplate in run time based on the type of object bound to it.
class TabContentTemplateSelector:DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate DachdeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TabViewModel tabViewModel)
{
if (tabViewModel.VM != null && tabViewModel.VM is DachdefinitionViewModel)
{
return DachdeTemplate;
}
else
{
return DefaultTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
<DataTemplate x:Key="DachdeTemplate">
</DataTemplate>
<DataTemplate x:Key="SomeOtherTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<local:TabContentTemplateSelector x:Key="myTabContentTemplateSelector"
DachdeTemplate="{StaticResource DachdeTemplate}"
DefaultTemplate="{StaticResource
SomeOtherTemplate}"
/>
</UserControl.Resources>
<Grid>
<TabControl ItemsSource="{Binding TabCollection}"
ContentTemplateSelector="{StaticResource
myTabContentTemplateSelector}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
https://www.c-sharpcorner.com/UploadFile/41e70f/dynamically-selecting-datatemplate-for-wpf-listview-way-1/
Check this for how to dynamically change template.. In the template you can use any kind of user control.

Selecting DataTemplate from Window Resource in ViewModel with MVVM

I have a View where a small part of the window that displays the details of the item a user clicks on. The format of these details changes, so my original implementation had a hide/show logic for the different kinds of items:
<Grid Name="Details1" Visibility="Collapsed">
<TextBox Name="Details_Field1" />
</Grid>
<Grid Name="Details2" Visibility="Visible">
<TextBox Name="Details_Field2" />
<TextBox Name="Details_Field3" />
</Grid>
<Grid Name="Details3" Visibility="Collapsed">
<TextBox Name="Details_Field4" />
<TextBox Name="Details_Field5" />
<DataGrid Name="Details_DataGrid1 />
</Grid>
Now, I want to make this 'less bad'. My strategy was to make each of these Grids it's own DataTemplate, and manage state like so:
View:
<Window.Resource>
<DataTemplate x:Key="Details_Template1>
<Grid Name="Details1">
<TextBox Name="Details_Field1" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="Details_Template2>
<Grid Name="Details2">
<TextBox Name="Details_Field2" />
<TextBox Name="Details_Field3" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="Details_Template3>
<Grid Name="Details3">
<TextBox Name="Details_Field4" />
<TextBox Name="Details_Field5" />
<DataGrid Name="Details_DataGrid1 />
</Grid>
</DataTemplate>
</Window.Resources>
....
<Grid Name="DetailsGoHere">
<ContentControl ContentTemplate="{Binding DetailsDisplay}" />
</Grid>
ViewModel:
private DataTemplate _detailsDisplay;
public DataTemplate DetailsDisplay
{
get => _detailsDisplay;
private set => RaisePropertyChangedEvent(ref _detailsDisplay, value);
}
....
private void Item_OnClick()
{
// Pseudocode! How do I reference Details_Template1 as a resource?
DetailsDisplay = MyView.Details_Template1;
}
As the comment implies, I'm not sure how to reference the DataTemplates in my Window.Resource block from my ViewModel, so my question is twofold:
1: Is this a good solution to this kind of a problem?
2: If so, how do I reference an item from my Window.Resource block in my ViewModel?
DataTemplateSelector are used to switch DataTemplate in WPF.
Kindly refer this link http://www.wpftutorial.net/datatemplates.html for detailed implementation.
PropertyDataTemplateSelector-
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DependencyPropertyInfo dpi = item as DependencyPropertyInfo;
if (dpi.PropertyType == typeof(bool))
{
return BooleanDataTemplate;
}
if (dpi.PropertyType.IsEnum)
{
return EnumDataTemplate;
}
return DefaultnDataTemplate;
}
}
View -
<Window x:Class="DataTemplates.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:DataTemplates"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Window.Resources>
<!-- Default DataTemplate -->
<DataTemplate x:Key="DefaultDataTemplate">
...
</DataTemplate>
<!-- DataTemplate for Booleans -->
<DataTemplate x:Key="BooleanDataTemplate">
...
</DataTemplate>
<!-- DataTemplate for Enums -->
<DataTemplate x:Key="EnumDataTemplate">
...
</DataTemplate>
<!-- DataTemplate Selector -->
<l:PropertyDataTemplateSelector x:Key="templateSelector"
DefaultnDataTemplate="{StaticResource DefaultDataTemplate}"
BooleanDataTemplate="{StaticResource BooleanDataTemplate}"
EnumDataTemplate="{StaticResource EnumDataTemplate}"/>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding}" Grid.IsSharedSizeScope="True"
HorizontalContentAlignment="Stretch"
ItemTemplateSelector="{StaticResource templateSelector}"/>
</Grid>

DataTemplate and ContentControl when user class as DataContext

What I have:
User class
public class MyButton
{
public String ButtonProperty { get; set; }
public String LabelProperty { get; set; }
public MyButton()
{
ButtonProperty = "MyButtonText!";
LabelProperty = "LabelText!";
}
}
DataTemplate defined in window resources
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyButton}">
<Border Width="100" Height="100" BorderThickness="2" BorderBrush="Aquamarine">
<StackPanel >
<Button>
<TextBlock Text="{Binding ButtonProperty}"></TextBlock>
</Button>
<Label Content="{Binding LabelProperty}"></Label>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
I want to DataTemplate will draw instead of instance of MyButton class
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication7"
Title="MainWindow" Height="500" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyButton}">
<Border Width="100" Height="100" BorderThickness="2" BorderBrush="Aquamarine">
<StackPanel >
<Button>
<TextBlock Text="{Binding ButtonProperty}">
</TextBlock>
</Button>
<Label Content="{Binding LabelProperty}">
</Label>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<!-- Create instance of MyButton in XAML-->
<local:MyButton></local:MyButton>
</Window>
It works fine, but it is not what I want at the end. What if instance of MyButton will DataContext for Window?
public MainWindow()
{
//Set instance of MyButton as DataContext
DataContext = new MyButton();
InitializeComponent();
}
I thought I must write that in XAML-side
<ContentControl DataContext="{Binding}">
<!--MyButton XAML code from DataTemplate here -->
</ContentControl>
instead of
<local:MyButton></local:MyButton>
but it doesn't work at all. what I am doing wrong?
You should try to bind to the Content property of your ContentControl instead of the DataContext property :
<ContentControl Content={Binding } />
Besides, the DataContext of the ContentControl is already the MyButton.
I'm not really sure what are you trying to achieve there. IF you simply want to extend the functionality of the default Button you could define attached properties
Why would you want the DataContext of your Window to be the Button? Maybe the other way around? Not sure I understood that part correctly.

Combobox binding doesn't work in ItemsControl using MVVM

I have a ComboBox in ItemsControl .I use WPF and MVVM, I have problem to figure out the binding to ComboBox, would someone give me a hand for this. XAML and VM as following:
<Window x:Class="OutageManagement.Views.MarketAssignmentsView"
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"
mc:Ignorable="d"
Title="Market Selection"
WindowStartupLocation="CenterOwner"
Width="700" Height="850"
DataContext="{Binding MarketAssignmentsVM, Source={StaticResource Locator}}" >
<Grid>
<ItemsControl ItemsSource="{Binding USMarket}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Content="{Binding MarketName}" Height="28"
HorizontalAlignment="Left" Name="lblUSMarketName"
VerticalAlignment="Center" />
<ComboBox Grid.Column="1" Height="23" HorizontalAlignment="Left"
Name="cbUSUsers" VerticalAlignment="Center" MinWidth="140"
ItemsSource="{Binding RelativeSource={RelativeSource
AncestorType=Window}, Path=UserList}"
DisplayMemberPath="UserName"
SelectedValue="{Binding SelectedUserID}"
SelectedValuePath="UserID"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
ViewModel :
public class MarketAssignmentsViewModel : ViewModelBase
{
#region Data
ObservableCollection<NOCUserViewModel> _userList;
ObservableCollection<MarketAssignmentViewModel> _usMarket;
ObservableCollection<MarketAssignmentViewModel> _caMarket;
#endregion
#region Constructor
public MarketAssignmentsViewModel()
{
GetUserList();
GetMarketAssignments();
}
#endregion
#region Properties
public ObservableCollection<NOCUserViewModel> UserList
{
get { return _userList; }
}
public ObservableCollection<MarketAssignmentViewModel> USMarket
{
get { return _usMarket; }
}
public ObservableCollection<MarketAssignmentViewModel> CAMarket
{
get { return _caMarket; }
}
#endregion
.
.
.
}
The problem is that you're trying to access the UserList as a property of the Window, instead of a property of the Window's DataContext...
Modify the ItemsSource like this:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor
AncestorType=Window}, Path=DataContext.UserList}" ... />
I recommend always looking in the Output window when you have binding problems, you probably would have seen something like this:
System.Windows.Data Error: 40 : BindingExpression path error: 'UserList' property not found on 'object' ''MarketAssignmentsView' (Name='')'.

Binding from ItemsSource context

I'm having a problem with the DataContext and the Title. The following works as intended:
<chartingToolkit:LineSeries Title={Binding TrendDaily.Name} ItemsSource="{Binding TrendDaily.Progress}">
//...
</chartingToolkit:LineSeries>
But the Title should contain more information so I'm doing this:
<chartingToolkit:LineSeries ItemsSource="{Binding TrendDaily.Progress}">
<chartingToolkit:LineSeries.Title>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TrendDaily.Name}"/>
<TextBlock Text="-test text"/>
</StackPanel>
</chartingToolkit:LineSeries.Title>
//...
</chartingToolkit:LineSeries>
I figured out the Title binding doesn't work because it has the "Progress" elements as his context but I wasn't able to find a working binding.
Edit:
The complete new code with binding error (Cannot find source for binding with reference 'ElementName=LineName'):
<Window x:Class="WpfApplication1.MainWindow"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<chartingToolkit:Chart Title="Trend">
<chartingToolkit:Chart.Series>
<chartingToolkit:LineSeries DataContext="{Binding TrendDaily}"
ItemsSource="{Binding Progress}" DependentValuePath="Value" IndependentValuePath="Key" x:Name="LineName">
<chartingToolkit:LineSeries.Title>
<TextBlock>
<Run Text="{Binding DataContext.Name, ElementName=LineName}"/>
<Run Text="*"/>
</TextBlock>
</chartingToolkit:LineSeries.Title>
</chartingToolkit:LineSeries>
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public TrendDailyClass TrendDaily { get; set; }
public MainWindow()
{
TrendDaily = new TrendDailyClass();
DataContext = this;
InitializeComponent();
}
}
public class TrendDailyClass
{
public Dictionary<string, double> Progress { get; set; }
public string Name { get; set; }
public TrendDailyClass()
{
Progress = new Dictionary<string, double>();
Progress.Add("10", 10);
Progress.Add("20", 20);
Name = "test";
}
}
Bind TrendDaily to the DataContext of LineSeries, then use DataContext in the inner bindings, using ElementName as:
<chartingToolkit:Chart Title="Trend"
DataContext="{Binding TrendDaily}"
x:Name="LineName">
<chartingToolkit:LineSeries ItemsSource="{Binding Progress}">
<chartingToolkit:LineSeries.Title>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DataContext.Name, ElementName=LineName}"/>
<TextBlock Text="-test text"/>
</StackPanel>
</chartingToolkit:LineSeries.Title>
//...
</chartingToolkit:LineSeries>
Moreover, there is no need to use two TextBlock.. You can use Run (which is very lightweight class) as:
<StackPanel Orientation="Horizontal">
<TextBlock>
<Run Text="{Binding DataContext.Name, ElementName=LineName}"/>
<Run Text="-test text"/>
</TextBlock>
</StackPanel>
It's better, as it avoids unnecessary visual element. Classes derived from UIElement are relatively heavier.
If you're first code example is working, you should be able to use the StringFormat property in your first binding:
<chartingToolkit:LineSeries Title={Binding TrendDaily.Name, StringFormat='{}{0}-test text'} ItemsSource="{Binding TrendDaily.Progress}">
//...
</chartingToolkit:LineSeries>

Categories