MySpecialView is a complex image control, I would like to reuse it from different views and pass its ViewModel as in this example.
MainWindow.xaml
<Window x:Class="YouBug.MainWindow"
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:local="clr-namespace:YouBug"
mc:Ignorable="d"
DataContext="{Binding MainViewModel}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:MySpecialView ViewModel="{Binding MySpecialViewModel}"></local:MySpecialView>
</Grid>
MainViewModel
public class MainViewModel
{
public MySpecialViewModel MySpecialViewModel { get; set; }
public MainViewModel()
{
MySpecialViewModel = new MySpecialViewModel();
//gets not displayed!
Task.Run(() => MySpecialViewModel.changeImage(5000, "C:\\Users\\user\\Pictures\\Capture.PNG"));
}
}
MySpecialView.xaml
<UserControl x:Class="YouBug.MySpecialView"
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:YouBug"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Image Source="{Binding ImageSource}" />
</Grid>
MySpecialView.xaml.cs
public partial class MySpecialView : UserControl
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MySpecialViewModel), typeof(MySpecialView), new FrameworkPropertyMetadata(new MySpecialViewModel(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public MySpecialViewModel ViewModel { get { return (MySpecialViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } }
public MySpecialView()
{
DataContext = ViewModel;
InitializeComponent();
}
}
MySpecialViewModel
public class MySpecialViewModel : ViewModelBase
{
public BitmapSource imageSource { get; set; }
public BitmapSource ImageSource { get { return imageSource; }
set { if (value != imageSource)
{
imageSource = value; RaisePropertyChanged("ImageSource");
}
} }
public MySpecialViewModel()
{
//gets displayed
ImageSource = new BitmapImage(new Uri("C:\\Users\\user\\Pictures\\test.jpg"));
//gets displayed aswell
Task.Run(() => changeImage(10000, "C:\\Users\\user\\Pictures\\clickMe.png"));
}
public async void changeImage(int sleep, string uri)
{
await Task.Delay(sleep);
BitmapSource source = new BitmapImage(new Uri(uri));
source.Freeze();
ImageSource = source;
}
}
But whenever I assign MySpecialViewModels Properties from MainViewModel, the RaisePropertyChange event does not force the Image element or other bindings to update from the MySpecialViewModel.
What am I doing wrong here? Is this a general wrong approach?
You are too used to "View-First-Approach" (VFA). Your situation is better off using "ViewModel-First-Approach" (VMFA). In VFA, you place your child views from the main View, and each subview is linked to the respective ViewModel via DataContext.
In VMFA, your ViewModel holds references of sub-ViewModels. You expose these ViewModel references through property binding, and the View display them via DataTemplate.
MainWindow.xaml
<Window x:Class="YouBug.MainWindow"
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:local="clr-namespace:YouBug"
mc:Ignorable="d"
DataContext="{Binding MainViewModel}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:MySpecialViewModel}">
<local:MySpecialViewModel />
</DataTemplate>
</Grid.Resources>
<ContentControl Content={Binding MySpecialView}" />
</Grid>
MainViewModel
public class MainViewModel : ViewModelBase // Not sure why you didn't subclass ViewModelBase in your question
{
private MySpecialViewModel _mySpecialViewModel;
public MySpecialViewModel MySpecialViewModel
{
get
{
return _mySpecialViewModel;
}
set
{
if (value != _mySpecialViewModel)
{
_mySpecialViewModel = value;
RaisePropertyChanged(); // The property changed method call
}
}
}
public MainViewModel()
{
MySpecialViewModel = new MySpecialViewModel();
//gets not displayed!
Task.Run(() => MySpecialViewModel.changeImage(5000, "C:\\Users\\user\\Pictures\\Capture.PNG"));
}
}
MySpecialView does not need that DependencyProperty, nor set the DataContext. The DataContext is set automatically by the DataTemplate part. Your MySpecialViewModel can stay as it is now.
Edit
I just realized your MainWindow is not doing the DataContext correctly either.
MainWindow.xaml.cs
public partial class MainWindow: Window
{
public MainWindow()
{
InitializeComponents();
this.DataContext = new MainViewModel();
}
}
Do not specify viewmodel property in you view, use DataContext.
See the following code.
public partial class MySpecialView : UserControl
{
public MySpecialView()
{
InitializeComponent();
}
}
ViewModel for special:
public class MySpecialViewModel : ViewModelBase
{
public BitmapSource imageSource { get; set; }
public BitmapSource ImageSource { get { return imageSource; }
set { if (value != imageSource)
{
imageSource = value;
RaisePropertyChanged("ImageSource");
}
} }
public MySpecialViewModel()
{
//gets displayed
ImageSource = new BitmapImage(new Uri("C:\\Users\\user\\Pictures\\test.jpg"));
//gets displayed aswell
Task.Run(() => changeImage(10000, "C:\\Users\\user\\Pictures\\clickMe.png"));
}
public async void changeImage(int sleep, string uri)
{
await Task.Delay(sleep);
BitmapSource source = new BitmapImage(new Uri(uri));
source.Freeze();
ImageSource = source;
}
}
In XAML special:
<UserControl x:Class="YouBug.MySpecialView"
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:YouBug"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Image Source="{Binding ImageSource}" />
</Grid>
For main:
public class MainViewModel : ViewModelBase
{
public MySpecialViewModel SpecialViewModel
{
get { return _specialViewModel; }
set
{
if (value != _specialViewModel)
{
_specialViewModel= value;
RaisePropertyChanged("SpecialViewModel");
}
}
}
private MySpecialViewModel _specialViewModel;
public MainViewModel()
{
MySpecialViewModel = new MySpecialViewModel();
//gets not displayed!
Task.Run(() => MySpecialViewModel.changeImage(5000, "C:\\Users\\user\\Pictures\\Capture.PNG"));
}
}
And in XAML:
<Window x:Class="YouBug.MainWindow"
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:local="clr-namespace:YouBug"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<MainWindowViewModel/
</Window.DataContext>
<Grid>
<local:MySpecialView DataContext="{Binding Path=SpecialViewModel}"></local:MySpecialView>
</Grid>
Related
I have 2 classes (InnerModel and OuterModel). OuterModel contains 2 InnerModel instances. I would like to create UserControls for them (InnerUserControl and OuterUserControl). OuterUserControl contains 2 InnerUserControls. But I can't figure out how to make binding works in this case.
Below is the complete code of what I try to do.
Please advise how to fix it to get the same result as on a pic at the end.
Thanks in advance!
MainWindow.xaml.cs
<Window x:Class="NestedUserControl.MainWindow"
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:local="clr-namespace:NestedUserControl"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="450">
<Grid>
<local:OuterUserControl x:Name="test"/>
</Grid>
MainWindow.xaml
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var model = new OuterModel("TEST1", "TEST2");
test.DataContext = model;
}
}
InnerModel.cs
public class InnerModel : INotifyPropertyChanged
{
public String Data
{
get { return data; }
set { data = value; }
}
private string data;
public event PropertyChangedEventHandler PropertyChanged;
public InnerModel(string _data) => data = _data;
public void OnPropertyChanged([CallerMemberName]string prop = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
InnerUserControl.xaml
<UserControl x:Class="NestedUserControl.InnerUserControl"
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:NestedUserControl"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="450">
<Grid>
<TextBlock Text="{Binding Path=Data}"/>
</Grid>
InnerUserControl.xaml.cs
public partial class InnerUserControl : UserControl
{
public InnerUserControl()
{
InitializeComponent();
}
}
OuterModel.cs
public class OuterModel : INotifyPropertyChanged
{
public InnerModel model1;
public InnerModel model2;
public event PropertyChangedEventHandler PropertyChanged;
public OuterModel(string data1, string data2)
{
model1 = new InnerModel(data1);
model2 = new InnerModel(data2);
}
public void OnPropertyChanged([CallerMemberName]string prop = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
OuterUserControl.xaml
<UserControl x:Class="NestedUserControl.OuterUserControl"
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:NestedUserControl"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="450">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:InnerUserControl Grid.Row="0" x:Name="inner1" DataContext="model1"/>
<local:InnerUserControl Grid.Row="1" x:Name="inner2" DataContext="model2"/>
</Grid>
OuerUserControl.xaml.cs
public partial class OuterUserControl : UserControl
{
public OuterUserControl()
{
InitializeComponent();
}
}
Working binding MainWindow.xaml.cs
Welcome to SO!
Two problems here, first of all your inner models need to be properties, so change their declarations to this:
public InnerModel model1 {get; set;}
public InnerModel model2 {get; set;}
Second problem is with your bindings, you need to do this instead:
<local:InnerUserControl Grid.Row="0" x:Name="inner1" DataContext="{Binding model1}"/>
<local:InnerUserControl Grid.Row="1" x:Name="inner2" DataContext="{Binding model2}"/>
I'm trying to realize a simple example of a UserControl, showing in a TextBox the current DateTime, updated four times each second.
I create a simple user control:
<UserControl x:Class="UC.TestUC"
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:UC"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="100">
<d:UserControl.DataContext>
<local:TestUC_VM/>
</d:UserControl.DataContext>
<Grid Background="Azure">
<TextBox Text="{Binding TestString}"/>
</Grid>
</UserControl>
Where its ViewModel is:
namespace UC
{
public class TestUC_VM : INotifyPropertyChanged
{
private string _testString;
public string TestString
{
get => _testString;
set
{
if (value == _testString) return;
_testString = value;
OnPropertyChanged();
}
}
public TestUC_VM()
{
TestString = "Test string.";
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow XAML:
<Window x:Class="UC.MainWindow"
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:local="clr-namespace:UC"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="200">
<Window.DataContext>
<local:MainWindow_VM/>
</Window.DataContext>
<Window.Resources>
<local:TestUC_VM x:Key="TestUC_VM"/>
</Window.Resources>
<Grid>
<local:TestUC DataContext="{StaticResource TestUC_VM}"/>
</Grid>
</Window>
And its ViewModel:
namespace UC
{
public class MainWindow_VM
{
public TestUC_VM _uc_VM;
public MainWindow_VM()
{
_uc_VM = new TestUC_VM();
Task.Run(() => ChangeString());
}
public async Task ChangeString()
{
while (true)
{
_uc_VM.TestString = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
await Task.Delay(250);
}
}
}
}
Even though I see with debugger that I'm passing through the TestString setter, the MainWindow is not updated.
I'm quite sure I'm missing something trivial in setting DataContext of UC in MainWindow, but I've not been able to find what after several hours of browsing and thinking.
Any help appreciated.
The expression
<local:TestUC DataContext="{StaticResource TestUC_VM}"/>
assigns the value of the TestUC_VM resource to the UserControl's DataContext. This is a different object than the _uc_VM member of the main view model, which you are later updating.
Turn the member into a public property
public TestUC_VM UcVm { get; } = new TestUC_VM();
and write
<local:TestUC DataContext="{Binding UcVm}"/>
Update the view model like this:
UcVm.TestString = ...
In a WPF project (code below) I have a UserControl of type MyUserControl with a dependency property, called MyOrientation of type Orientation.
On the MainWindow I have 2 instances of MyUserControl, where via XAML I set the Orientation property on one to be Horizontal and the other instance to be Vertical.
I have made the MyOrientation property a DP as I want the ability to set it directly in XAML as in this example or using a binding.
My problem is that when I run the project both instances of the UserControl show up with the Orientation = Horizontal?
Could someone please tell me what I am doing wrong and how to fix it?
Many thanks in advance.
Here is the code:
MYUSERCONTROLVIEWMODEL:
public class MyUserControlViewModel : ViewModelBase
{
private Orientation _myOrientation;
public Orientation MyOrientation
{
get { return _myOrientation; }
set
{
if (_myOrientation == value)
return;
_myOrientation = value;
OnPropertyChanged();
}
}
}
MYUSERCONTROL.XAML
<UserControl x:Class="TestUserControlDPProblem.MyUserControl"
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:TestUserControlDPProblem"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="root">
<Grid.DataContext>
<local:MyUserControlViewModel/>
</Grid.DataContext>
<StackPanel Orientation="{Binding MyOrientation}">
<TextBlock>Hello</TextBlock>
<TextBlock>There</TextBlock>
</StackPanel>
</Grid>
MYUSERCONTROL CODE BEHIND:
public partial class MyUserControl : UserControl
{
MyUserControlViewModel _vm;
public MyUserControl()
{
InitializeComponent();
_vm = root.DataContext as MyUserControlViewModel;
}
public static readonly DependencyProperty MyOrientationProperty = DependencyProperty.Register("MyOrientation", typeof(Orientation), typeof(MyUserControl), new FrameworkPropertyMetadata(Orientation.Vertical, new PropertyChangedCallback(OnMyOrientationChanged)));
private static void OnMyOrientationChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var myUserControl = o as MyUserControl;
myUserControl?.OnMyOrientationChanged((Orientation)e.OldValue, (Orientation)e.NewValue);
}
protected virtual void OnMyOrientationChanged(Orientation oldValue, Orientation newValue)
{
_vm.MyOrientation = newValue;
}
public Orientation MyOrientation
{
get
{
return (Orientation)GetValue(MyOrientationProperty);
}
set
{
SetValue(MyOrientationProperty, value);
}
}
}
MAINWINDOW.XAML
<Window x:Class="TestUserControlDPProblem.MainWindow"
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:local="clr-namespace:TestUserControlDPProblem"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<local:MyUserControl Margin="10" MyOrientation="Horizontal"/>
<local:MyUserControl Margin="10" MyOrientation="Vertical"/>
</StackPanel>
</Grid>
The "internal" view model of the UserControl makes no sense and should not be there. You should instead bind directly to the dependency property by means of a RelativeSource or ElementName Binding:
<StackPanel Orientation="{Binding MyOrientation,
RelativeSource={RelativeSource AncestorType=UserControl}}">
You wouldn't even need the PropertyChangedCallback:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty MyOrientationProperty =
DependencyProperty.Register(
nameof(MyOrientation), typeof(Orientation), typeof(MyUserControl),
new FrameworkPropertyMetadata(Orientation.Vertical));
public Orientation MyOrientation
{
get { return (Orientation)GetValue(MyOrientationProperty); }
set { SetValue(MyOrientationProperty, value); }
}
}
I created a Dependency Property in the usercontrol, but however changes in the usercontrol was NOT notified to the Viewmodel
Usercontrol
<UserControl x:Class="DPsample.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 x:Name="txtName"></TextBox>
</Grid>
UserControl.cs
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
#region SampleProperty
public static readonly DependencyProperty SamplePropertyProperty
= DependencyProperty.Register("SampleProperty",
typeof(string),
typeof(UserControl1),
new PropertyMetadata(OnSamplePropertyChanged));
public string SampleProperty
{
get { return (string)GetValue(SamplePropertyProperty); }
set { SetValue(SamplePropertyProperty, value); }
}
static void OnSamplePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as UserControl1).OnSamplePropertyChanged(e);
}
private void OnSamplePropertyChanged(DependencyPropertyChangedEventArgs e)
{
string SamplePropertyNewValue = (string)e.NewValue;
txtName.Text = SamplePropertyNewValue;
}
#endregion
}
MainWindow
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DPsample" x:Class="DPsample.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 SampleProperty="{Binding SampleText,Mode=TwoWay}" HorizontalAlignment="Left" Margin="76,89,0,0" VerticalAlignment="Top" Width="99"/>
<Button Content="Show" HorizontalAlignment="Left" Margin="76,125,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
MainWindow.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var item = this.DataContext as MainViewModel;
MessageBox.Show(item.SampleText.ToString());
}
MainViewModel.cs
public class MainViewModel : NotifyViewModelBase
{
public MainViewModel()
{
this.SampleText = "test";
}
private string _sampleText;
public string SampleText
{
get { return _sampleText; }
set { _sampleText = value; OnPropertyChanged("SampleText"); }
}
}
Bind the TextBox.Text property in the UserControl to its SampleProperty like this:
<TextBox Text="{Binding SampleProperty,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
Now you could simply remove your OnSamplePropertyChanged callback.
You might also register SampleProperty to bind two-way by default like this:
public static readonly DependencyProperty
SamplePropertyProperty = DependencyProperty.Register(
"SampleProperty", typeof(string), typeof(UserControl1),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
An alternative way to do this is an ElementName Binding. First assign the x:Name attribute to the UserControl (for example x:Name="MyUC"), then change the binding to:
<TextBox Text="{Binding ElementName=MyUC, Path=SampleProperty}"/>
I have custom user control with the only property - SubHeader.
<UserControl x:Class="ExpensesManager.TopSection"
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"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<StackPanel>
<Label Name="Header" Content="Constant header text" Style="{StaticResource Header}"/>
<Label Name="SubHeader" Content="{Binding SubHeaderText}" Style="{StaticResource SubHeader}"/>
</StackPanel>
public partial class TopSection : UserControl
{
public TopSection()
{
this.InitializeComponent();
}
public static readonly DependencyProperty SubHeaderTextProperty =
DependencyProperty.Register("SubHeaderText", typeof(string), typeof(TopSection));
public string SubHeaderText
{
get { return (string)GetValue(SubHeaderTextProperty); }
set { SetValue(SubHeaderTextProperty, value); }
}
}
There are two usages in xaml. First with the constant text:
...
<my:TopSection SubHeaderText="Constant text"/>
...
Another one using binding:
<Page x:Class="MyNamespace.MyPage"
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:my="clr-namespace:My"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
...
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText}"/>
...
</Page>
My page code behind:
public partial class MyPage : Page
{
private MyModel myModel;
public MyModel MyModel
{
get
{
return this.myModel?? (this.myModel = new MyModel());
}
}
public MyPage(MyEntity entity)
{
this.InitializeComponent();
this.MyModel.MyEntity = entity;
}
}
MyModel code:
public class MyModel : NotificationObject
{
private MyEntity myEntity;
private string subHeaderText;
public MyEntity MyEntity
{
get
{
return this.myEntity;
}
set
{
if (this.myEntity!= value)
{
this.myEntity= value;
this.RaisePropertyChanged(() => this.MyEntity);
this.RaisePropertyChanged(() => this.SubHeaderText);
}
}
}
public string SubHeaderText
{
get
{
return string.Format("Name is {0}.", this.myEntity.Name);
}
}
}
The problem is that second one doesn't work. If I pass the constant text - it is displayed, if I use binding to the other property - nothing is displayed.
Does anybody knows what's wrong with the code? Thanks.
The problem is you set DataContext on the UserControl element. It will cause the following binding
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText}"/>
to be relative to that DataContext, which is UserControl itself - so it cannot find the value.
To fix this, I suggest you not set DataContext on the UserControl, but the StackPanel inside:
<UserControl x:Class="ExpensesManager.TopSection"
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">
<StackPanel DataContext="{Binding RelativeSource={RelativeSource AncesterType=UserControl}}">
<Label Name="Header" Content="Constant header text" Style="{StaticResource Header}"/>
<Label Name="SubHeader" Content="{Binding SubHeaderText}" Style="{StaticResource SubHeader}"/>
</StackPanel>
Many people set DataContext on UserControl but that's really BAD. When you use the UserControl later, you have no idea the DataContext is actually set internally and will not respect the outside DataContext - really confusing. This rule also applies to other properties.
MyModel is a property in your DataContext? Try to check what object is your DataContext. If your data context is an object of your class MyModel you doesn't need the MyModel. part in your binding.
This kind of bindings always are to objects in your data context.
Hope this tips helps.
Declare your UserControl like this:
<my:TopSection
x:Name="myControl">
Then change your binding to this:
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText, ElementName=myControl}"/>
You didn't set the Model in your UserControl
public partial class TopSection : UserControl
{
public class SampleViewModel { get; set; }
public TopSection()
{
this.InitializeComponent();
this.DataContext = new SampleViewModel();
}
public static readonly DependencyProperty SubHeaderTextProperty =
DependencyProperty.Register("SubHeaderText", typeof(string), typeof(TopSection));
public string SubHeaderText
{
get { return (string)GetValue(SubHeaderTextProperty); }
set { SetValue(SubHeaderTextProperty, value); }
}
}
Update
Since you don't want Model to known to the View. Create a ViewModel
public class SampleViewModel : NotificationObject
{
public class MyModel { get; set; }
public class SampleViewModel()
{
MyModel = new MyModel() { SubHeaderText = "Sample" };
RaisePropertyChange("MyModel");
}
}