I have a UserControl, we'll call it "Header". It has a DependencyProperty called ProjectID, this control has a View Model and I set it to be the DataContext:
public BillingInfoHeaderControlVM VM
{
get
{
return (BillingInfoHeaderControlVM)DataContext;
}
set
{
DataContext = value;
}
}
public static readonly DependencyProperty ProjectIDProperty =
DependencyProperty.Register("ProjectID", typeof(int), typeof(BillingInfoHeaderControl), new PropertyMetadata();
public int ProjectID
{
set
{
SetValue(ProjectIDProperty, value);
}
get
{
return (int)GetValue(ProjectIDProperty);
}
}
Now what I want to do, is to bind the ProjectID of a control to this control's ProjectID:
<controls:Header Grid.Row ="0" x:Name="Header" ProjectID="{Binding ProjectID, Mode=OneWay}"></controls:Header>
Now when I run this, I get an error in the InitializeControl() method that states "
Property Get method was not found.
From what I'm reading, I'm seeing this is because the Binding ProjectID is relative to the data context of the control. Of course I could set the ElementName within the binding:
<controls:Header Grid.Row ="0" x:Name="Header" ProjectID="{Binding ProjectID, Mode=OneWay, ElementName=ParentControl}"></controls:Header>
But this is ugly, and to be honest we don't want to have to remember to do this for this control whenever we use it. What other options do I have? Is there a way to set the source of the binding to use the DataContext of the parent?
I duplicated your concept in code and it compiles and runs fine.
I have included the control code and the viewmodel below in case you are doing something different.
*Note: I kept the viewmodel ProjectID as a simple update property.:
namespace Demo1
{
public partial class BillingInfoHeaderControl : UserControl
{
public BillingInfoHeaderControl()
{
InitializeComponent();
this.DataContext = new BillingInfoHeaderControlVM();
}
public int ProjectId
{
get { return (int)GetValue(ProjectIdProperty); }
set { SetValue(ProjectIdProperty, value); }
}
public static readonly DependencyProperty ProjectIdProperty =
DependencyProperty.Register("ProjectId", typeof(int), typeof(BillingInfoHeaderControl),
new PropertyMetadata(0));
}
}
namespace Demo1
{
public class BillingInfoHeaderControlVM : INotifyPropertyChanged
{
private int _projectId;
public int ProjectId
{
get { return _projectId; }
set
{
if (_projectId != value)
{
_projectId = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ProjectId"));
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Related
This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
Closed 3 years ago.
I am trying to create a simple UserControl in WPF for reusing in my other applications. It is a simple DateRangePicker. Some of the control's properties are bound to child UI elements and hence I implement INotifyPropertyChanged. My control.xaml.cs looks like below (only relevant portions)
public partial class DateRangePicker : UserControl, INotifyPropertyChanged
{
public DateRangePicker()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
public static readonly DependencyProperty InitialFromDateProperty =
DependencyProperty.Register("InitialFromDate", typeof(DateTime), typeof(DateRangePicker), new PropertyMetadata(default(DateTime),
new PropertyChangedCallback(OnInitialDateRangeChanged)));
public static readonly DependencyProperty InitialToDateProperty =
DependencyProperty.Register("InitialToDate", typeof(DateTime), typeof(DateRangePicker), new PropertyMetadata(default(DateTime),
new PropertyChangedCallback(OnInitialDateRangeChanged)));
public static readonly DependencyProperty SelectedFromDateProperty =
DependencyProperty.Register("SelectedFromDate", typeof(DateTime), typeof(DateRangePicker), new PropertyMetadata(default(DateTime), null));
public static readonly DependencyProperty SelectedToDateProperty =
DependencyProperty.Register("SelectedToDate", typeof(DateTime), typeof(DateRangePicker), new PropertyMetadata(default(DateTime), null));
public DateTime InitialFromDate
{
get { return (DateTime)GetValue(InitialFromDateProperty); }
set
{
SetValue(InitialFromDateProperty, value);
}
}
public DateTime InitialToDate
{
get { return (DateTime)GetValue(InitialToDateProperty); }
set
{
SetValue(InitialToDateProperty, value);
}
}
public DateTime SelectedFromDate
{
get { return (DateTime)GetValue(SelectedFromDateProperty); }
set
{
SetValue(SelectedFromDateProperty, value);
}
}
public DateTime SelectedToDate
{
get { return (DateTime)GetValue(SelectedToDateProperty); }
set
{
SetValue(SelectedToDateProperty, value);
}
}
private static void OnInitialDateRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DateRangePicker control = (DateRangePicker)d;
control.RefreshLists();
}
My Test application xaml file which hosts the control looks like below (some lines including namespaces have been removed):
<Window x:Class="WpfApp3.MainWindow"
xmlns:ControlDateRangePicker ="clr-namespace:SurfServer.Apps.Common.UI"
xmlns:local="clr-namespace:WpfApp3"
Title="MainWindow" Height="414.831" Width="565.808">
<Grid>
<ControlDateRangePicker:DateRangePicker
InitialFromDate="{Binding InitialFromDateVM, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
InitialToDate="{Binding InitialToDateVM, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedFromDate ="{Binding SelectedFromDateVM, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedToDate ="{Binding SelectedToDateVM, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
HorizontalAlignment="Left" Margin="136,78,0,0" VerticalAlignment="Top" Width="347"/>
</Grid>
</Window>
And my Test Application xaml.cs looks like this
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
try
{
var viewModel = new SampleViewModel();
DataContext = viewModel;
viewModel.Initialize();
}
catch (Exception)
{
Close();
}
}
}
My Test Application's ViewModel looks like below :
class SampleViewModel : ViewModelBase
{
private DateTime m_dtInitialFrom;
public DateTime InitialFromDateVM
{
get => m_dtInitialFrom;
set
{
m_dtInitialFrom = value;
RaisePropertyChangedEvent(nameof(InitialFromDateVM));
}
}
private DateTime m_dtInitialTo;
public DateTime InitialToDateVM
{
get => m_dtInitialTo;
set
{
m_dtInitialTo = value;
RaisePropertyChangedEvent(nameof(InitialToDateVM));
}
}
public DateTime SelectedFromDateVM
{
get;
set;
}
public DateTime SelectedToDateVM
{
get;
set;
}
public void Initialize()
{
InitialFromDateVM = new DateTime(DateTime.Now.Year - 1, DateTime.Now.Month, DateTime.Now.Day);
InitialToDateVM = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
}
As you can see, some of the properties in the test application's View model are bound to my control's (DateRangePicker) dependency properites.
Now the problem I am facing is, though I am trying to set the Initial values in my Test Application's ViewModel (in the Initialize method), it looks like the binding does not work and I am not getting a callback in my control (in fact, I am unable to hit even the 'set' of the dependency property itself). What am I trying to do wrong here ?
You have to remove setting DataContext from DateRangePicker constructor. So the DataContext for your UserControl was not your ViewModel SampleViewModel but the UserControl itself.
public partial class DateRangePicker : UserControl, INotifyPropertyChanged
{
public DateRangePicker()
{
InitializeComponent();
//DataContext = this; REMOVE
}
}
Well, having a go at MVVM with UWP template 10. I have read many pages, and although everyone tries to say its really easy, I still can't make it work.
To put it into context, OCR is being run on an image, and I would like the text to be displayed in textbox automatically.
Here is my Model:
public class TextProcessing
{
private string _ocrText;
public string OcrText
{
get { return _ocrText; }
set
{
_ocrText = value;
}
}
}
Here is my ViewModel:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private TextProcessing _ocrTextVM;
public ScanPageViewModel()
{
_ocrTextVM = new TextProcessing();
}
public TextProcessing OcrTextVM
{
get { return _ocrTextVM; }
set {
_ocrTextVM = value;
this.OnPropertyChanged("OcrTextVM");
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Here is my View:
<TextBox x:Name="rtbOcr"
Text="{Binding OcrTextVM.OcrText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Firstly, that is not working. Could someone try to show where I am going wrong?
Then, the data is coming from a Services file, how would the Services update the value? What would be the correct code?
Thanks in advance.
Following code is cite from code.msdn (How to achieve MVVM design patterns in UWP), it will be helpful for you:
Check you code step by step.
1.ViewModel implemented interface INotifyPropertyChanged,and in property set method invoked PropertyChanged, like this:
public sealed class MainPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _productName;
public string ProductName
{
get { return _productName; }
set
{
_productName = value;
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(ProductName)));
}
}
}
}
2.Initialize you ViewMode in you page, and set DataContext as the ViewMode, like this:
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; set; } = new MainPageViewModel();
public MainPage()
{
...
this.DataContext = ViewModel;
}
}
3.In you xaml, binding data from viewMode, like this:
<TextBox Text="{Binding Path=ProductName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="ProductNameTextBox" TextChanged="ProductNameTextBox_TextChanged" />
Your OnPropertyChanged call on OcrTextVM isn't actually called in your case, since you set the value in the constructor to its backing field and bypass the property.
If you set the value via the property, it should work:
public ScanPageViewModel()
{
OcrTextVM = new TextProcessing();
}
Of course your view needs to know that ScanPageViewModel is its DataContext. Easiest way to do it is in the constructor of the code-behind of your view:
public OcrView()
{
DataContext = new ScanPageViewModel();
InitializeComponent();
}
Assuming your OCR service is returning a new TextProcessing object on usage, setting the property of OcrTextVM should suffice:
public class ScanPageViewModel : ViewModelBase, INotifyPropertyChanged
{
//...
private void GetOcrFromService()
{
//...
TextProcessing value = OcrService.Get();
OcrTextVM = value;
}
}
On a note, the OcrTextVM name doesn't really reflect what the property is doing, since it doesn't look like it's a viewmodel. Consider renaming it.
Actually, it is very easy once I manage to understand. Here is the code needed to update a TextBox.Text
In the Models:
public class DisplayText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
In the XAML file:
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... />
In the ViewModels:
private DisplayText _helper = new DisplayText();
public DisplayText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
Then any mod from the ViewModels:
Helper.Text = "Whatever text, or method returning a string";
I am new to the binding concept and got stuck with the following.
public sealed partial class MainPage : Page
{
Model model;
public MainPage()
{
this.InitializeComponent();
model = new Model();
this.DataContext = model;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
model.Name = "My New Name";
}
}
class Model : DependencyObject
{
public static DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Model), new PropertyMetadata("My Name"));
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
I have bound the Name property to Text property of TextView. All I need to do is, on the button click I want to update the Name value that will have to update the text box value. I thought, if I use dependency property instead of normal CLR property, I dont need to implement INotifyPropertyChanged.
But the value in the UI is not updating as expected. Am I missing something?
Thanks in advance.
There are a couple things that need to be addressed with your question. First of all, your model does not need to inherit from DependencyObject, rather it should implement INotifyPropertyChanged:
public class Model : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
An object that implements INotifyProperty can then be used as a DependencyProperty in your page/window/object:
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model",
typeof(Model), typeof(MainWindow));
public Model Model
{
get { return (Model)GetValue(ModelProperty); }
set { SetValue(ModelProperty, value); }
}
Finally, then, you can bind your TextBox.Text property to that in the XAML:
<Grid>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Name}"/>
<Button Click="Button_Click">Click</Button>
</StackPanel>
</Grid>
The INotifyPropertyChanged is still necessary here because there needs to be a way for the UI to know that the model object has been updated.
How do i pass a bool from the viewmodel to the view and change the value.
For example in my viewmodel i have created a bool
public bool load
{
get { return m_load; }
set
{
m_load = value;
OnPropertyChanged(this, o => o.load);
}
}
I then have my SelectedVm code
public ViewModel SelectedVm
{
get { return _selectedVm; }
set
{
_selectedVm = value;
if (_selectedVm != null && load == true)
{
_selectedVm.Load();
}
Load = false;
OnPropertyChanged(this, o => o.SelectedVm);
}
}
In my View the SelectedVm is bound twice but only on one of the bindings do i want Load to be called, hence the need to change the bool load
So in my view if i have the following
<ListView Grid.Row="1" Name="Sample"
ItemsSource="{Binding Path=SampleViewModel}"
SelectedItem="{Binding SelectedVm, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True" Width="500">
</ListView>
How do i change the bool load to either true or false
All of the above are just quick samples, i think this is probably quite simple however i am not that used to WPF and am still learning. any advice would be great
Ok if you want to get value of Load in View and you want to do it in pure MVVM pattern then create DependencyProperty of bool type in View and Bind it to Load property of VM like
public partial class MainWindow : Window
{
public static readonly DependencyProperty LoadProperty = DependencyProperty.Register("MyCustom", typeof(bool), typeof(MainWindow), new PropertyMetadata(new PropertyChangedCallback(LoadPropertyChangedCallback)));
public bool Load
{
get
{
return (bool)this.GetValue(LoadProperty) ;
}
set
{
this.SetValue(LoadProperty, value);
}
}
static void LoadPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Do your load stuff here
}
public MainWindow()
{
InitializeComponent();
this.SetBinding(LoadProperty, new Binding("load"));
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
load = true;
}
bool m_load;
public bool load
{
get { return m_load; }
set
{
m_load = value;
OnPropertyChanged("load");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
The code you published ensures that Load() is being called once, even with multiple bindings.
If I understand your real question correctly, you are actually asking for a way to make sure that each SelectedVM is calling Load() function once, and only once. Right?
if that so, you need to add a bool property to the ViewModel class, instead of the main class, that's all.
And then:
public ViewModel SelectedVm
{
get { return _selectedVm; }
set
{
_selectedVm = value;
if (_selectedVm != null && _selectedVm.load == true)
{
_selectedVm.Load();
_selectedVm.load = false;
}
OnPropertyChanged(this, o => o.SelectedVm);
}
}
you can keep your XAML as it is.
In developing some UserControls for internal use I followed this exmaple from MSDN http://msdn.microsoft.com/en-us/library/vstudio/ee712573(v=vs.100).aspx
The public value of one control is used by another control. The way I have this working currently is hooking into an event that is fired in the first control through code-behind. I am thinking that making one or both of the properties DependencyProperties which would eliminate the need for the code-behind.
public partial class UserControl1 : UserControl
{
private DataModel1 dm;
public UserControl1()
{
this.DataContext = new DataModel1();
dm = (DataModel1)DataContext;
InitializeComponent();
}
public DataValue CurrentValue
{
get { return dm.CurrentValue; }
set { dm.CurrentValue = value; }
}
}
public class DataModel1 : INotifyPropertyChanged
{
private DataValue _myData = new DataValue();
public DataValue CurrentValue
{
get { return _myData; }
set { if (_myData != value) {_myData = value OnPropertyChanged("CurrentValue"); }
}
// INotifyPropertyChanged Section....
}
The property is just a pass through from the DataModel1 class.
Both UserControls are very similar in their structure and have the same public properties. I would like to replace the code behind eventhandler with a Binding similar, I think to:
<my:UserControl1 Name="UserControl1" />
<my:UserControl2 CurrentValue={Binding ElementName="UserControl1", Path="CurrentValue"} />
but the standard examples of DependencyProperties have getters and setter that use the GetValue and SetValue functions which use a generated backing object instead of allowing a pass through.
public DataValue CurrentValue
{
get { return (DataValue)GetValue(CurrentValueProperty); }
set { SetValue(CurrentValueProperty, value); }
}
I think the DP should look like:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1));
How can I change the definition of the public backing property to support the databinding pass through?
I found that jumping into the OnPropertyChanged event allowed me to pass the data through to the DataModel1. I am not 100% sure that this is the correct answer but it gets the job done.
Here is the corrected code:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1),
new PropertyMetadata(new PropertyChangedCallback(OnCurrenValueChanged)));
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
if (e.NewValue != null)
{
uc.dm.CurrentValue = e.NewValue as DataValue;
}
}
public DataValue CurrentValue
{
get { return GetValue(CurrentValueProperty) as DataValue; }
set { SetValue(CurrentValueProperty, value); }
}