MVVM: Trigger method on property changed with Fody.PropertyChanged - c#

I'd like to run a method on property changed. I'd like my code to compile to something like this:
public string Property
{
get { return _property; }
set
{
_property= value;
IWantToCallFromHere(); // I want to inject this call
NotifyPropertyChanged();
}
}

This is described in the Wiki in the page named On_PropertyName_Changed.
Essentially you add a method with the naming convention private void OnYourPropertyNameChanged()
A complete example of what you would like to achieve is as follows:
public string Property
{
get; set;
}
private void OnPropertyChanged()
{
IWantToCallFromHere();
}
which gets translated into
private string _property;
public string Property
{
get => _property;
set
{
if(_property != value)
{
_property = value;
OnPropertyChanged();
NotifyPropertyChanged();
}
}
}
private void OnPropertyChanged()
{
IWantToCallFromHere();
}

Related

View not updating when property is changed from another ViewModel [duplicate]

I found a notify property changed example for static properties in static class. but it doesn't update any changes in TextBlock. Here are the codes.
First binding is working with the "test" string in constructor but StaticPropertyChanged is always null.
public static class InteractionData
{
public static List<string> SelectedDirectories { get; set; }
private static string errorMessage { get; set; }
public static string ErrorMessgae
{
get { return errorMessage; }
set
{
errorMessage = value;
NotifyStaticPropertyChanged("errorMessage");
}
}
static InteractionData()
{
SelectedDirectories = new List<string>();
errorMessage = "test";
}
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
private static void NotifyStaticPropertyChanged(string propertyName)
{
if (StaticPropertyChanged != null)
StaticPropertyChanged(null, new PropertyChangedEventArgs(propertyName));
}
}
In View ...
xmlns:error ="clr-namespace:CopyBackup.Providers"
<TextBlock Text="{Binding Source={x:Static error:InteractionData.ErrorMessgae} ,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Wherever I change the property, TextBlock doesn't update.
Appreciate
Similar to an implementation of INotifyPropertyChanged, static property change notification only works if you use the correct property name when firing the StaticPropertyChanged event.
Use the property name, not the name of the backing field:
public static string ErrorMessgae
{
get { return errorMessage; }
set
{
errorMessage = value;
NotifyStaticPropertyChanged("ErrorMessgae"); // not "errorMessage"
}
}
You should certainly also fix the misspelled property name:
public static string ErrorMessage
{
get { return errorMessage; }
set
{
errorMessage = value;
NotifyStaticPropertyChanged("ErrorMessage");
}
}
The binding should look like this:
Text="{Binding Path=(error:InteractionData.ErrorMessage)}"
See this blog post for details about static property change notification.
You may also avoid to write property names at all by using the CallerMemberNameAttribute:
using System.Runtime.CompilerServices;
...
public static event PropertyChangedEventHandler StaticPropertyChanged;
private static void NotifyStaticPropertyChanged(
[CallerMemberName] string propertyName = null)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
You could now call the method without explicitly specifying the property name:
NotifyStaticPropertyChanged();

C# WPF Saving RadioSelect bool value to XML File (it's always false)

