Updating a control after INotifyPropertyChanged - c#

I've checked the existing answers on Stack and still can't get this correct:
In my View:
<TextBlock Margin="8,0,0,0"
FontSize="48"
Text="{Binding YearsToSave}"
d:LayoutOverrides="Width">
...
<SurfaceControls:SurfaceSlider x:Name="slider" Grid.Row="8"
Grid.Column="2"
VerticalAlignment="Bottom"
Maximum="{Binding YearsToSaveMaxValue}"
Minimum="{Binding YearsToSaveMinValue}"
Value="{Binding YearsToSave}"
d:LayoutOverrides="Width" />
In my view model:
class YearsToSaveViewModel : INotifyPropertyChanged
{
private int yearsToSave;
public event PropertyChangedEventHandler PropertyChanged;
public YearsToSaveViewModel()
{
Questions = new SavingsCalculatorQuestions();
YearsToSave = 5; //Binds correctly
YearsToSaveMinValue = 0;
YearsToSaveMaxValue = 30;
}
public SavingsCalculatorQuestions Questions { get; set; }
public int YearsToSaveMinValue { get; private set; }
public int YearsToSaveMaxValue { get; private set; }
public int YearsToSave
{
get { return yearsToSave; }
set
{
yearsToSave = value;
OnPropertyChanged("YearsToSave");
}
}
public void Reset()
{
YearsToSave = 0;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
switch (name)
{
case "YearsToSave":
Questions.NumberOfYears = YearsToSave;
break;
}
}
}
}
The property changed event fires correctly and gets the value, updates the Questions.NumberOfYears correctly but the change never propagates back to the view.

Your OnPropertyChanged method is not raising the PropertyChanged event...
Update your method like so:
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
switch (name)
{
case "YearsToSave":
Questions.NumberOfYears = YearsToSave;
handler(this, new PropertyChangedEventArgs(name));
break;
}
}
}

Another option is using the NotifyPropertyWeaver Project!
I like it,because it automatically does the Event call for you!
(a bit of black magic but convenient)
See
http://crosscuttingconcerns.com/NotifyPropertyWeaver

Related

OnPropertyChanged() not working in MVVM

