Bind property of childcontrol to parentcontrol - c#

I have a UserControl (Parent) in which is another UserControl (Child). I want to Bind a property in the Child-Control to a property in the Parent-Control. Both UserControls are developed within MVVM. My question now is, how do I access the property of the child-control in the xaml of the parent-control?
Do I have to do something in the code-behind of the child-control or is there another way?
I have an interface called IPerson which looks like:
public interface IPerson
{
string Firstname {get;set;}
string Lastname {get;set;}
}
The parent-control (PersonsView) has a TreeView on the left side and on the right side there's the child-control (PersonView).
The XAML of Persons-View is:
<UserControl x:Class="ScM.Contents.View.PersonsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:viewModel="clr-namespace:ScM.Contents.ViewModel"
xmlns:view="clr-namespace:ScM.Contents.View"
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">
<UserControl.DataContext>
<viewModel:PersonsViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/ScM.Interface;component/GUI/ScMStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<view:PersonsTreeView Grid.Column="0" Margin="2"
OpenPupil="{Binding DataContext.OpenPersonCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
Persons="{Binding DataContext.Persons, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" />
<GridSplitter Grid.Column="1" Width="2" ShowsPreview="True" VerticalAlignment="Stretch"
HorizontalAlignment="Center" Margin="0,2" />
<view:PersonView Grid.Column="2" Margin="2"></view:PersonView>
</Grid>
</UserControl>
The Person-View consist only of labels and textboxes. The PersonViewModel looks like:
internal class PersonViewModel : ViewModelBase
{
private readonly PersonModel _personModel;
private IPerson _person;
public PersonViewModel()
{
_personModel = new PersonModel();
}
public IPerson Person
{
get { return _person; }
set
{
_person = value;
OnPropertyChanged();
}
}
}

You can either name your parent control to point to it using ElementName
<ParentControl x:Name=ParentControl>
<ChildControl SomeChildControlProperty="{Binding SomeParentControlProperty,
ElementName=ParentControl}"/>
</ParentControl>
or you can use RelativeSource with FindAncestor :
<ParentControl>
<ChildControl SomeChildControlProperty="{Binding SomeParentControlProperty,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ParentControl}}}"/>
</ParentControl>
I didn't test this code though so the syntax might not be exactly right

Related

Is it possible to set child controls properties in a user control without code?

I have a sample UserControl I snagged from somewhere and I'm using it to try and understand the capabilities of UserControls. What I'm trying to do is set properties of the embedded controls (say, txtInput from the UserControl below) in the window xaml.
I know that I can define new properties in the UserControl code (and have done so, see below), but with the UserControl defined as is, can a user of the control txtInput set some random properties, like say FontSize in xaml without me having to have defined FontSize as a property of the UserControls. Something like:
<uc:LimitedInputUserControl x:Name="liucDescription" Title="Enter description:" InitialText="Enter text here" MaxLength="140" Grid.Row="1">
<txtInput FontSize=20/>
</uc:LimitedInputUserControl>
<UserControl x:Class="TestBed.UserControls.LimitedInputUserControl"
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:TestBed"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="grdGrid">
<Grid.Background>
<SolidColorBrush Opacity="0.5" Color="Orange" />
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label x:Name="lblTitle" Content="{Binding Title}" />
<Label x:Name="lblLength" Grid.Column="1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElementName=txtInput, Path=Text.Length}" />
<TextBlock Text="/" />
<TextBlock Text="{Binding MaxLength}" />
</StackPanel>
</Label>
<TextBox x:Name="txtInput" MaxLength="{Binding MaxLength}" Grid.Row="1" Grid.ColumnSpan="2" ScrollViewer.VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" />
</Grid>
</UserControl>
Code is as follows:
namespace TestBed.UserControls
{
/// <summary>
/// Interaction logic for LimitedInputUserControl.xaml
/// </summary>
public partial class LimitedInputUserControl : UserControl
{
public LimitedInputUserControl()
{
InitializeComponent();
this.DataContext = this;
}
public string Title { get; set; }
private string sInitialText;
public string InitialText { get => sInitialText;
set
{
sInitialText = value;
txtInput.Text = value;
txtInput.SelectAll();
}
}
public int MaxLength { get; set; }
}
}