This is the XAML of the radio. Nothing else is editing this. Once this is set it is not changing. But somehow no matter what it is setting the XML to "false".
Here is how I save the XML file (works just fine).
There are 3 radio buttons, as you can see, that I am trying to get set to false or true but they all just get saved as false.
<RadioButton x:Name="sx80" Content="Cisco SX80" HorizontalAlignment="Left" Margin="701,244,0,0" VerticalAlignment="Top" GroupName="codecType" TabIndex="17" FontWeight="Normal" Height="25" Width="95" Padding="0,2"/>
class SaveXml
{
public static void savedata(object obj, string filename)
{
XmlSerializer sr = new XmlSerializer(obj.GetType());
TextWriter writer = new StreamWriter(filename);
sr.Serialize(writer, obj);
writer.Close();
}
}
Here is the main class that tells it what information we are saving to the XML file.
public class information
{
private string city;
private string chairCount;
private string stateSelect;
private string HostNameIPTyped;
private string VTCmac;
private string vtcUser;
private string vtcPass;
private string VTCserial;
private string AssetTag;
private string SIPURI;
private string SystemName;
private string firstName;
private string lastName;
private string contactPhone;
private string provisionerName;
private string provisionerInitials;
private string provisionDate;
private bool sx80;
private bool codecPlus;
private bool codecPro;
public string postcity
{
get { return city; }
set { city = value; }
}
public string postchairCount
{
get { return chairCount; }
set { chairCount = value; }
}
public string poststateSelect
{
get { return stateSelect; }
set { stateSelect = value; }
}
public string postHostNameIPTyped
{
get { return HostNameIPTyped; }
set { HostNameIPTyped = value; }
}
public string postVTCmac
{
get { return VTCmac; }
set { VTCmac = value; }
}
public string postvtcUser
{
get { return vtcUser; }
set { vtcUser = value; }
}
public string postvtcPass
{
get { return vtcPass; }
set { vtcPass = value; }
}
{ e164 = value; }
}
public string postVTCserial
{
get { return VTCserial; }
set { VTCserial = value; }
}
public string postAssetTag
{
get { return AssetTag; }
set { AssetTag = value; }
}
public string postSIPURI
{
get { return SIPURI; }
set { SIPURI = value; }
}
public string postSystemName
{
get { return SystemName; }
set { SystemName = value; }
}
public string postfirstName
{
get { return firstName; }
set { firstName = value; }
}
public string postlastName
{
get { return lastName; }
set { lastName = value; }
}
public string postcontactPhone
{
get { return contactPhone; }
set { contactPhone = value; }
}
public string postprovisionerName
{
get { return provisionerName; }
set { provisionerName = value; }
}
public string postprovisionerInitials
{
get { return provisionerInitials; }
set { provisionerInitials = value; }
}
public string postprovisionDate
{
get { return provisionDate; }
set { provisionDate = value; }
}
public bool postsx80
{
get { return sx80; }
set { sx80 = value; }
}
public bool postcodecPlus
{
get { return codecPlus; }
set { codecPlus = value; }
}
public bool postcodecPro
{
get { return codecPro; }
set { codecPro = value; }
}
}
The code you posted doesn't show any data binding on the RadioButton or how you've set your DataContext. But you said in the comments that the strings are working so I assume you've set the DataContext somewhere. If you can update your question to show how your Window/View is bound to the information object it will be easier to give you a more accurate solution. You also said the following in one of your comments:
Yes, it is actually being saved as false. If it didn't find a value it would just show nothing. :-) <postsx80>false</postsx80>
The default value for a bool is actually false, so even if no value is retrieved from your RadioButton, your XML file will still show false.
Your RadioButton's would normally be bound like this, depending on how your DataContext is set. Notice the Binding in the IsChecked property. The Mode=TwoWay means that the UI can set the value of the property and not just read it:
<RadioButton x:Name="sx80" Content="Cisco SX80" IsChecked="{Binding Info.postsx80, Mode=TwoWay}" />
In the code behind of this Window I have created a public property called Info which contains an instance of your information class. The RadioButton above is bound the the postsx80 property of this information instance so you would need to pass this instance to your savedata method like below.
public partial class MainWindow : Window
{
public information Info { get; set; } = new information(); // The UI is bound to this instance
public MainWindow()
{
InitializeComponent();
this.DataContext = this; // I've set the Window's DataContext to itself
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SaveXml.savedata(Info, "somefile.xml");
}
}
You should also implement INotifyPropertyChanged which will notify the UI when a property's value has changed. For example your information class could look like this:
// You will need to add the following namespaces
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace YourAppsNamespace
{
public class information : INotifyPropertyChanged // Implement the INotifyPropertyChanged interface
{
private bool sx80;
public bool postsx80
{
get { return sx80; }
set {
sx80 = value;
OnPropertyChanged(); // Notify the UI that this property's value has changed
}
}
// This code raises the event to notify the UI which property has changed
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
You would need to add OnPropertyChanged() to the setters of all of your properties.
You also mentioned in the comments that you don't know how to use auto properties. An auto property is basically a shorter way to write a property when there are no additional actions which need to be performed when getting or setting a value. For example, this:
private bool someBool;
public bool SomeBool
{
get { return someBool; }
set { someBool = value; }
}
Would just become:
public bool SomeBool { get; set; }
There is no need to create the private variable or define the body of the getter and setter. This is handled automatically for you. This is only suitable if you don't need to perform any additional actions in the getter or setter. So in my example above where we need to call OnPropertyNotifyChanged() in the setter, you wouldn't be able to use an auto property.
An additional tip is that you can simply type prop in Visual Studio and press Tab twice to insert an auto property without having to type it out yourself. You then simply change the data type, press Tab again to move to the name and change that. The same can be done for a full property like the ones you wrote by typing propfull.

C# WPF MVVM | Change Binded to StatusBar Vars

So, I have a statusbar as UserControl.
Model:
public class StatusBarModel : BindableBase
{
private string _status;
public string Status
{
get { return _status; }
set
{
_status = value;
RaisePropertyChanged("Status");
}
}
private int _p_value;
public int P_Value
{
get { return _p_value; }
set
{
_p_value = value;
RaisePropertyChanged("P_Value");
}
}
}
ViewModel:
public class StatusBarVM : BindableBase
{
readonly source.elements.StatusBar.StatusBarModel _model = new source.elements.StatusBar.StatusBarModel();
public StatusBarVM()
{
_model.PropertyChanged += (s, e) => { RaisePropertyChanged(e.PropertyName); };
}
public string Status
{
get { return _model.Status; }
set { _model.Status = value; }
}
public int P_Value
{
get { return _model.P_Value; }
set { _model.P_Value = value; }
}
}
And for example I wanna change Status variable from others ViewModels.
How I can do it? I have seen examples with only buttons and etc.
There are multiple ways to achieve your requirement. as #bitclicker says, you can use static class that hold its value. But I think It is too much that makes it static class, because that variable value may be used only two viewmodel.
I suggest you communicate between two view model. you will find Prism's event aggregator or you could implement your own event publish-subscriber model. making your own event pub-sub model would help you to make a first step into the design pattern.
You could create a static class to hold that value.
public static class Globals()
{
public static StatusBarModel GlobalStatus { get; set; }
}
Then whenever you want to alter it you just do
Globals.GlobalStatus.Status = "something";
Globals.GlobalStatus.P_Value = 14;
does that accomplish what you need?

Dual binding on Checkbox

I have a WPF application using MVVM. I have the IsChecked value bound to a boolean on my model instance on my ViewModel. I also need to bind a method on the ViewModel to the Checked and Unchecked events. (This is so I can track unsaved changes and change the background to give my users visual indication of the need to save. I tried:
<CheckBox
Content="Enable"
Margin="5"
IsChecked="{Binding Enabled}"
Checked="{Binding ScheduleChanged}"
Unchecked="{Binding ScheduleChanged}"
/>
But I get a 'Provide value on 'System.Windows.Data.Binding' threw an exception.' error. Advice?
Here is the Model I am working with:
public class Schedule : IEquatable<Schedule>
{
private DateTime _scheduledStart;
private DateTime _scheduledEnd;
private bool _enabled;
private string _url;
public DateTime ScheduledStart
{
get { return _scheduledStart; }
set
{
_scheduledStart = value;
}
}
public DateTime ScheduledEnd
{
get { return _scheduledEnd; }
set
{
if(value < ScheduledStart)
{
throw new ArgumentException("Scheduled End cannot be earlier than Scheduled Start.");
}
else
{
_scheduledEnd = value;
}
}
}
public bool Enabled
{
get { return _enabled; }
set { _enabled = value; }
}
public string Url
{
get { return _url; }
set { _url = value; }
}
public bool Equals(Schedule other)
{
if(this.ScheduledStart == other.ScheduledStart && this.ScheduledEnd == other.ScheduledEnd
&& this.Enabled == other.Enabled && this.Url == other.Url)
{
return true;
}
else
{
return false;
}
}
}
My viewModel contains a property that has an ObservableCollection. An ItemsControl binds to the collection and generates a list. So my ViewModel sort of knows about my Model instance, but wouldn't know which one, I don't think.
Checked and Unchecked are events, so you can not bind to them like you can IsChecked, which is a property. On a higher level it is also probably wise for your view model not to know about a checkbox on the view.
I would create an event on the view model that fires when Enabled is changed, and you can subscribe to that and handle it any way you like.
private bool _enabled;
public bool Enabled
{
get
{
return _enabled;
}
set
{
if (_enabled != value)
{
_enabled = value;
RaisePropertyChanged("Enabled");
if (EnabledChanged != null)
{
EnabledChanged(this, EventArgs.Empty);
}
}
}
}
public event EventHandler EnabledChanged;
// constructor
public ViewModel()
{
this.EnabledChanged += This_EnabledChanged;
}
private This_EnabledChanged(object sender, EventArgs e)
{
// do stuff here
}
You should be able to just handle this in the setter for Enabled...
public class MyViewModel : ViewModelBase
{
private bool _isDirty;
private bool _enabled;
public MyViewModel()
{
SaveCommand = new RelayCommand(Save, CanSave);
}
public ICommand SaveCommand { get; }
private void Save()
{
//TODO: Add your saving logic
}
private bool CanSave()
{
return IsDirty;
}
public bool IsDirty
{
get { return _isDirty; }
private set
{
if (_isDirty != value)
{
RaisePropertyChanged();
}
}
}
public bool Enabled
{
get { return _enabled; }
set
{
if (_enabled != value)
{
_enabled = value;
IsDirty = true;
}
//Whatever code you need to raise the INotifyPropertyChanged.PropertyChanged event
RaisePropertyChanged();
}
}
}
You're getting a binding error because you can't bind a control event directly to a method call.
Edit: Added a more complete example.
The example uses the MVVM Lite framework, but the approach should work with any MVVM implementation.

INotifyPropertyChanged with singelton class

i'm trying to implement INotifyPropertyChanged within singelton class.
Here is my code:
public class plc : INotifyPropertyChanged
{
private static plc instance;
public plc()
{
}
public static plc Instance
{
get
{
if (instance == null)
{
instance = new plc();
}
return instance;
}
set
{
instance = value;
}
}
private static string _plcIp{get; set;}
public string plcIp
{
get
{
return _plcIp;
OnPropertyChanged();
}
set
{
_plcIp = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm getting error unreachable code deleted and of course NotifyPropertyChange isn't working
It's because you are calling OnPropertyChanged(); after you return _plcIp;.
It should be called after you set the value. i.e.:
public string plcIp
{
get
{
return _plcIp;
}
set
{
if (value != _plcIp)
{
_plcIp = value;
OnPropertyChanged();
}
}
}
You should also check that the value is actually changing in the setter before raising the event.
There are several issues in your code:
if you are implementing singleton, then constructor of class should be private
use fields instead of private properties
properties should not be static (you are using singleton)
verify if property value really changed before raising OnPropertyChanged event
raise event before returning property value
use PascalNames for class name and properties names
raise event from setter instead of getter
Code:
public class Plc : INotifyPropertyChanged {
private static Plc _instance;
private Plc() { } // constructor should be private
public static Plc Instance
{
get
{
if (_instance == null)
_instance = new Plc();
return _instance;
} // you don't need setter
}
private string _plcIp; // instance field instead of static property
public string PlcIp
{
get { return _plcIp; }
set
{
if (_plcIp == value)
return; // check if value changed
_plcIp = value; // change value
OnPropertyChanged(); // raise event
}
}
// ...
}
This contains the error :
public string plcIp
{
get
{
return _plcIp;
OnPropertyChanged(); //This row..
}
set { _plcIp = value; }
}
It's in the Set method you want the update in the UI, not when you get the value.
Something like this :
public string plcIp
{
get { return _plcIp; }
set { _plcIp = value; OnPropertyChanged(); }
}

Categories