I searched through all other question with the same problem. But I can't find any solution from them.
OnPropertyChanged is firing but the Control is not updating. I'm using the Mahapps.Metro ProgressRing Control.
View Code
<controls:MetroWindow.Resources>
<userObj:LoginViewLogic x:Key="UserData"/>
<userObj:LoginViewLogic x:Key="LoginViewLogic"/>
</controls:MetroWindow.Resources>
<Grid>
<Canvas>
<controls:ProgressRing Name="ProgressRing" Canvas.Left="133" Canvas.Top="154" Height="50" Width="35" IsActive="{Binding Source={StaticResource UserData},Path=UserData.IsProgressRingActive}"/>
</Canvas>
</Grid>
ViewModel Code
public class LoginViewLogic {
public LoginViewLogic() {
_userData = new User(AppSettings.ReadCredentials(),(bool)loadedSettings);
}
private User _userData;
public User UserData
{
get { return _userData; }
set { _userData = value; }
}
public async void Login() {
_userData.IsProgressRingActive = true;
var loginResult = await Stuff.Login(_userData);
if (!loginResult) {
MessageBox.Show("You have entered an invalid username or password",
"Information", MessageBoxButton.OK, MessageBoxImage.Error);
_userData.IsProgressRingActive = false;
}
}
Model Code
public class User : INotifyPropertyChanged {
private bool _isProgressRingActive;
public bool IsProgressRingActive {
get { return _isProgressRingActive; }
set {
_isProgressRingActive = value;
OnPropertyChanged("IsProgressRingActive");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Have you tried this:
<controls:MetroWindow.Resources>
<userObj:LoginViewLogic x:Key="LoginViewLogic"/>
</controls:MetroWindow.Resources>
<Grid>
<Canvas>
<controls:ProgressRing Name="ProgressRing" Canvas.Left="133" Canvas.Top="154" Height="50" Width="35" IsActive="{Binding Source={StaticResource LoginViewLogic},Path=UserData.IsProgressRingActive}"/>
</Canvas>
</Grid>
So you are only creating and binding to one instance of LoginViewLogic class?
Your viewmodel should also raise a PropertyChanged event.
Try with below code.
public class LoginViewLogic : INotifyPropertyChanged
{
public LoginViewLogic()
{
_userData = new User(AppSettings.ReadCredentials(),(bool)loadedSettings);
}
private User _userData;
public User UserData
{
get { return _userData; }
set { _userData = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Create a binding between properties

I have a Listbox where it's items are objects. In these objects I store two colors.
I want to bind these colors with an other object's property, but how can I achieve this?
The listbox looks like this:
Listbox1.Items.Add(new ColorAndMoreClass(Color.Red, Color.Blue));
Far away, in an other class there is a property which I'd like to bind to.
How can I do that?
Your rootclass could look like this.
In the class you have a object representing a different Class.
public class ColorAndMoreClass: INotifyPropertyChanged
{
private Color _c;
private Color _c2;
private OtherClass _example;
public ColorAndMoreClass(Color c, Color c2)
{
_c= c;
_c2 = c2;
}
public OtherClass example
{
get { return _example }
set
{
_example = value;
OnPropertyChanged("example");
}
}
public Color c
{
get { return _c; }
set
{
_c= value;
OnPropertyChanged("c");
}
}
public Color c2
{
get { return _c2; }
set
{
_c2 = value;
OnPropertyChanged("c2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
Your other class could look like this. I just took a simple string.
public class OtherClass : INotifyPropertyChanged
{
private String _someOtherProperty;
public OtherClass(){}
public String someOtherProperty
{
get { return _someOtherProperty; }
set
{
_someOtherProperty= value;
OnPropertyChanged("someOtherProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
In your Code behind make a property the Listbox can bind to
public List<ColorAndMoreClass>> ListOfColorAndMore{ get; set; }
public Window1()
{
ListOfColorAndMore = GetDataThatFillsUpTheProperty();
InitializeComponent();
DataContext = this;
}
Your XAML could then look like this. The Datatemplate is used to tell XAML how to display your object.
<Grid>
<ListBox ItemsSource={Binding ListOfColorAndMore}>
<DataTemplate x:Key="myTaskTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=c.R}" />
<TextBlock Text="{Binding Path=c2.R}"/>
<TextBlock Text="{Binding Path=example.someOtherProperty}"/>
</StackPanel>
</DataTemplate>
</ListBox>
</Grid>
I hope it is this that you mean. But your question is not that clear.

Bindings on a ContentControl do not update the data

I have the code:
<TextBox Width="200" Text="{Binding Value}"></TextBox>
Which works. However the "Value" can be different types. So if I have an bool I want to show a checkbox. I rewrote it as as follow, which kinda works:
<ContentControl Content="{Binding Value}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=.}"></CheckBox>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Double}">
<TextBox Width="200" Text="{Binding Path=.}"></TextBox>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
But now the property isn't updated like before. I have tried setting Mode=Twoway, but it still do not work.
Edit
It worked perfectly fine when I only had the textbox, editing the text of the textbox updated the model. However when I tried doing this with the second code (ContentControl) it just doesn't work.
Code
I'm using Mvvm-light togheter with bindings. The "Value" is bound to an instance of Property
[JsonObject]
public class Property<T> : INotifyPropertyChanged
{
[JsonProperty]
public String name;
public Property(String name, T value)
{
this._value = value;
this.name = name;
}
[JsonIgnore]
public T Value {
get { return _value; }
set {
_value = value;
hot = true;
NotifyPropertyChanged("Value");
}
}
[JsonProperty(PropertyName = "value")]
private T _value;
[JsonIgnore]
public String Name { get { return name; } set { name = value; } }
[JsonProperty]
public bool hot = false;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You should implement INotifyPropertyChanged interface in order to track property changes. I'm sure everything works fine then.
This works for me:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private object value;
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
DataContext = this;
}
public object Value
{
get { return value; }
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Value = true;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

WPF two way data bind to custom data type in observable collection

I'm trying to data bind to a custom data type property FormulaField in WPF. I don't understand if there's something I've missed or if what I'm trying to do can't be done?
I've followed the convention of how I've bound to a primitive and found that hasn't worked, there's not update on the FormulaField property. I've also noticed that the custom data type set method is never hit. I'm using MVVM.
A model:
public class OBQModel : NotificationObject
{
private FormulaField _tovLitres;
public FormulaField TOVLitres
{
get
{
if (_tovLitres.UsesFormula)
{
_tovLitres.Value = ConversionHelper.USBarrelsToLitres(_tovBarrels);
}
return _tovLitres;
}
set
{
_tovLitres = value;
RaisePropertyChanged("TOVLitres");
}
}
}
The NotificationObject implements INotifyPropertyChanged:
public abstract class NotificationObject : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged<T>(Expression<Func<T>> action)
{
var propertyName = GetPropertyName(action);
RaisePropertyChanged(propertyName);
}
private static string GetPropertyName<T>(Expression<Func<T>> action)
{
var expression = (MemberExpression)action.Body;
var propertyName = expression.Member.Name;
return propertyName;
}
protected internal void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
FormulaField looks like this:
public class FormulaField
{
public bool UsesFormula { get; set; }
public double Value { get; set; }
}
EDIT
Implementing INotifyPropertyChanged in FormulaField goes stack overflow...
public class FormulaField : INotifyPropertyChanged
{
public bool UsesFormula { get; set; }
public double Value
{
get
{
return Value;
}
set
{
Value = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
The Models sit inside an ObservableCollection in a ViewModel.
An illustration of the View:
<StackPanel>
<DataGrid ItemsSource="{Binding OBQModelCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="new TOV (L)" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox BorderThickness="0"
Text="{Binding TOVLitres.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
Based upon what you wrote, you are raising INPC on "LiquidGOVLitres", which doesn't seem to appear in your code listing, but you are binding to "TOVLitres".
Fixing this inconsistency will help, but you will also need to implement INPC on the FormulaField if you want changes to its members to be part of your UI.
ETA: After the clarifying edit to your code listing, the remaining task is to implement INPC on your FormulaField class and raise the event accordingly.
Also, if you are using 4.5 you can investigate the new Member Info class which helps avoid the use of magic strings in INPC.
Finally, for semantic clarity, it wouldn't hurt to rename "Value" to "FormulaValue"...
To avoid recursion, try this model...
private double _value;
public double Value
{
[DebuggerStepThrough]
get { return _value; }
[DebuggerStepThrough]
set
{
if (Math.Abs(value - _value) > Double.Epsilon)
{
_value = value;
OnPropertyChanged("Value");
}
}
}

INotifyPropertyChanged Not Working

I am using INotifyPropertyChanged but it will give me null when I shaw the PropertyChanged so what i can do..
my code is like this..
public class Entities : INotifyPropertyChanged
{
public Entities(int iCount)
{
_iCounter = iCount;
}
private int _iCounter;
public int iCounter
{
get
{
return _iCounter;
}
set
{
value = _iCounter;
NotifyPropertyChanged("iCounter");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Thanks...
I tried putting your code in my program and it is working fine. I am getting the EventArg as the property:
class Program
{
static void Main(string[] args)
{
var ent = new Entities(10);
ent.PropertyChanged += new PropertyChangedEventHandler(ent_PropertyChanged);
ent.iCounter = 100;
}
static void ent_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
}
public class Entities : INotifyPropertyChanged
{
public Entities(int iCount)
{
_iCounter = iCount;
}
private int _iCounter;
public int iCounter
{
get
{
return _iCounter;
}
set
{
_iCounter = value;
NotifyPropertyChanged("iCounter");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
What is the exact erro you are getting?
This is i think a bug in INotifyPropertyChanged .
There can be 2 workaround
1st Workaround
1- Assign iCounter property to a UI control like Lable.
2- Now change the value of the property this time , PropertyChanged event will have a reference of your method and will not be null;
2nd workaround
Assign PropertyChanged delegate in the Entities class constructor
i am giving the demo code in WPF
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.Resources>
<ToolTip x:Key="#tooltip">
<TextBlock Text="{Binding CompanyName}"/>
</ToolTip>
</Grid.Resources>
<TextBlock Text="{Binding Name}" Background="LightCoral" />
<Rectangle Width="200" Height="200" Fill="LightBlue" VerticalAlignment="Center" HorizontalAlignment="Center" ToolTip="{DynamicResource #tooltip}" Grid.Row="1"/>
<Button Click="Button_Click" Grid.Row="2" Margin="20">Click Me</Button>
</Grid>
see here CompanyName is assigned to a tool tip.
// this is Window1.Cs file
public Window1()
{
DataContext = DemoCustomer.CreateNewCustomer();
InitializeComponent();
}
// Now DemoCustomer Class
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;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
// 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;
OnPropertyChanged("CompanyName");
}
}
}
public string PhoneNumber
{
get { return this.phoneNumberValue; }
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
OnPropertyChanged("PhoneNumber");
}
}
}
}
and finally changing the value
private void Button_Click(object sender, RoutedEventArgs e)
{
DemoCustomer dc = this.DataContext as DemoCustomer;
dc.CompanyName = "Temp";
}

Categories

Resources