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.
Related
I have had some problems with data bindings in WPF, so I have been playing around to try to figure out what is going on. But I ran into something that I do not understand, and I hope someone could explain it to me. The code below is not anything I'm trying to use, it is only for testing.
I have a simple class "Lamp" with only one string property "Name". I also override ToString(), so that it returns the name.
In a "ViewModel" class I create a "Lamp" property and a ICommand:
class ViewModel : ViewModelBase
{
private Lamp _lamp1;
public Lamp Lamp1
{
get { return _lamp1; }
set { _lamp1 = value; }// OnPropertyChanged("Lamp1"); }
}
public ICommand Lamp_click { get { return new RelayCommand(param => LampClickExecute(param)); } }
public ViewModel()
{
Lamp1 = new Lamp() { Name = "Test" };
}
private void LampClickExecute(object param)
{
var name = Lamp1.Name + "I";
//HERE IS THE QUESTION!
//Lamp1 = new Lamp() { Name = name };
Lamp1.Name = name;
OnPropertyChanged("Lamp1");
}
}
In the view, I only have a button that binds to the command, and a label that I'm binding to Lamp1:
<Button x:Name="btn_lamp" Content="Button" HorizontalAlignment="Left" Margin="859,27,0,0" VerticalAlignment="Top" Height="29" Command="{Binding Path= Lamp_click}" CommandParameter="{Binding Path=Lamp1 }"/>
<Label Content= "{Binding Lamp1}" HorizontalAlignment="Left" Margin="797,56,0,0" VerticalAlignment="Top"/>
If I in the command create a new instance of "Lamp" with a new name and call OnPropertyChanged (still in the command, it is commented away in the setter) everything is fine and the new value is shown in the view. But if I do not create a new instance, instead just changing the name of the current one, the view is not updated. I have put a breakpoint in the command to see that everytime the button is clicked, there is an "I" added to the name, nothing strange there.
What is going on behind the scenes here? Is it somehow required that the setter is called, eventhough OnPropertyChanged is called in the command?
As I said, I'm not trying to acheive anyhting specific with this code, just want this behaviour explained.
UPDATE:
My ViewModelBase looks like this:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You should use the INotifyPropertyChanged and add the code to your ViewModelBase class to update any object on the View.
{
public class ViewModelBase: INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
}
}
And then your code should call the OnPropertyChanged
public Lamp Lamp1
{
get { return _lamp1; }
set { SetProperty(ref _lamp1, value; }
}
In your code you are not changing the property Lamp1. You are changing the property in the lamp class Name. If you implement INotifyPropertyChanged in you lamp class with the Name field it will update. With your current code if you changed Lamp1 to a new instance of Lamp with a different name then it would record the change because you are changing the Lamp1 field.
public class Lamp : NotifyChange { //NotifyChange is the INotifyPropertyChanged implementation in a base class
private _Name;
public Name{
get{ return _Name; }
set{
if( _Name != value ) {
_Name = value;
OnPropertyChanged( nameof( Name ) );
}
}
}
}
The property change needs to be implemented where the change is happening or it won't know that its changed. Hope that makes sense why you are not getting the update to show in your view.
I have a ViewModel that is a DependencyObject for which the DependencyPropertys are not updating the View with the new values.
A sample property (the get/set wrapper is called as expected)
public static readonly DependencyProperty WeaponNameProperty = DependencyProperty.Register(
"WeaponName",
typeof(string),
typeof(WeaponSystemVM),
new PropertyMetadata(null, new PropertyChangedCallback(OnWeaponNameChanged)));
public string WeaponName
{
get { return (string)GetValue(WeaponNameProperty); }
set { SetValue(WeaponNameProperty, value); }
}
The Callback (called when WeaponName is changed)
private static void OnWeaponNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WeaponSystemVM vm = d as WeaponSystemVM;
if (vm != null)
{ vm.CommandAddWeaponSystem.RaiseCanExecuteChanged(); }
}
The CanExecute Delegate (gets run as expected and updates the relevant Button)
private bool CanAddSystem()
{
if (string.IsNullOrWhiteSpace(WeaponName)) return false;
if (string.IsNullOrWhiteSpace(WeaponLock)) return false;
if (string.IsNullOrWhiteSpace(WeaponDamage)) return false;
if (string.IsNullOrWhiteSpace(WeaponAttack)) return false;
return true;
}
The input TextBox
<TextBox x:Name="NameInput" Text="{Binding WeaponName, Mode=TwoWay}" Margin="12,4" RelativePanel.Below="NameAdorner" RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"/>
The output TextBlock (is NOT updated with the new value and the DataContext is the same as the input TextBox)
<TextBlock Text="{Binding WeaponName}"/>
Frustratingly, it seems to be just this implementation that isn't working.
In an attempt to reproduce the issue, I created a seperate project without all the extra info associated with my app, and the View is being updated exactly as expected.
What I don't understand is what is not being done correctly in this implementation. The ViewModel is updating exactly as expected. The Bindings are valid according to the LiveVisualTree.
Can anyone point me to the issue?
You shouldn't use DependencyPropertys in your ViewModel: it is a markup class, used for binding on the View side. Overkill and out of scope for it being used that way.
You should implement INotifyPropertyChanged, and fire the INotifyPropertyChanged.PropertyChanged event in every single property you want to notify the UI about.
Something like:
your ViewModel inherits from
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetAndRaiseIfChanged<T>(
ref T backingField,
T newValue,
[CallerMemberName] string propertyName = null)
{
if (!object.Equals(backingField, newValue))
return;
backingField = newValue;
this.RaisePropertyChanged(propertyName);
}
}
and in your ViewModel you define your property like
private string _weaponName;
public string WeaponName
{
get { return this._weaponName; }
set { SetAndRaiseIfChanged(ref this._weaponName, value); }
}
a more concise CanAddSystem
private bool CanAddSystem()
{
return
!string.IsNullOrWhiteSpace(WeaponName)
&& !string.IsNullOrWhiteSpace(WeaponLock)
&& !string.IsNullOrWhiteSpace(WeaponDamage)
&& !string.IsNullOrWhiteSpace(WeaponAttack);
}
build your ViewModel's command with something that implements ICommand interface (something like a RelayCommand)
the View piece would be
<TextBlock Text="{Binding WeaponName}"/>
and you're done: when you bind an ICommand to the UI, the system automatically updates the CanExecute reading it from the ViewModel.
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 having a binding issue I wasn't able to figure out for the past two days. I have thoroughly went through most of the relevant threads on SO, and I still wasn't able to pinpoint where my error lies.
The issue I'm having is with one of the textboxes in my program. The purpose of it is to show the file the user has selected from the file browser. I have bound the text property of it to a string called parameterFileSelected but the textbox never updates even though debugging seems to be showing that the iNotifyPropertyChanged is called and executed properly.
Please help me take a look at my code below if there are any mistakes in my code.
The textbox is part of an xaml called GenerateReports and this view is tied to the GenerateReportsViewModel as follows:
Code for setting datacontext to GenerateReportsViewModel
<Grid >
<Grid.DataContext>
<vm:GenerateReportsViewModel/>
</Grid.DataContext>
<Grid.ColumnDefinitions>
....
Code for TextBox. I have tried removing the Twoway mode, changing it to Oneway and removing the mode but there is no difference.
<TextBox Grid.Column="2" Grid.Row="1" Margin="5" Text="{Binding parameterFileSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
To get the file browser and then to pass the selected file result to the GenerateReportsViewModel, this is the function in the codebehind file. The genviewmodel is initialized in the beginning of the codebehind file as GenerateReportsViewModel genViewModel = new GenerateReportsViewModel();
private void ParaFileButtonClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
DataContext = genViewModel;
genViewModel.updateParameterFileSelected(openFileDialog.FileName.ToString());
}
}
This is the code that's called in GenerateReportsViewModel to update the parameterFileSelected string the textbox is bound to.
class GenerateReportsViewModel : ViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get { return _parameterFileSelected; }
set { SetValue(ref _parameterFileSelected, value); }
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
Here is the ViewModelBase the viewmodel is attached to.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
if (property != null)
{
if (property.Equals(value)) return;
}
OnPropertyChanged(propertyName);
property = value;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
EDIT
Working Solution after Applying Kevin's Suggestions
For simplicity sake, the Datacontext was set in the XAML.
<Grid>
<Grid.DataContext>
<vm:GenerateReportsViewModel x:Name="generateReportsViewModel"/>
</Grid.DataContext>
Then, I call the string the textbox was bound to, in the viewmodel directly from code behind.
private void ParaFileButtonClick(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
generateReportsViewModel.parameterFileSelected = openFileDialog.FileName.ToString();
}
}
The ViewModel now uses Kevin's ViewModelBase:
public class GenerateReportsViewModel : ViewModelBase
{
public string parameterFileSelected
{
get { return this.GetValue<string>(); }
set { this.SetValue(value); }
}
}
Thank you Kevin for your solution. Now my 2-day-long problem is solved.
I found out that my previous ViewModelBase was calling iNotifyPropertyChanged but somehow when the View was updated, the value was null instead.
I'm trying to understand why using the ref keyword in your viewModel. I learned a nice way to create the BaseViewModel from the Classon and Baxter book which you can find below. The view-model implements the INotifyPropertyChanged like you did. What you did with [CallerMemberName] is great, it's really magical the way we can reference to our properties thanks to it.
The view model uses a the dictionary to store its properties. It uses a pretty neat trick of looking through the dictionnary keys to see if we contain the string name of the property.Otherwise, we will return a default T value.
public class CommonBaseViewModel: INotifyPropertyChanged
{
private Dictionary<string, object> Values { get; set; }
protected CommonBaseViewModel()
{
this.Values = new Dictionary<string, object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected T GetValue<T>([CallerMemberName] string name=null)
{
if (this.Values.ContainsKey(name))
{
return (T)this.Values[name];
}
else
{
return default(T);
}
}
protected void SetValue(object value, [CallerMemberName] string name = null)
{
this.Values[name] = value;
//notify my property
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected void OnPropertyChanged([CallerMemberName] string name=null)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
}
As for your GenerateReportViewModel, with the common view model that I provided you, your class then becomes :
public class GenerateReportsViewModel : CommonViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get { return _parameterFileSelected; }
set { SetValue(ref _parameterFileSelected, value); }
}
get
{
return this.GetValue<string>();
}
set
{
this.SetValue(value);
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
Oh before I forgot, I don't know if it was your intention, but your GenerateReportViewModel is private. This has some impact on your code. Don't forget that by defaut, classes are private!
As for your code behind, even though it could be consider bad practice, I recommend that you have a private field (OpenFileDialog _openFileDialog)that you construct while initializing your page. Because doing it each time your clicking your button is going to consume more data that you need your application to.
//EDIT
I have review my code,and it seemed that the property was not programmed correctly.
public class GenerateReportsViewModel : CommonViewModelBase
{
private string _parameterFileSelected;
public string parameterFileSelected
{
get
{
return this.GetValue<string>();
}
set
{
this.SetValue(value);
}
public void updateParameterFileSelected(string parameterFile)
{
parameterFileSelected = parameterFile;
}
}
More about my comment about constructing the page and binding the view model. While creating your page, you have to create the view-model for that page and then bind it to the data context.
I don't know what you do in your code, but I could provide with this sample such as
public GenerateReportView()
{
InitializeComponent();
//Some operations
var generateReportViewModel = new GenerateReportViewModel();
this.DataContext = generateReportViewModel;
}
There must be a lot of questions surrounding this area but I couldn't find anything to help in my instance.
The problem I'm experiencing is getting my ViewModel, and specifically a property within ViewModel, to be updated to my View. Below is my implementation. I think I understand where I'm going wrong but not sure how to resolve it.
I have a Module that has a list and edit view. Quite simply lists domain objects and then ability to edit a domain object.
My xaml binds the DataContent to a ViewModel property in my View.
I then use the INavigationAware.NavigateTo method to navigate to my ViewModel and this is where I load the domain object.
The problem is that obviously this is not reflected back to the View. The view already has an instance of the ViewModel. This method worked fine when the ViewModel was using a list of objects using ObservableCollection. However, this did not work when using a simple object or even an ObservableObject.
Could someone please help my understanding or point me to some links with a better implementation of what I am trying to achieve?
MyModule
public class MyModule : IModule
{
private readonly IRegionManager _regionManager;
public MyModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
_regionManager.RegisterViewWithRegion(Constants.MainRegionName, typeof(MyListView));
_regionManager.RegisterViewWithRegion(Constants.MainRegionName, typeof(MyEditView));
}
}
XAML
<UserControl
DataContext="ViewModel">
...
<TextBlock Text="{Binding Path=MyDomainObject.AProperty}" />
...
View
public partial class MyEditView
{
public readonly static string ViewName = "MyEditView";
public MyEditView(MyEditViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public MyEditViewModel ViewModel
{
get { return DataContext as MyEditViewModel; }
private set { DataContext = value; }
}
}
ViewModel
public class MyViewModel : INavigationAware
{
private readonly IRegionManager _regionManager;
public MyDomainObject MyDomainObject { get; set; }
public void Load(ViewModelKey key)
{
// get domain object
// this method worked when MyDomainObject was
// ObservableCollection<T> as just adding elements to list
// where this is creating a new instance of MyDomainObject
var id = parameter from navigationContext;
MyDomainObejct = server.GetDomainObject(id);
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
var key = key from navigationContext;
Load(key);
}
}
SOLUTION
public class MyEditViewModel : INavigationAware
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private MyDomainObject _myDomainObject;
public MyDomainObject MyDomainObject
{
get
{
return _myDomainObject;
}
set
{
if (value != _myDomainObject)
{
_myDomainObject = value;
NotifyPropertyChanged();
}
}
}
View
public partial class MyEditView
{
public MyEditView(MyEditViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
}
public MyEditViewModel ViewModel
{
get { return DataContext as MyEditViewModel; }
private set { DataContext = value; }
}
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!(sender is MyEditViewModel))
return;
ViewModel = (MyEditViewModel)sender;
}
}
For your binding to update you need to implement INotifyPropertyChanged and raise PropertyChanged Event on the set accessor of your domain object.
public event PropertyChangedEventHandler PropertyChanged = delegate {};
public MyDomainObject MyDomainObject
{
get
{
return myDomainObject;
}
set
{
if(value != myDomainObject)
{
myDomainObject = value;
RaisePropertyChanged("MyDomainObject");
}
}
}
private void RaisePropertyChanged(String p)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
Or as in the Prism book, inherit NotificationObject and call RaisePropertyChanged(()=> PropertyName) which is refactoring-safe