My issue is my OnMatrixPropertyChanged method never gets called. The label, which is bound to the same property, does update so I know binding is happening on the Matrix property.
I have a UserControl that I want to add a DependencyProperty to in order that it can be bound to. My MainWindow looks like this:
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel>
<Button
Command="{Binding LoadMatrixCommand}"
Content="Load"
Width="150">
</Button>
<Label
Content="{Binding Matrix.Title}">
</Label>
<controls:MatrixView
Matrix="{Binding Path=Matrix, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</controls:MatrixView>
</StackPanel>
In my MatrixView UserControl code-behind I have the DependencyProperty set as such:
public partial class MatrixView : UserControl
{
public static readonly DependencyProperty MatrixProperty =
DependencyProperty.Register(nameof(Matrix), typeof(Matrix), typeof(MatrixView), new PropertyMetadata(default(Matrix), OnMatrixPropertyChanged));
private static void OnMatrixPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Do Something
}
public Matrix Matrix
{
get => (Matrix)GetValue(MatrixProperty);
set => SetValue(MatrixProperty, value);
}
public MatrixView()
{
InitializeComponent();
}
}
I must be missing something very obvious...
EDIT #1: View Models
public class MatrixViewModel : ViewModelBase
{
public MatrixViewModel()
{
}
}
public class MainWindowViewModel : ViewModelBase
{
private IMatrixService _matrixService;
private Matrix _matrix;
public Matrix Matrix
{
get => _matrix;
set
{
_matrix = value;
base.RaisePropertyChanged();
}
}
public ICommand LoadMatrixCommand { get; private set; }
public MainWindowViewModel()
{
LoadMatrixCommand = new RelayCommand(LoadMatrix);
_matrixService = new MatrixService();
}
private void LoadMatrix()
{
var matrixResult = _matrixService.Get(1);
if (matrixResult.Ok)
{
Matrix = matrixResult.Value;
}
}
}
There certainly is something like
<UserControl.DataContext>
<local:MatrixViewModel/>
</UserControl.DataContext>
in the XAML of your UserControl. Remove that, because it prevents that a Binding like
<controls:MatrixView Matrix="{Binding Matrix}" />
looks up the Matrix property in the correct view model instance, i.e. the one inherited from the MainWindow.
UserControls with bindable (i.e. dependency) properties should never set their own DataContext, because doing so breaks any DataContext based bindings of these properties.
Related
This is my first question here, please understand. I've spent on this problem hours of digging nothing works for me, maybe somebody will explain me this strange (for me) problem?
I've made my app in WPF with MVVM
I got in MainWindow.xaml with usercontrol which loads view with binding:
<UserControl Content="{Binding CurrentView}" />
MainWindow DataContext is MainViewModel, which derives from BaseViewModel, where i set and get CurrentView from and implement INotifyPropertyChanged.
First CurrentView is LoginViewModel - it loads in constructor of MainViewModel properly and set the view (Usercontrol Loginview.xaml).
And I don't understand why when I change CurrentView property from this loaded LoginViewModel (it definitely changes - I checked it and NotifyPropertyChanged raises) - my view doesn't change - it's still LoginView, but should be WelcomeView.
But when I change the same property with the same code from MainViewModel - my view changes properly. Somebody could point where's an error? Is it impossible to change CurrentView property from outside of MainViewModel even it's not the part of MainViemodel but another class or what? What I'm missing here?
CODE:
public class BaseViewModel : NotifyPropertyChanged
{
private object? _currentView;
public object? CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged(nameof(CurrentView));
}
}
}
public class MainViewModel : BaseViewModel
{
public LoginViewModel LoginViewModel { get; set; }
public WelcomeViewModel WelcomeViewModel { get; set; }
[..]
public ICommand LoginCommand { get; set; } //- this works
public MainViewModel()
{
LoginViewModel = new();
WelcomeViewModel = new();
CurrentView = LoginViewModel;
// COMMANDS
LoginCommand = new RelayCommand(o => DoLogin(), o => CanLogin()); // this works
}
private bool CanLogin()
{
return true;
}
private void DoLogin()
{
CurrentView = WelcomeViewModel;
}
}
public class LoginViewModel : BaseViewModel
{
[...]
public WelcomeViewModel WelcomeViewModel { get; set; }
// COMMANDS PROPERTIES
public ICommand LoginCommand { get; set; }
public LoginViewModel()
{
WelcomeViewModel = new();
LoginCommand = new RelayCommand(o => DoLogin(), o => CanLogin());
}
private bool CanLogin()
{
return true;
}
private void DoLogin()
{
MessageBox.Show("Login!"); // message box test showes
// here will be some authentication
CurrentView = WelcomeViewModel; // property CurrentView changes
// CurrentView = new MainViewModel().WelcomeViewModel; // this way also doesn't work
}
}
and finally XAML from UserControl LoginView.xaml (command runs properly, property CurrentView changes, but view remains the same:
<Button
Width="200"
Height="50"
Margin="10"
Command="{Binding LoginCommand}"
Content="Login"
FontSize="18" />
<!-- Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}},
Path=DataContext.LoginCommand}" THIS WORKS! -->
App.xaml has:
<DataTemplate DataType="{x:Type vievmodels:LoginViewModel}">
<viewscontents:LoginView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vievmodels:WelcomeViewModel}">
<viewscontents:WelcomeView/>
</DataTemplate>
The question is: how to set DataContext ....
Do this.
Main ViewModel:
public class MainViewModel : BaseInpc
{
#region CurrentContent
private object? _currentContent;
public object? CurrentContent { get => _currentContent; set => Set(ref _currentContent, value); }
private RelayCommand _setCurrentCommand;
public RelayCommand SetCurrentCommand => _setCurrentCommand
??= new RelayCommand(content => CurrentContent = content);
#endregion
public LoginViewModel LoginViewModel { get; } = new LoginViewModel();
public WelcomeViewModel WelcomeViewModel { get; } = new WelcomeViewModel();
public MainViewModel()
{
CurrentContent = WelcomeViewModel;
}
}
public class WelcomeViewModel: BaseInpc // Derived not from MainViewModel!
{
// Some Code
}
public class LoginViewModel: BaseInpc // Derived not from MainViewModel!
{
// Some Code
}
Create an instance of MainViewModel in the application resources:
<Application.Resources>
<local:MainViewModel x:Key="mainVM"/>
</Application.Resources>
In Windows XAML:
<Window ------------
------------
DataContext="{DynamicResource mainVM}">
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginViewUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type WelcomeViewModel}">
<local:WelcomeViewUserControl/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentContent}"/>
An example of a button toggling CurrentContent:
<Button Command="{Binding SetCurrentCommand, Source={StaticResource mainVM}}"
CommandParameter="{Binding LoginViewModel, Source={StaticResource mainVM}}"/>
BaseInpc and RelayCommand classes.
The mistake you make is that there are two CurrentView variables. One is in MainViewModel and the other one is in LoginViewModel. Both classes are derived from BaseViewModel but that doesn't mean they share the same instance of CurrentView. Both have a newinstance of the CurrentView variable. Meaning that only one is bound to the DataContext of the page.
What i'm unable to see it where you assign which CurrentView to the DataContext. So i'm not able to completely answering this question.
But it looks like you have 1 window filled with 2 controls.
To solve this, you should create a 3rd ViewModel which only contains the CurrentView. Use this instance on a parent where the UserControl is used. And use the other ViewModels to the usercontrol itself.
I have a usercontrol with couple of controls inside. So I decide to use ViewModel to do managing for all those bindable value. But I find my binding is always null. So how to setup binding for ViewModel in usercontrol
MainWindows.xaml
<Window x:Class="Test.MainWindow"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<cus:Wizard WizardModel="{Binding MyModel}"/>
</StackPanel>
</Window>
MainWindows.xaml.cs
public partial class MainWindow : Window
{
private ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
}
}
ViewModel.cs(MainWindow viewmodel)
public class ViewModel : INotifyPropertyChanged
{
private Model _MyModel;
public Model MyModel
{
get
{
return _MyModel;
}
set
{
_MyModel = value;
NotifyPropertyChanged("MyModel");
}
}
}
Wizard.xaml(my UserControl)
<UserControl mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Something}" />
</Grid>
</UserControl>
Wizard.xaml.cs
public partial class Wizard : UserControl
{
private readonly object modelLock = new object();
private Model CurrentModel = new Model();
public Wizard()
{
InitializeComponent();
DataContext = CurrentModel;
}
public Model WizardModel
{
get { return (Model)this.GetValue(WizardModelProperty); }
set { this.SetValue(WizardModelProperty, value); }
}
public static readonly DependencyProperty WizardModelProperty = DependencyProperty.Register("WizardModel", typeof(Model), typeof(Wizard), new PropertyMetadata(null, new PropertyChangedCallback(ModelChanged)));
private static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Wizard)d).OnModelChanged();
}
private void OnModelChanged()
{
lock (this.modelLock)
{
if(CurrentModel != null)
{
CurrentModel = null;
}
if (WizardModel != null)
{
CurrentModel = WizardModel;
}
}
}
}
The WizardModel in UserControl is always null. So how to setup this ViewModel in UserControl
A UserControl that is supposed to operate on a particular view model class - or more precisely on a class with a particular set of public properties - may directly bind to the view model properties in its XAML.
Given a view model like
public class Model
{
public string Something { get; set; }
}
you may write a UserControl with nothing more than this XAML
<UserControl ...>
...
<TextBox Text="{Binding Something}" />
...
</UserControl>
and this code behind
public partial class Wizard : UserControl
{
public Wizard()
{
InitializeComponent();
}
}
If you now set its DataContext to an instance of Model (or any other class with a Something property), it will just work:
<local:Wizard DataContext="{Binding MyModel}"/>
Since the value of the DataContext property is inherited from parent to child elements, this will also work:
<StackPanel DataContext="{Binding MyModel}">
<local:Wizard/>
</StackPanel>
However, the UserControl still dependends on the existence of a Something property in its DataContext. In order to get rid of this dependence, your control may expose a dependency property
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register(nameof(MyText), typeof(string), typeof(Wizard));
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
and bind the element in its XAML to its own property
<UserControl ...>
...
<TextBox Text="{Binding MyText,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
...
</UserControl>
Now you would bind the control's property instead of setting its DataContext:
<local:Wizard MyText="{Binding MyModel.Something, Mode=TwoWay}"/>
I have a simple Window with a TextBox
XAML
<Window x:Class="Configurator.ConfiguratorWindow"
x:Name="ConfigWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBox x:Name="DescriptionTextBox" Text="{Binding Path=Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
</Window>
in the code behind
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(ConfiguratorWindow), new PropertyMetadata());
public string Description
{
get { return GetValue(DescriptionProperty).ToString(); }
set {
SetValue(DescriptionProperty, value);
_actual_monitor.Description = value;
}
}
}
the graphic is updating right, but when i change the text in the textbox and lose focus it doesn't update the source property.
What is wrong?
DependencyProperties are used for UserControls rather than ViewModel type bindings.
You should
Create a ConfigurationWindowViewModel (Read about MVVM) and implement INotifyPropertyChanged
Create a Property Description that utilizes the INotifyPropertyChanged
Create a new instance of that view model to be set to the DataContext of your ConfigurationWindow.
The getter and setter of the CLR wrapper of a dependency property must not contain any other code than GetValue and SetValue. The reason is explained in the XAML Loading and Dependency Properties article on MSDN.
So remove the _actual_monitor.Description = value; assignment from the setter and add a PropertyChangedCallback to react on property value changes:
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register(
"Description", typeof(string), typeof(ConfiguratorWindow),
new PropertyMetadata(DescriptionPropertyChanged));
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set { SetValue(DescriptionProperty, value); }
}
private static void DescriptionPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ConfiguratorWindow obj = d as ConfiguratorWindow;
obj._actual_monitor.Text = (string)e.newValue;
}
}
Try this
<Window x:Class="Configurator.ConfiguratorWindow"
xmlns:myWindow="clr-namespace:YourNamespace"
x:Name="ConfigWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBox x:Name="DescriptionTextBox" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type myWindow}}, Path=Description, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
public partial class ConfiguratorWindow : Window
{
public ConfiguratorWindow()
{
InitializeComponent();
}
private static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(ConfiguratorWindow), new PropertyMetadata(null, CallBack);
private static void callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var foo = d as ConfiguratorWindow ;
all you need to do, you can do here
}
public string Description
{
get { return GetValue(DescriptionProperty).ToString(); }
set { SetValue(DescriptionProperty, value);}
}
}
But it would be much easier to just have a View Model and bind to property there.
I am creating a custom "PageHeaderControl" UserControl, with a header property:
public partial class PageHeaderControl: UserControl
{
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header",
typeof(string), typeof(PageHeaderControl),
new PropertyMetadata(""));
public string Header
{
get { return GetValue(HeaderProperty) as string; }
set { SetValue(HeaderProperty, value); }
}
}
In the XAML for that control, I have:
<sdk:Label Content="{Binding Header,Mode=TwoWay}" />
Now for the problem: When I create the control, binding it only works to do this:
<my:PageHeaderControl Header="This is my page header" />
And it does not work to do this, where PageHeader is the property in my ViewModel holding the header value:
<my:PageHeaderControl Header="{Binding PageHeader,Mode=TwoWay}" />
I thought maybe my properties were messed up, but this also works:
<TextBlock Text="{Binding PageHeader,Mode=TwoWay}" />
Any ideas as to what the problem could be!
Thanks so much!!!
Edit:
In my ViewModel, PageHeader is this:
private string _pageHeader = "This is my page header";
public string PageHeader
{
get
{
return _pageHeader;
}
set
{
_pageHeader = value;
RaisePropertyChanged("PageHeader");
}
}
Edit 2:
When I put a breakpoint inside the "get" for my PageHeader property, it does not get hit AT ALL, unless I add in the TextBlock...
If I understand you correctly you're trying to bind a property of an element within your control's XAML markup to the property of the control itself.
If this is the case, see if the following helps you.
PageHeaderControl.xaml:
<UserControl x:Class="TryElementBinding.PageHeaderControl"
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"
x:Name = "MyControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Header, ElementName=MyControl}"></TextBlock>
</Grid>
PageHeaderControl.xaml.cs:
public partial class PageHeaderControl : UserControl
{
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(PageHeaderControl), new PropertyMetadata(""));
public string Header
{
get
{
return GetValue(HeaderProperty) as string;
}
set
{
SetValue(HeaderProperty, value);
}
}
public PageHeaderControl()
{
InitializeComponent();
}
}
ViewModel.cs:
public class ViewModel : INotifyPropertyChanged
{
private string _pageHeader = "This is my page header";
public string PageHeader
{
get
{
return _pageHeader;
}
set
{
_pageHeader = value;
PropertyChanged(this, new PropertyChangedEventArgs("PageHeader"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage.xaml:
<Grid x:Name="LayoutRoot" Background="White">
<my:PageHeaderControl Header="{Binding PageHeader, Mode=TwoWay}"></my:PageHeaderControl>
</Grid>
MainPage.xaml.cs:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
I'm a little bit confused and I think you missed the syntax of Binding inline expression.
after "{Binding" comes Path to your property. Is "PageHeader" is a path to your property?!
I think you mean this:
<my:PageHeader Header="{Binding PageHeader, Mode=TwoWay}" />
<TextBlock Text="{Binding PageHeader, Mode=TwoWay}" />
The problem is that Binding expression only works when you set the value of property using SetValue method and notify the parent DependencyObject that specific property has changed!
You should use a DependencyProperty to have TwoWay Binding on it, OR implement System.ComponentModel.INotifyPropertyChange interface in your class and notify the Binding object manually by calling PropertyChanged event in the interface.
The definition of PageHeader property should be like this:
public static readonly DependencyProperty PageHeaderProperty = DependencyProperty.Register("PageHeader", typeof(string), typeof(YOUROWNER), new PropertyMetadata(""));
public string PageHeader
{
get { return GetValue(PageHeaderProperty) as string; }
set { SetValue(PageHeaderProperty, value); }
}
Cheers
I've attached some WPF C# binding code - why doesn't this simple example work? (just trying to understanding binding to a custom object). That is when clicking on the button to increase the counter in the model, the label isn't updated.
<Window x:Class="testapp1.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">
<Grid>
<Button Height="23" HorizontalAlignment="Left" Margin="20,12,0,0"
Name="testButton" VerticalAlignment="Top" Width="126"
Click="testButton_Click" Content="Increase Counter" />
<Label Content="{Binding Path=TestCounter}" Height="37"
HorizontalAlignment="Right" Margin="0,12,122,0"
Name="testLabel2" VerticalAlignment="Top"
BorderThickness="3" MinWidth="200" />
</Grid>
</Window>
namespace testapp1
{
public partial class MainWindow : Window
{
public TestModel _model;
public MainWindow()
{
InitializeComponent();
InitializeComponent();
_model = new TestModel();
_model.TestCounter = 0;
this.DataContext = _model;
}
private void testButton_Click(object sender, RoutedEventArgs e)
{
_model.TestCounter = _model.TestCounter + 1;
Debug.WriteLine("TestCounter = " + _model.TestCounter);
}
}
public class TestModel : DependencyObject
{
public int TestCounter { get; set; }
}
}
thanks
For this simple example, consider using INotifyPropertyChanged and not DependencyProperties!
UPDATE
If you do want to use DPs, use the propdp snippet in VS2010 or Dr WPF's snippets for VS2008?
TestCounter needs to be a DepenencyProperty
public int TestCounter
{
get { return (int)GetValue(TestCounterProperty); }
set { SetValue(TestCounterProperty, value); }
}
// Using a DependencyProperty as the backing store for TestCounter.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestCounterProperty =
DependencyProperty.Register
("TestCounter",
typeof(int),
typeof(TestModel),
new UIPropertyMetadata(0));
You can implement the INotifyPropertyChanged interface in the System.ComponentModel namespace. I usually implement a Changed method that can take a number of property names and check for the event not being set. I do that because sometimes I have multiple properties that depend on one value and I can call one method from all of my property setters.
For instance if you had a Rectangle class with Width and Height properties and an Area read-only property that returns Width * Height, you could put Changed("Width", "Area"); in the property setter for Width.
public class TestModel : INotifyPropertyChanged
{
int m_TestCounter;
public int TestCounter {
get {
return m_TestCounter;
}
set {
m_TestCounter = value;
Changed("TestCounter");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
void Changed(params string[] propertyNames)
{
if (PropertyChanged != null)
{
foreach (string propertyName in propertyNames)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}