WPF Notify changes on object - wpf

I have a gridview were I define some columns, like this...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProp}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
I bind my gridview to a collection and implemts INotifyPropertyChanged in the property MyProp. This works well and any changes of MyProp are reflected to the gridview.
If I add another column that is bound to the object itself I dont get any notifications/updates. My code...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource myConverter}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
I think I need something like INotifyPropertyChanged for the object but I have no idea how to do this. Any suggestions?

Yes, the actual instance itself never changes - only its properties.
Presumably your converter relies on a bunch of properties from the object you've bound to? If so, you could use a MultiBinding and change your converter to an IMultiValueConverter. Then you can bind to all the dependent properties that might cause the TextBlock to update.

Make the object impletment the interface INotifyPropertyChanged
Here is an example from MSDN
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerName = String.Empty;
private string companyNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerName = "no data";
companyNameValue = "no data";
phoneNumberValue = "no data";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CompanyName
{
get {return this.companyNameValue;}
set
{
if (value != this.companyNameValue)
{
this.companyNameValue = value;
NotifyPropertyChanged("CompanyName");
}
}
}
public string PhoneNumber
{
get { return this.phoneNumberValue; }
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged("PhoneNumber");
}
}
}
}

Related

WPF MVVM Data Binding - One Way?

I am new to WPF but have an small understanding of MVVM, so far this is what I have implemented.
UpdateTableView - View (Short snippet of larger user control)
<UserContol.DataContext>
<local:UpdateTableViewModel />
</UserContol.DataContext>
<StackPanel>
<TextBox Text="{Binding InputPath}"/>
<TextBlock Content="Placeholder" />
</StackPanel>
UpdateTableModel - Model
public class UpdateTableModel : ObservableObject
{
private string _inputPath;
public string InputPath
{
get
{
return _inputPath;
}
set
{
if (value != _inputPath)
{
_inputPath = value;
OnPropertyChanged("InputPath");
}
}
}
}
ObservableObject
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanaged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanaged;
if (handler != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And an empty UpdateTableViewModel
class UpdateTableViewModel : ObservableObject { }
My question is how would I use data binding so that as a user when I enter a inputPath in the text box, firstly whatever I type is store in the property _inputPath so I can use it in code behind and additionally be reflected in the text block.
I have done some research and found about one way and two way data binding and can't really work out what else I need to add for my desired functionality.
Thanks in advance.
Your view models must contain the properties you want to bind to.
Generally the TextBox.Text property automatically binds TwoWay. This is the default behavior. So, without specifying the Binding.Mode explicitly, the text entered into the TextBox will be automatically sent to the binding source. In your case the input would be automatically sent to the InputPath property.
UpdateTableModel.cs
public class UpdateTableModel
{
public void SaveUserNameToFile(string filePath, string userName)
{
File.AppendAllText(filePath, userName, Encoding.UTF8);
}
}
UpdateTableViewModel.cs
An implementation of RelayCommand can be found at
Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern - Relaying Command Logic
class UpdateTableViewModel : ObservableObject
{
private UpdateTableModel UpdateTableModel { get; }
public ICommand SaveUserCommand => new RelayCommand(SaveUserName);
private string _userName;
public string UserName
{
get => _userName;
set
{
if (value != _userName)
{
_userName = value;
OnPropertyChanged(nameof(UserName));
}
}
}
private string _inputPath;
public string InputPath
{
get => _inputPath;
set
{
if (value != _inputPath)
{
_inputPath = value;
OnPropertyChanged(nameof(InputPath));
}
}
}
public UpdateTableViewModel()
{
this.UpdateTableModel = new UpdateTableModel();
}
// Alternative constructor
public UpdateTableViewModel(UpdateTableModel updateTableModel)
{
this.UpdateTableModel = updateTableModel;
}
private void SaveUserName(object param)
{
// Pass the data to the model
this.UpdateTableModel.SaveUserNameToFile(this.InputPath, this.UserName);
}
}
UpdateTableView.xaml
<UserControl>
<UserContol.DataContext>
<local:UpdateTableViewModel />
</UserContol.DataContext>
<StackPanel>
<TextBox Text="{Binding UserName}" />
<TextBox Text="{Binding InputPath}" />
<Button Command="{Binding SaveUserCommand}"
Content="Save to File" />
</StackPanel>
</UserControl>

ListBox bind to ObservableCollection is not updated with collection

I have next model:
public class MyModel
{
public ObservableCollection<MyObject> MyList {get; set;}
}
public class MyObject
{
MyObservableDictionary MyDictionary {get; set;}
}
public class MyObservableDictionary : ObservableCollection<EnymValue>
{
}
public class EnymValue : INotifyPropertyChanged
{
private MyEnum key;
private string value;
public MyEnum Key
{
get
{
return this.key;
}
set
{
this.key = value;
NotifyPropertyChanged("Key");
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
public LanguageValue(MyEnum key, string value)
{
this.Key = key;
this.Value = value;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public enum MyEnum
{
}
And on View I have a ListBox:
<ListBox x:Name="MyList" SelectionMode="Single" ItemsSource="{Binding Path=MyList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyDictionary, Mode=OneWay, Converter={StaticResource myEnumToTextConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
(myEnumToTextConverter converter is just selects first element from collection and return it's value, or some specified constant if collection is null or empty)
I want my Model's list box to be updated on view, when any EnymValue values are changed.
Is it possible somehow to implement this?
Currently the view is not updated when Value changed.
I've tried to inherit EnymValue from INotifyPropertyChanged, but this didn't helped. Looks like PropertyChanged == null on EnymValue.NotifyPropertyChanged when property updated.
ObservableCollection is able to notify UI about changes when collection itself is changed(elemends are added or deleted). But ObservableCollection is not aware of changes that are happening when you modify one of it's items. To solve the problem you may subscribe to CollectionChange event of observable collection, and when new item is added, subscribe to new items's PropertyChanged. When PropertyChanged event is raised, you can trigger notification on your list OnPropertyChanged(()=>MyItems); You should be careful implementing this solution and remember to unsubscribe from the event's to avoid memory leaks.
An example of what I mean you can see in this answer.
Your MyDictionary should force a refresh. Easiest way is to re-assign its old value, and implement INPC in MyObject like below :
public class MyObject: INotifyPropertyChanged
{
MyObservableDictionary _myDictionary;
public MyObservableDictionary MyDictionary {
get
{
return _myDictionary;
}
set
{
_myDictionary = value;
OnPropertyChanged("MyDictionary");
}
}
public MyObject()
{
MyDictionary = new MyObservableDictionary();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Sample code to change Value :
private void Button_Click(object sender, RoutedEventArgs e)
{
// vm is ViewModel instance, vm is DataContext set for Window
var old = vm.MyList[0].MyDictionary;
vm.MyList[0].MyDictionary[0].Value = "aaaa";
vm.MyList[0].MyDictionary = old;
}
I tested this, and it displays changed value as "aaaa".

MVVM Binding to 2 controls not working

I'm trying to make a control that has a current value with an optional equation string.
I have 2 textboxes:
One (a) where you can enter an equation shortcut to a value to put into the other (b).
(b) contains the actual value.
(for example, in (a), if you enter 'pi', the second will then fill with "3.1415926535897931")
I'm using 2 textboxes so the user can refine their equation if they need to, and watch the value change as they modify it.
The data has 2 fields, one being the equation string and the other being the current value.
so I have (a).Text bound to the string, a new property on (a) that holds the value, and I bind (b).Text to the value also.
(a).Text is TwoWay
(a).Value is OneWayToSource (since changes to the text should only be pushed to b)
(b).Value is TwoWay
This all works fine if I have the data set in the constructor before any XAML binding, but does not work at all if I add the data after binding.
Here is a minimal amount of code that shows the problem.
The only comment is at the line that can make it work or not.
As a last resort I could turn it into a custom control and handle it in the code-behind, but I'd think this should work in the first place.
Any ideas why this isn't working?
Thanks!
Here is the XAML:
<Window x:Class="twoBindingsOnSameField.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:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:twoBindingsOnSameField"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Content="load data" Click="Button_Click" Width="80" IsEnabled="{Binding NeedsData}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="enter text:" Width="80"/>
<local:TextBoxCalc Text="{Binding Item.ItemString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBoxCalculatedValue="{Binding Item.ItemValue, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
Width="200"
IsEnabled="{Binding HasData}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="updated text:" Width="80"/>
<TextBox Text="{Binding Item.ItemValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="200"
IsEnabled="{Binding HasData}"
/>
</StackPanel>
</StackPanel>
</Window>
Here is the codebehind.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace twoBindingsOnSameField
{
public partial class MainWindow : Window
{
data data;
public MainWindow()
{
InitializeComponent();
data = new data();
/// ---- Does not work with the following line commented out, but does if it is uncommented ----
/// ---- use the button to set the data ----
//setdata();
DataContext = data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
setdata();
}
void setdata()
{
if (data.Item == null)
data.Item = new dataitem();
}
}
public class data : notifybase
{
dataitem item;
public data()
{
}
public dataitem Item
{
get
{
return item;
}
set
{
if (item != value)
{
item = value;
notifyPropertyChanged("Item");
notifyPropertyChanged("HasData");
notifyPropertyChanged("NeedsData");
}
}
}
public bool HasData
{
get
{
return Item != null;
}
}
public bool NeedsData
{
get
{
return Item == null;
}
}
}
public class dataitem : notifybase
{
string itemString;
string itemValue;
public dataitem()
{
itemString = "3";
itemValue = "4";
}
public virtual string ItemString
{
get
{
return this.itemString;
}
set
{
if (!object.Equals(this.itemString, value))
{
this.itemString = value;
notifyPropertyChanged("ItemString");
}
}
}
public virtual string ItemValue
{
get
{
return this.itemValue;
}
set
{
if (!object.Equals(this.itemValue, value))
{
this.itemValue = value;
notifyPropertyChanged("ItemValue");
}
}
}
}
public class TextBoxCalc : TextBox
{
public TextBoxCalc()
{
TextProperty.AddHandler(this, (o,e)=>TextBoxCalculatedValue="updated:" + Text);
}
#region TextBoxCalculatedValue
public static DependencyProperty TextBoxCalculatedValueProperty = DependencyProperty.Register("TextBoxCalculatedValue", typeof(string), typeof(TextBoxCalc), new PropertyMetadata(""));
public string TextBoxCalculatedValue
{
get
{
return (string)GetValue(TextBoxCalculatedValueProperty);
}
set
{
if (!object.Equals(TextBoxCalculatedValue, value))
SetValue(TextBoxCalculatedValueProperty, value);
}
}
#endregion
}
public class notifybase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
protected virtual void notifyPropertyChanged(string propertyName)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(e);
}
}
static class extensions
{
public static void AddHandler(this DependencyProperty prop, object component, EventHandler handler)
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(prop, component.GetType());
if (dpd != null)
dpd.AddValueChanged(component, handler);
}
}
}
The reason why it works when you uncomment //setdata(); is because it is initializing the object in what is effectively your viewmodel, therefore you can change its properties via binding. To clarify as a side note, data would be your view model, and dataitem is your model, however you're dataitem is using INPC, so it doesn't really make sense in this case to have a viewmodel necessarily.
Anyways, the issue is that TextBoxCalculatedValue is set to a OneWayToSource binding. When you run the code commented out, its going to try and bind to a null value. When it does, it tries to update a null value, which isn't possible. WPF handles what would normally be a null exception automatically. When you update the dataItem by clicking the button, it doesn't update the object TextBoxCalc is bound to, so instead, it will continue trying to bind & update the null object. Change it to a TwoWay binding and you'll see a difference. Changing to TwoWay is probably your best option.
Good practice is to use constructor injection to practice dependency injection. With that being said, passing a dataItem to data would be the best route, and at the very least, initializing dataItem in data's constructor would be an ideal approach. So,
public data(dataItem item)
{
Item = item;
}
or
public data()
{
Item = new dataitem();
}

How to Databind to a CLR object with Source different to path

I am trying to update a textblock on the view by databinding to a property in the viewmodel (the datacontext for the view).
In the code below; when SelectedItem changes, I want the textblock text to update with the value of the Name property on SelectedItem.
In an attempt to achieve this I have set the binding source to the property that is changing and the binding path to the data I want to update the textblock with.
I.e. I am expecting that the binding engine will see a change on the binding Source (SelectedItem) and pull the data from the binding Path (SelectedItem.Name).
http://msdn.microsoft.com/en-us/library/ms746695.aspx
Setting the SelectedItem raises INPC but the text does not update.
public class ViewModel
{
public IConfiguration Configuration { get; set;}
}
public class Configuration : IConfiguration, INotifyPropertyChanged
{
public Item SelectedItem
{
get { return _item;}
set
{
_item = value;
ItemName = _item.Name;
RaisePropertyChangedEvent("SelectedItem");
}
}
public string ItemName
{
get { return _itemName;}
set
{
_itemName= value;
RaisePropertyChangedEvent("ItemName");
}
}
}
public class Item
{
public string Name { get; set;}
}
I know that changes on Configuration are seen because this works:
<TextBlock Text="{Binding Configuration.ItemName}"/>
But this does not:
<TextBlock Text="{Binding Path=Name, Source=Configuration.SelectedItem}"/>
And nor does this:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name, Source=Configuration.SelectedItem}"/>
I'm assuming that this should be straightforward - what have I missed?
I've never actually seen anyone use Binding.Source before, so I don't know much about it. But my guess is that it's not dynamic. When you create your binding, it's grabbing a reference to the object specified in your Source, and then that's it: it uses that same reference for the lifetime of the binding.
Why make this complicated? Just use Path. That's the normal way of doing binding, and it's dynamic all the way -- what you're doing is exactly what Path is intended for.
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}"/>
This is probably working, you just can not see it. The Binding engine has not been notified that the Name property of the Item object has changed.
Try implementing the INotifyPropertyChanged interface on the Item class as well (raising the PropertyChanged event as necessary)
This will work for your third binding situation, and also for a similar definition as below
<TextBlock DataContext="{Binding Path=Configuration.SelectedItem}" Text="{Binding Path=Name}"/>
But for a simpler fix, this should work:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}" />
Edit:
public class Configuration : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private Item _SelectedItem = null;
public Item SelectedItem
{
get
{
return _SelectedItem;
}
set
{
_SelectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
public class Item
{
public string Name { get; set; }
}
Then in a Command Execute somewhere I have this:
Configuration.SelectedItem = new Item() { Name = "test" };
Which updates the TextBlock in the View fine:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}" />

How to bind a Property with a textblock From a Class

public class myClass : INotifyPropertyChanged
{
public string myName(string myNameIs)
{
Name = myNameIs;
return myNameIs;
}
public string My = "Hasan";
public string Name {
get { return My; }
set
{
My = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged( this, new PropertyChangedEventArgs(
propertyName));
}
}
}
.
XAML:
<TextBlock Height="42" Margin="107,245,0,0" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top" HorizontalAlignment="Left" Width="159" DataContext="{Binding Source={StaticResource myClassDataSource}}"/>
This is working. But when i update property then it isn`t work?
Your code is rather confusing, you seem to be all over the place with it. I know this isn't the question you asked, but i thought i would point this out anyway:
your member variable is declared as public (public string My = "Hasan";)
your member variable has a totally different name to its property (My and Name)
you have a setter for the public property, and also a setting function (myName(string myNameIs))
you are returning the same value from the setting function as what you passed in
Here is an example of how you could rewrite it:
public class MyClass : INotifyPropertyChanged
{
//normal default constructor
public MyClass()
{
_name = "Hasan";
}
//extra constructor for when you need to set the name to something other than the default
//although this is really only useful if you have no setter on the Name property
public MyClass(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Raise the PropertyChanged event
this.PropertyChanged(this, new PropertyChangedEventArgs(
propertyName));
}
}
private string _name;
}
You just need to set the TextBlock (or it's parent's) DataContext property to an instance of this class.
Next bind the Text property to the backing property like this
<TextBlock Text="{Binding Name}"/>
Try going through a few tutorials online (or a book) instead of trying to forge your way through. It's easy once you get how DataBinding works.
Update: Once I formatted your question correctly, I could see the XAML you are using...
The mistake here is that you're trying to use the ElementName property (which is used to bind one UI element with another by name). This isn't what you're trying to achieve.

Resources