wpf mvvm nested user controls datacontext

i have a problem with the datacontext of nested user-controls. The Problem is that the Model in the ViewModel is binded on a DependencyProperty of the first user-control. In the first user-control is a second user-control. The second user-control has also a DependencyProperty. The first user-control binds on that DependencyProperty of the second user-control.
Mainview:
<UserControl x:Class="XXX.XXX.Hmi.Views.L1SetupViewerView"
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:prism="http://prismlibrary.com/"
xmlns:uc="clr-namespace:XXX.XXX.Hmi.UserControls"
d:DesignHeight="450"
d:DesignWidth="800"
prism:ViewModelLocator.AutoWireViewModel="True"
Background="{DynamicResource BaseBrush}"
Foreground="{DynamicResource TextBrush}"
mc:Ignorable="d">
.
.
.
<GroupBox Grid.Row="1" Header="Entry Section">
<uc:L1SetupViewerEntrySectionView Margin="8" Setup="{Binding L1Setup}" />
</GroupBox>
.
.
.
</UserControl>
MainViewModel:
public class L1SetupViewerViewModel : ViewModelBase
{
private L1Setup _l1Setup;
private Pdi _pdi;
public L1SetupViewerViewModel()
{
Title = LanguageProvider.Instance.GetValue("L1SetupViewer_Window_Title");
LanguageProvider.Instance.UiCultureChanged += Instance_UiCultureChanged;
}
#region Properties
public L1Setup L1Setup
{
get { return _l1Setup; }
set { SetProperty(ref _l1Setup, value); }
}
public Pdi Pdi
{
get { return _pdi; }
set { SetProperty(ref _pdi, value); }
}
#endregion
#region Navigation
public override void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters.ContainsKey("L1Setup"))
{
L1Setup = navigationContext.Parameters.GetValue<L1Setup>("L1Setup");
}
if (navigationContext.Parameters.ContainsKey("Pdi"))
{
Pdi = navigationContext.Parameters.GetValue<Pdi>("Pdi");
}
}
#endregion
private void Instance_UiCultureChanged(object sender, EventArgs e)
{
Title = LanguageProvider.Instance.GetValue("L1SetupViewer_Window_Title");
}
}
First UserControl:
<UserControl x:Class="XXX.XXX.Hmi.UserControls.L1SetupViewerEntrySectionView"
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:uc="clr-namespace:XXX.XXX.Hmi.UserControls"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" SharedSizeGroup="c" />
<ColumnDefinition Width="*" SharedSizeGroup="c" />
<ColumnDefinition Width="*" SharedSizeGroup="c" />
<ColumnDefinition Width="*" SharedSizeGroup="c" />
<ColumnDefinition Width="*" SharedSizeGroup="c" />
<ColumnDefinition Width="*" SharedSizeGroup="c" />
<ColumnDefinition Width="*" SharedSizeGroup="c" />
</Grid.ColumnDefinitions>
<uc:L1SetupViewerCellControl Title="Over Speed"
Grid.Row="0"
Grid.Column="0"
Margin="0,0,5,0"
Value="{Binding Setup.EnOveSpd}" />
.
.
.
</Grid>
</UserControl>
First UserControl CodeBehind:
public partial class L1SetupViewerEntrySectionView : UserControl
{
public L1Setup Setup
{
get { return (L1Setup)GetValue(SetupProperty); }
set { SetValue(SetupProperty, value); }
}
public static readonly DependencyProperty SetupProperty =
DependencyProperty.Register("Setup", typeof(L1Setup), typeof(L1SetupViewerEntrySectionView), new PropertyMetadata(null));
public L1SetupViewerEntrySectionView()
{
InitializeComponent();
this.DataContext = this;
}
}
Second UserControl:
<UserControl x:Class="XXX.XXX.Hmi.UserControls.L1SetupViewerCellControl"
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"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Margin="0,0,3,0"
Text="{Binding Title}" />
<TextBox Grid.Column="1" Text="{Binding Value}" />
</Grid>
</UserControl>
Second UserControl CodeBehind:
public partial class L1SetupViewerCellControl : UserControl
{
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(L1SetupViewerCellControl), new PropertyMetadata(string.Empty));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(L1SetupViewerCellControl), new PropertyMetadata(string.Empty));
public L1SetupViewerCellControl()
{
InitializeComponent();
this.DataContext = this;
}
}
If i run the application i get the following binding error:
System.Windows.Data Error: 40 : BindingExpression path error: 'L1Setup' property not found on 'object' ''L1SetupViewerCellControl' (Name='')'. BindingExpression:Path=L1Setup.EnPor1OutDia; DataItem='L1SetupViewerCellControl' (Name=''); target element is 'L1SetupViewerCellControl' (Name=''); target property is 'Value' (type 'String')
I tried several answers on stackOverflow, nothing worked for me and i don't get it whats wrong.
Setting this.DataContext = this on the L1SetupViewerEntrySectionView breaks the inheritance of the data context from L1SetupViewerView and that's why the binding to L1Setup fails.
Instead of explicitly setting the DataContext in the code-behind, you should either set the DataContext of the root element in UserControl like this:
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}>
...or the source on each binding:
<uc:L1SetupViewerCellControl ...
Value="{Binding Setup.EnOveSpd, RelativeSource={RelativeSource AncestorType=UserControl}}" />
The very same thing applies to the L1SetupViewerCellControl, i.e. remove this.DataContext = this from the constructor and bind to the Value property using a RelativeSource:
<TextBox Grid.Column="1" Text="{Binding Value, RelativeSource={RelativeSource AncestorType=UserControl}}" />

