I've recently started experimenting with DataBinding and implementing DependencyProperties for my custom classes. It all works fine and the possibilities are exciting, however, I came across a problem that may be only solvable by slightly modifying the overall class design. And I want to make sure this is the only option and I'm not missing anything.
So, my class stores information about video files the user imports into the application. Among other properties, it contains:
public class VideoFile {
public string FilePath { get; protected set; }
public uint ID { get; protected set; ]
public string Extension { get { return Path.GetExtension(FilePath); } }
public string FileName { get { return Path.GetFilename(FilePath); } }
}
So, I've successfully replaced FilePath with an DependencyProperty. However, in the UI, I mostly want to display just the filename, which uses some logic to provide its value. As far as I know, here are my options:
I could simply create DependencyProperties for FileName and Extension, and set their value in the constructor, but that's redundant; I already have that information in the FilePath, so I want to avoid this option.
Create ValueConverters, one for displaying Filename and one for displaying Extension, and use them in my bindings.
I've only met ValueConverters briefly, so I'm not sure about it. Can I use them for this purpose? Or, have I just encountered one of the main reasons they exist? :)
And last but not least, can anyone think of a situation similar to this, when a ValueConverter is not the right way to go? I want to avoid jumping straight into them, only to realize it will not work because "that one" property just can't be expressed in this way.
You don't need DependencyProperties for this. You only need a DependencyProperty when you're going to set into a property using a MarkupExtension, and I doubt you're doing that with a model class (because you won't be declaring this class in Xaml!).
A much more lightweight way would be to use INotifyPropertyChanged. Here's a .NET 3.5-style implementation:
public class VideoFile : INotifyPropertyChanged
{
private string _filePath;
public string FilePath
{
get
{
return _filePath;
}
protected set
{
_filePath = value;
OnPropertyChanged("FilePath");
OnPropertyChanged("Extension");
OnPropertyChanged("FileName");
}
}
public uint ID { get; protected set; }
public string Extension { get { return Path.GetExtension(FilePath); } }
public string FileName { get { return Path.GetFileName(FilePath); } }
protected void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
(In .NET 4.5 this can be simplified somewhat thanks to the new [CallerMemberName] attribute.)
The only downside is that you require backing fields for your properties. However, there's a VS extension called NotifyPropertyWeaver that can automate part of this work and remove the need for explicit backing properties, too.
Don't duplicate data.
Prefer Binding and an IValueConverter, because that way, whenever FilePath changes, Extension and FileName will be updated in the UI as well.
You could also, of course, raise PropertyChanged for them in FilePath's setter but that's bad practice since FilePath should not have to care about who/what uses it.
The class then looks like:
public class VideoFile : INotifyPropertyChanged {
string m_FilePath;
public string FilePath
{
get { return m_FilePath; }
protected set
{
if(value != m_FilePath)
{
m_FilePath = value;
RaisePropertyChanged(() => this.FilePath);
}
}
}
public uint ID { get; protected set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged<T>(Expression<Func<T>> _PropertyExpression)
{
RaisePropertyChanged(PropertySupport.ExtractPropertyName(_PropertyExpression));
}
protected void RaisePropertyChanged(String _Prop)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(_Prop));
}
}
#endregion
}
Please note that PropertySupport is part of Prism, but you can do without it by calling RaisePropertyChanged("FilePath"), it's just neat to have type safety because if you change the property's name you will have a compile-time error.
As far as I understand you would like to just display File Name on UI. Then you may consider updating FileName property whenever FilePath dependency property is changed (OnChangedFilePath method). You can also check if FilePath is OK in ValidateFilePath method. Please note that FileName must be also a dependency property or supporting IPropertyChanged, otherwise UI will not be updated when you change it. You do not need to use a converter for this purpose.
public string FilePath
{
get { return (string)GetValue(FilePathProperty); }
set { SetValue(FilePathProperty, value); }
}
private static object CoerceFilePath(DependencyObject d, object value)
{
return value;
}
private static bool ValidateFilePath(object Value)
{
return true;
}
private static void OnChangedFilePath(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
public static readonly DependencyProperty FilePathProperty =
DependencyProperty.Register("FilePath", typeof(string), typeof(ClassName),
new PropertyMetadata(#"C:\File.avi", OnChangedFilePath, CoerceFilePath),
new ValidateValueCallback(ClassName.ValidateFilePath));
Related
This question already has answers here:
Better way to trigger OnPropertyChanged
(6 answers)
Closed 6 years ago.
I want to simplify the property declarations in my classes. The problem is the getter and setter definition. I am doing exactly the same for hundreds of properties. All properties are created like this, where the method "LogPropertyChanged" also RaisePropertyChange.
public class PCS_MA_V1_ALARMSTAT : ViewModelBase
{
private Boolean _ActionAlarmHighHigh;
public Boolean ActionAlarmHighHigh
{
get
{
return _ActionAlarmHighHigh;
}
set
{
if(value!= _ActionAlarmHighHigh)
{
_ActionAlarmHighHigh = value;
LogpropertyChanged("ActionAlarmHighHigh", oldVal, newVal);
}
}
}
private Boolean _ActionAlarmLowLow;
public Boolean ActionAlarmLowLow
{
get
{
return _ActionAlarmLowLow;
}
set
{
if(value!= _ActionAlarmLowLow)
{
_ActionAlarmLowLow = value;
LogpropertyChanged("ActionAlarmLowLow", oldVal, newVal);
}
}
}
}
Now i think this syntax is much to complex, and a huge hasle to work with. Is there a way where i could create the class like this:
public class PCS_MA_V1_ALARMSTAT: ViewModelBase
{
public Boolean ActionAlarmHighHigh { get; set; }
public Boolean ActionAlarmLowLow { get; set; }
}
And then monitor the instance. If a property has changed i run LogPropertyChanged on that particular property. Is this possible?
You can't override the default behavior of get and set, unfortunately.
What you can do is write a ViewModelBase like this. It's a common idiom. My code below is mostly stolen from this answer, and Prism's BindableBase has a similar SetProperty<T> method, but I didn't go looking for their implementation. There can't be all that much to it.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace HollowEarth.MVVM
{
public class ViewModelBase : INotifyPropertyChanged
{
#region SetProperty
protected virtual bool SetProperty<T>(ref T backingField,
T newValue,
[CallerMemberName] String propName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, newValue))
{
#if DEBUG
System.Diagnostics.Trace.WriteLine(
$"No change in {propName} == {backingField}");
#endif
return false;
}
#if DEBUG
System.Diagnostics.Trace.WriteLine(
$"Changing {propName} from {backingField} to {newValue}");
#endif
backingField = newValue;
OnPropertyChanged(propName);
return true;
}
#endregion SetProperty
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(
[CallerMemberName] String propName = null)
{
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(propName));
}
#endregion INotifyPropertyChanged
}
}
And use it like so:
public class MyViewModel : ViewModelBase
{
#region Whenever Property
private DateTime? _whenever = default(DateTime?);
public DateTime? Whenever
{
get { return _whenever; }
set { SetProperty(ref _whenever, value); }
}
#endregion Whenever Property
}
I write Visual Studio snippets to generate properties. You said that you're generating all this stuff programmatically so snippets aren't an immediate requirement, but it's something to keep in mind.
Another possibility, if maintainability of generated code is a concern, to use partial classes. Put the generated code in separate files that are never edited by hand (much like Form1.Designer.cs/Form1.resx in winforms), and maintain the input files that are used to generate those files. Set up your generator as a build action for whatever your input files are.
For a meta-level witticism on the subject of maintainability, write the partial class generator in Perl.
Im sorry if it's a duplicate but I couldn't find the question similiar to this one.
From the stackoverflow other questions (this site is superb) I discovered how to bind XAML items to static class with event updates. It works that way:
XAML:
<Label x:Name="label_cc_CPUPOWER" Content="{Binding Path=(database:Database.CPU_CPUPOWER)}"...
Code:
public static class Database
{
#region Event firing
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
private static void NotifyStaticPropertyChanged(string propertyName)
{
if (StaticPropertyChanged != null)
StaticPropertyChanged(null, new PropertyChangedEventArgs(propertyName));
}
#endregion
//CPU
private static string _CPU_CPUCLOCK;
public static string CPU_CPUCLOCK
{
get { return _CPU_CPUCLOCK; }
set
{
if (value != _CPU_CPUCLOCK)
{
_CPU_CPUCLOCK = value;
NotifyStaticPropertyChanged("CPU_CPUCLOCK");
}
}
}
private static string _CPU_CPUPOWER;
public static string CPU_CPUPOWER
{
get { return _CPU_CPUPOWER; }
set
{
if (value != _CPU_CPUPOWER)
{
_CPU_CPUPOWER = value;
NotifyStaticPropertyChanged("CPU_CPUPOWER");
}
}
}
It works and Im really grateful to You that I don't have to figure this out all by myself BUT I have about 25-50 values to store like that. I Wonder if there's a way automate this instead of copy/paste with changing names in brackets?
I also thought about making an List for each object like CPU,RAM and just send EventChanged to the whole object but Im trying to minimize cpu usage and IMHO raising PropertyChanged to all objects while I just want to update two of five isn't good way to do that as whole object values would be refreshed.
Regards
Yes there are, check this out: PropertyChanged.Fody
Then all you have to do is to add :
using PropertyChanged;
...
[ImplementPropertyChanged]
public class MyClass
{
public object Object {get;set;}
// INotifyPropertyChanged implemented automatically # compile-time
}
There are some similar questions on SO, but they weren't quiet the same, so I'm posting this instead. I'm new to MVVM, so I'm trying to figure out how I can create a class that can hold properties that can be shared among views. So, if I set a property in one view, all the other views would get notified if its changed and would adjust their properties accordingly.
What I have now is rather very crude and is definitely not something I want to use. This is my common class that will hold all the properties:
public static class Common
{
private static string _title;
public static string Title
{
get { return _title; }
set
{
if (_title == value)
{
return;
}
_title = value;
OnPropertyChanged("Title");
}
}
public static void Load()
{
// set properties here
}
public static event PropertyChangedEventHandler PropertyChanged;
private static void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(typeof(SettingsWorker), new PropertyChangedEventArgs(name));
}
}
}
...and I have to subscribe to it from each ViewModel:
Common.PropertyChanged += Common_PropertyChanged;
private void Common_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Title = Common.Title;
}
But this is where the breakdown happens. I can get the property name from the PropertyChangedEventArgs, but I've no idea how to get the value. Therefore, I'm forced to update all the properties, and that can get nasty to maintain. The code is becoming a mess.
I'm basically trying to get properties that ViewModels can share. How can I accomplish this?
It looks like you just have some global data you want to show in multiple places. The most straightforward way to do this is to just make it like and normal ViewModel class and then make it available to each of your other ViewModels and expose it from them to bind to directly (rather than copying the property into each of them). You can do this using IOC, or make it available statically, more similar to how you have it now.
If you go the static direction, the key change you need to make is to use a singleton rather than a static class in order to allow property change notification to work. Bindings work with INPC on instances but not static classes. The Common class would look more like this:
public class Common : INotifyPropertyChanged
{
private static Common _instance = null;
protected Common()
{
}
public static Common GetInstance()
{
if (_instance == null)
_instance = new Common();
return _instance;
}
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title == value)
return;
_title = value;
OnPropertyChanged("Title");
}
}
public void Load()
{
}
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventArgs ea = new PropertyChangedEventArgs(propertyName);
if (PropertyChanged != null)
PropertyChanged(this, ea);
}
public event PropertyChangedEventHandler PropertyChanged;
}
There are a lot of different ways you can then use this. Here's one of the more direct ones:
public class SomeViewModel : ViewModelBase
{
public Common CommonData { get; private set; }
public SomeViewModel()
{
CommonData = Common.GetInstance();
}
}
Then in XAML you can bind to the properties from the common class and get change notification, even across the different VM usages.
<TextBox Text="{Binding Path=CommonData.Title}"/>
There's also the option of making the singleton accessible as a property and binding to it directly from XAML using x:Static but that's a little different direction that what you were asking.
So if you have other views that want to get notified when a property changes I would assume you have a separate viewmodel for each of those views. In that case what you would be asking for is "How can viewmodels talk to other viewmodels?" For a good learning experience, I would recommend looking into the observer pattern. If you don't like that style, then I would recommend you look into using a MVVM Framework like "SimpleMVVM, Catel, or many others" (Just need to look some up). Then once you have that framework in you project, I would create a baseviewmodel class that all your viewmodels will inherit. Once that is done you can take advantage of the frameworks messenger system. Basically it would look something like:
public YourViewModel()
{
RegisterToReceiveMessages(MessageTokens.SomeToken, OnChangeCallYourMethodToAddress); //The MessageTokens is something you generally create ur self.
}
#region Notifications
void OnChangeCallYourMethodToAddress(object sender, NotificationEventArgs<SlideShowLocale> e)
{
SomeProperty = e.Data;
}
Then to send a message to "YourViewModel" From another ViewModel:
public AnotherViewModel
{
SendMessage(MessageTokens.SomeToken, new NotificationEventArgs(WhateverYouWantToSend));
}
So basically, by declaring the token you want, it then finds the viewmodel that is registered with that token and listens for any messages to come in.
C# allows the creation of a property as below:
public string SomeRandomText
{
get; set;
}
The framework handles the creation of the backing variable for this property. How can I have such a property and still have change notification?
Is this allowed in a class that implements INotifyPropertyChanged?
public string SomeRandomTextBeingNotified
{
get;
set
{
NotifyPropertyChanged("SomeRandomTextBeingNotified");
}
}
You can't use automatic properties when trying to use this. You'll need to creating a backing store:
private string _someRandomText;
public string SomeRandomText {
get { return _someRandomText; }
set
{
_someRandomText = value;
NotifyPropertyChanged("SomeRandomText");
}
}
To make code look cleaner, you can use attributes for INotifyPropertyChanged.
Easy usage of INotifyPropertyChanged with Property-Attribute
Have a look at this Use of Attributes... INotifyPropertyChanged
Actually you can, but you basically need to change the bytecode post C# compiler.
This may sound like a lot of work, but this is one of the easier postprocessing steps that for example PostSharp includes.
http://www.sharpcrafters.com/solutions/notifypropertychanged
http://www.sharpcrafters.com/blog/post/Recording-Automate-INotifyPropertyChanged-with-Karol-Waledzik-from-Internetium.aspx
A lot more functionality is available ;)
Otherwise note that
enter code hereenter code here`NotifyPropertyChanged("SomeRandomTextBeingNotified");
is bad code. I do all that in one field update method:
set
{
OnUpateField (ref _someRandomText, value);
}
The update method does all - check for equality (you do NOT want to trigger when new value = old value), then trigger updates as needed. It gets the property name through the calling method third parameter that is automatically set by the compiler. Alternatives are using a LINQ statement ( ref someRandomText, value, this->SomeRandomText). I never would love a string there that does not get renamed on refactoring ;)
If you don't have a base class, something like this is cake and very flexible:
public class NotificationObject : INotifyPropertChanged
{
private readonly Dictionary<string, object> Properties = new Dictionary<string, object>();
public event PropertyChangedEventHandler PropertyChanged;
protected TType Get<TType>(string propertyName)
{
object value;
return Properties.TryGetValue(propertyName, out value) ? (TType)value : default(TType);
}
protected void Set<TType>(TType value, string propertyName, params string[] dependantPropertyNames)
{
Properties[propertyName] = value;
OnPropertyChanged(propertyName);
if (dependantPropertyNames != null)
{
foreach (string dependantPropertyName in dependantPropertyNames)
{
OnPropertyChanged(dependantPropertyName);
}
}
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArts(propertyName));
}
}
}
This can be used like this:
public SomeObjectThatNeedsToNotifySomething : NotificationObject
{
public int SomeValue
{
get { return Get<int>("SomeValue"); }
set { Set<int>(value, "SomeValue", "SomeAggregateValue"); }
}
public int SomeOtherValue
{
get { return Get<int>("SomeOtherValue"); }
set { Set<int>(value, "SomeOtherValue", "SomeAggregateValue"); }
}
public int SomeAggregateValue
{
get { return SomeValue + SomeOtherValue; }
}
}
If you already have a base class and need to just implement the INotifyPropertyChanged interface, #Rob is correct, provide a backing field and fire the event.
There is no such thing as semi-automatic properties. Nevertheless, there are quite a few ways to implement INotifyPropertyChanged that don't require the burdensome imperative code.
1) Mentioned before: PostSharp, an aspect oriented and commercial project.
2) Creating a Castle DynamicProxy solution. A sample can be found here, actually there's plenty of others out there.
It's worthwhile investing some time in a generic solution, the boilerplate code can get vexing after a while and is prone to errors.
This is just something I was thinking as I was learning on Attributes and I was using the INotifyPropertyChanged too much, is just and Idea and I would like to hear some opinios about it.( I know this would require some work on the compiler and not on the cosumer side)
Since INotifyPropertyChanged is used with the same Pattern most of the time .. just like calling the method that fire ups the event with the name of the property ,could it be designed as and Attribute and using Auto-Properties? So that the compiler knows it need to add the call to the PropertyChanged event?
So if we have the class....
public class DemoCustomer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string companyNameValue = String.Empty;
...
}
Instead of declaring the property
public string CompanyName
{
get
{
return this.companyNameValue;
}
set
{
if (value != this.companyNameValue)
{
this.companyNameValue = value;
NotifyPropertyChanged("CompanyName");
}
}
}
we could do something like this if we can indicate to the compiler by this attribute that it needs to generate a call to the PropertyChanged with the name of the property if the new value is different from the previous
[NotifyPropertyChanged]
public string CompanyName
{
get;set;
}
We could still keep coding in the old way for some custom behaviours when no using the Attribute..
In case anyone happens across this thread and is using C# 5 (VS 2012+, .NET 4.5+). This case be done "more easily" now with CallerMemberNameAttribute. This attribute is applied to a string parameter and causes the compiler to pass in the name of the calling method/property when the default value us used (i.e. when and argument is not passed). Making implementing INotifyPropertyChanged less tiresome. For example:
public class MyClass
{
private string myProperty;
public string MyProperty
{
get { return myProperty; }
set
{
myProperty = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
So, you just need OnPropertyChanged() in each setter to send out the event and don't have to deal with a hard-coded string for the property name.
This style of thinking is called Aspect Oriented Programming (or AOP). You can achieve the end result by adding a post build action using Mono's Cecil to go through properties with that attribute and modify the behavior of the property and spit out the newly compiled assembly with the appropriate behavior. You can look at Jason Bock's Dimecast on "Leveraging Cecil to inject code into your Assemblies"