WPF data binding through UserControls

I would like to bind property from the main window's DataContext, above you can see my UserControls and models. So I want to bind the Model.ID.Label1 and Model.ID.Label2 properties to the main_id/card_1/top and main_id/card_1/bottom controls. I hope it's clear. If I enable the
ref_lbl Label it will shows the "lbl1", the card_2 still working with the hardcoded texts but the card_1 will be blank. What should I modify to fix the binding on card_1?
I have an ID UserControl and it contains another UserControl.
XAML:
<UserControl x:Class="stack.ID"
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:Controls="clr-namespace:stack.Controls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Label Name="ref_lbl" Grid.Row="0" Content="{Binding Label1}" Visibility="Collapsed" />
<Controls:Card x:Name="card_1" Grid.Row="0" TopLabel="{Binding Label1}" BottomLabel="{Binding Label2}" />
<Controls:Card x:Name="card_2" Grid.Row="1" TopLabel="Text 1" BottomLabel="Text 2" />
</Grid>
Code Behind: default, auto generated
Here is the Card UserControl.
XAML:
<UserControl x:Class="stack.Controls.Card"
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" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Label Grid.Row="0" FontSize="20" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding TopLabel}" />
<Label Grid.Row="1" FontSize="20" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding BottomLabel}" />
</Grid>
Code Behind:
namespace stack.Controls
{
public partial class Card : UserControl
{
public static readonly DependencyProperty TopLabelProperty = DependencyProperty.Register("TopLabel", typeof(string), typeof(Card), new PropertyMetadata(default(string)));
public static readonly DependencyProperty BottomLabelProperty = DependencyProperty.Register("BottomLabel", typeof(string), typeof(Card), new PropertyMetadata(default(string)));
public Card()
{
InitializeComponent();
}
public string TopLabel
{
get
{
return (string)GetValue(TopLabelProperty);
}
set
{
SetValue(TopLabelProperty, value);
}
}
public string BottomLabel
{
get
{
return (string)GetValue(BottomLabelProperty);
}
set
{
SetValue(BottomLabelProperty, value);
}
}
}
}
And here is my main window.
XAML:
<Window x:Class="stack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:stack"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Model />
</Window.DataContext>
<Grid>
<local:ID x:Name="main_id" DataContext="{Binding ID}" />
</Grid>
Code Behind: default, auto generated
And I also have 2 models.
namespace stack
{
public class IDModel
{
private string label1 = "lbl1";
private string label2 = "lbl2";
public string Label1
{
get
{
return label1;
}
set
{
label1 = value;
}
}
public string Label2
{
get
{
return label2;
}
set
{
label2 = value;
}
}
}
public class Model
{
private IDModel id = new IDModel();
public IDModel ID
{
get
{
return id;
}
set
{
id = value;
}
}
}
}
Remove
DataContext="{Binding RelativeSource={RelativeSource Self}}"
from the Card's XAML.
It prevents inheriting the DataContext from its parent ID control, which is necessary when you write
<Controls:Card ... TopLabel="{Binding Label1}" />
Instead write the Content bindings in Card's XAML like this:
<Label ... Content="{Binding TopLabel,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />

Setting DataContext in XAML in WPF

I have following code:
MainWindow.xaml
<Window x:Class="SampleApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Employee}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="ID:"/>
<Label Grid.Row="1" Grid.Column="0" Content="Name:"/>
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Text="{Binding EmpID}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding EmpName}" />
</Grid>
</Window>
Employee.cs
namespace SampleApplication
{
public class Employee
{
public Employee()
{
EmployeeDetails employeeDetails = new EmployeeDetails();
employeeDetails.EmpID = 123;
employeeDetails.EmpName = "ABC";
}
}
public class EmployeeDetails
{
private int empID;
public int EmpID
{
get
{
return empID;
}
set
{
empID = value;
}
}
private string empName;
public string EmpName
{
get
{
return empName;
}
set
{
empName = value;
}
}
}
}
This is very simple code and I just want to bind the EmpID and EmpName properties in my Employee.cs class to Text properties of Textboxes in MainWindow.xaml but nothing is appearing in my these textboxes when I am running the code. Is the binding right?
This code will always fail.
As written, it says: "Look for a property named "Employee" on my DataContext property, and set it to the DataContext property". Clearly that isn't right.
To get your code to work, as is, change your window declaration to:
<Window x:Class="SampleApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Employee/>
</Window.DataContext>
This declares a new XAML namespace (local) and sets the DataContext to an instance of the Employee class. This will cause your bindings to display the default data (from your constructor).
However, it is highly unlikely this is actually what you want. Instead, you should have a new class (call it MainViewModel) with an Employee property that you then bind to, like this:
public class MainViewModel
{
public Employee MyEmployee { get; set; } //In reality this should utilize INotifyPropertyChanged!
}
Now your XAML becomes:
<Window x:Class="SampleApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
...
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Text="{Binding MyEmployee.EmpID}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding MyEmployee.EmpName}" />
Now you can add other properties (of other types, names), etc. For more information, see Implementing the Model-View-ViewModel Pattern
First of all you should create property with employee details in the Employee class:
public class Employee
{
public Employee()
{
EmployeeDetails = new EmployeeDetails();
EmployeeDetails.EmpID = 123;
EmployeeDetails.EmpName = "ABC";
}
public EmployeeDetails EmployeeDetails { get; set; }
}
If you don't do that, you will create instance of object in Employee constructor and you lose reference to it.
In the XAML you should create instance of Employee class, and after that you can assign it to DataContext.
Your XAML should look like this:
<Window x:Class="SampleApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:SampleApplication"
>
<Window.Resources>
<local:Employee x:Key="Employee" />
</Window.Resources>
<Grid DataContext="{StaticResource Employee}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="ID:"/>
<Label Grid.Row="1" Grid.Column="0" Content="Name:"/>
<TextBox Grid.Column="1" Grid.Row="0" Margin="3" Text="{Binding EmployeeDetails.EmpID}" />
<TextBox Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding EmployeeDetails.EmpName}" />
</Grid>
</Window>
Now, after you created property with employee details you should binding by using this property:
Text="{Binding EmployeeDetails.EmpID}"
There are several issues here.
You can't assign DataContext as DataContext="{Binding Employee}" because it's a complex object which can't be assigned as string. So you have to use <Window.DataContext></Window.DataContext> syntax.
You assign the class that represents the data context object to the view, not an individual property so {Binding Employee} is invalid here, you just have to specify an object.
Now when you assign data context using valid syntax like below
<Window.DataContext>
<local:Employee/>
</Window.DataContext>
know that you are creating a new instance of the Employee class and assigning it as the data context object. You may well have nothing in default constructor so nothing will show up. But then how do you manage it in code behind file? You have typecast the DataContext.
private void my_button_Click(object sender, RoutedEventArgs e)
{
Employee e = (Employee) DataContext;
}
A second way is to assign the data context in the code behind file itself. The advantage then is your code behind file already knows it and can work with it.
public partial class MainWindow : Window
{
Employee employee = new Employee();
public MainWindow()
{
InitializeComponent();
DataContext = employee;
}
}

How to bind a property in the view model of a usercontrol to a property in another usercontrol

I am currently working within a WPF user control using MVVM. My MainWindow.xaml looks like below.
MainWindow.xaml
<Window.Resources>
<ObjectDataProvider x:Key="TabsList" ObjectType="{x:Type local:MainWindowModel}" MethodName="GetTabs"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Source={StaticResource TabsList}}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding Source={StaticResource TabsList}, Path=MyUserControl}"/>
</Grid>
Data provider class is as below
public class MainWindowModel
{
public List<TabInfo> GetTabs()
{
return new List<TabInfo>()
{
new TabInfo() { TabName="Tab1", MyUserControl = new UserControl1()},
new TabInfo() { TabName="Tab2", MyUserControl = new UserControl2()}
};
}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControl MyUserControl { get; set; }
}
And now I have two usercontrols UserControl1 and UserControl2 each has a text box control. I would like to update the Text property of the textbox control in the UserControl1 whenever the Text property of the Textbox control in the UserControl2 is updated. To do this, I tried as below.
UserControl1
<UserControl x:Class="MoreOnBinding2.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>
<TextBox Width="200" Height="20" Text="{Binding Path=UserControl1Text}"/>
</Grid>
UserControl1ViewModel
public class UserControl1VM : ViewModelBase
{
public UserControl1VM()
{
this.UserControl1Text = "10";
}
private string userControl1Text;
public string UserControl1Text
{
get { return userControl1Text; }
set
{
if (userControl1Text != value)
{
userControl1Text = value;
RaisePropertyChanged(() => UserControl1Text);
}
}
}
}
UserControl2
<UserControl x:Class="MoreOnBinding2.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"
xmlns:local="clr-namespace:MoreOnBinding2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Width="200" Height="20"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl1}},
UpdateSourceTrigger=PropertyChanged, Path=UserControl1Text}" />
</Grid>
But it is not working. It seems there is a problem in the RelativeSource tag in the UserControl2. Can someone help on this.
You could use a Dependency Property on your user controls this would give you a bind able property. see link
EDIT 1
Just had another look at your class setup. If you are following the MVVM approach I don't think you should have a reference to your user control in your view model
View Model should be more Like
public Class
{
ObservableCollection<TabInfo> _Tabs = new ObservableCollection<TabInfo>();
public ObservableCollection<TabInfo> Tabs
{
get{return _Tabs;}
set {_Tabs = value;}//Need to implement INotifyPropertyChanged [Link][2]
}
public TabInfo SelectedTab {get;set;}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControlViewModel MyUserControlViewModel{ get; set; }
}
Then in your View
<Window.DataContext>
<vm:MainWindowModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True" SelectedValue="{Binding SelectedTab}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedTab.MyUserControlViewModel"/>
</Grid>
This should give you a list of Tabs and then when one is selected it will set the content of the ContentControl to the MyUserControlViewModel.
You would then need to use some sort of template selector to control the ContentTemplate to load different UserControls into the ContentControl
I haven't tested the code works and you would need to implement the INotifyPropertyChanged on all the public properties so the bindings update as the properties value is changed.
Hope that helps a bit.

Categories