We have a WPF Project that follows the MVVM pattern.
In the View Model there is a lot of code that looks like this:
private string m_Fieldname;
public string Fieldname
{
get { return m_Fieldname; }
set
{
m_Fieldname = value;
OnPropertyChanged("Fieldname");
}
}
Is there a way to do this that would require less code?
Would be nice with something like this:
[NotifyWhenChanged]
public string Fieldname { get; set ; }
You could have a look at PostSharp. They even have a sample at Data Binding. The code taken from there:
/// <summary>
/// Aspect that, when apply on a class, fully implements the interface
/// <see cref="INotifyPropertyChanged"/> into that class, and overrides all properties to
/// that they raise the event <see cref="INotifyPropertyChanged.PropertyChanged"/>.
/// </summary>
[Serializable]
[IntroduceInterface( typeof(INotifyPropertyChanged),
OverrideAction = InterfaceOverrideAction.Ignore )]
[MulticastAttributeUsage( MulticastTargets.Class,
Inheritance = MulticastInheritance.Strict )]
public sealed class NotifyPropertyChangedAttribute : InstanceLevelAspect,
INotifyPropertyChanged
{
/// <summary>
/// Field bound at runtime to a delegate of the method <c>OnPropertyChanged</c>.
/// </summary>
[ImportMember( "OnPropertyChanged", IsRequired = false)]
public Action<string> OnPropertyChangedMethod;
/// <summary>
/// Method introduced in the target type (unless it is already present);
/// raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
[IntroduceMember( Visibility = Visibility.Family, IsVirtual = true,
OverrideAction = MemberOverrideAction.Ignore )]
public void OnPropertyChanged( string propertyName )
{
if ( this.PropertyChanged != null )
{
this.PropertyChanged( this.Instance,
new PropertyChangedEventArgs( propertyName ) );
}
}
/// <summary>
/// Event introduced in the target type (unless it is already present);
/// raised whenever a property has changed.
/// </summary>
[IntroduceMember( OverrideAction = MemberOverrideAction.Ignore )]
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Method intercepting any call to a property setter.
/// </summary>
/// <param name="args">Aspect arguments.</param>
[OnLocationSetValueAdvice,
MulticastPointcut( Targets = MulticastTargets.Property,
Attributes = MulticastAttributes.Instance)]
public void OnPropertySet( LocationInterceptionArgs args )
{
// Don't go further if the new value is equal to the old one.
// (Possibly use object.Equals here).
if ( args.Value == args.GetCurrentValue() ) return;
// Actually sets the value.
args.ProceedSetValue();
// Invoke method OnPropertyChanged (our, the base one, or the overridden one).
this.OnPropertyChangedMethod.Invoke( args.Location.Name );
}
}
Usage is then as simple as this:
[NotifyPropertyChanged]
public class Shape
{
public double X { get; set; }
public double Y { get; set; }
}
Examples taken from PostSharp site and inserted for completing the answer
It looks like as if the Framework 4.5 slightly simplifies this:
private string m_Fieldname;
public string Fieldname
{
get { return m_Fieldname; }
set
{
m_Fieldname = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
// ... do stuff here ...
}
This doesn't quite automate things to the extent you're looking for, but using the CallerMemberNameAttribute makes passing the property name as a string unnecessary.
If you're working on Framework 4.0 with KB2468871 installed, you can install the Microsoft BCL Compatibility Pack via nuget, which also provides this attribute.
Josh Smith has a good article on using DynamicObject to do this here
Basically it involves inheriting from DynamicObject and then hooking into TrySetMember. CLR 4.0 only, unfortunately, although it may also be possible using ContextBoundObject in earlier versions but that would probably hurt performance, being primarily suited for remoting\WCF.
IMHO, the PostSharp approach, as in the accepted answer, is very nice and is of course the direct answer to the question asked.
However, for those who can't or won't use a tool like PostSharp to extend the C# language syntax, one can get most of the benefit of avoiding code repetition with a base class that implements INotifyPropertyChanged. There are many examples lying around, but none have so far been included in this useful and well-trafficked question, so here is the version I generally use:
/// <summary>
/// Base class for classes that need to implement <see cref="INotifyPropertyChanged"/>
/// </summary>
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
/// <summary>
/// Raised when a property value changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Updates a field for a named property
/// </summary>
/// <typeparam name="T">The type of the field</typeparam>
/// <param name="field">The field itself, passed by-reference</param>
/// <param name="newValue">The new value for the field</param>
/// <param name="onChangedCallback">A delegate to be called if the field value has changed. The old value of the field is passed to the delegate.</param>
/// <param name="propertyName">The name of the associated property</param>
protected void UpdatePropertyField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
OnPropertyChanged(propertyName);
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">The name of the property that has been changed</param>
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Used, for example, like this:
private int _value;
public int Value
{
get { return _value; }
set { UpdatePropertyField(ref _value, value); }
}
Not quite as concise as being able to just apply a code attribute to an auto-implemented property as in the PostSharp approach, but still goes a long way to speeding the implementation of view models and other similar types.
The key features above that distinguish it from some other implementations:
Equality is compared using EqualityComparer<T>.Default. This ensures that value types can be compared without being boxed (a common alternative would be object.Equals(object, object)). The IEqualityComparer<T> instance is cached, so after the first comparison for any given type T, it's very efficient.
The OnPropertyChanged() method is virtual. This allows derived types to easily and efficiently handle property changed events in a centralized way, without having to subscribe to the PropertyChanged event itself (e.g. for multiple levels of inheritance) and also of course gives the derived type better control over how and when it handles the property changed event relative to raising the actual PropertyChanged event.
Ok this doesn't clean the code up but it shortens the amount of time writing all of this code. I can now blow through a list of 20+ properties in a couple of minutes.
First you need to define all of your private variables, I'm assuming your first character is a lower case. Now copy that those variables into another list as the macro removes the original line.
For example:
private int something1 = 0;
private int something2 = 0;
private int something3 = 0;
private int something4 = 0;
private int something5 = 0;
private int something6 = 0;
Then put your cursor somewhere on that line and run this macro. Again this replaces the line with a public property so make sure you have the same private member variable defined before this in your class.
I'm sure this script could be cleaned up, but it saved me hours of tedious work today.
Sub TemporaryMacro()
DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText)
DTE.ActiveDocument.Selection.Delete(7)
DTE.ActiveDocument.Selection.Text = "public"
DTE.ActiveDocument.Selection.CharRight()
DTE.ExecuteCommand("Edit.Find")
DTE.Find.FindWhat = " "
DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument
DTE.Find.MatchCase = False
DTE.Find.MatchWholeWord = False
DTE.Find.Backwards = False
DTE.Find.MatchInHiddenText = False
DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
DTE.Find.Action = vsFindAction.vsFindActionFind
If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then
Throw New System.Exception("vsFindResultNotFound")
End If
DTE.ActiveDocument.Selection.CharRight()
DTE.ActiveDocument.Selection.WordRight(True)
DTE.ActiveDocument.Selection.CharLeft(True)
DTE.ActiveDocument.Selection.Copy()
DTE.ActiveDocument.Selection.CharLeft()
DTE.ActiveDocument.Selection.CharRight(True)
DTE.ActiveDocument.Selection.ChangeCase(VsCaseOptions.VsCaseOptionsUppercase)
DTE.ActiveDocument.Selection.EndOfLine()
DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText)
DTE.ExecuteCommand("Edit.Find")
DTE.Find.FindWhat = " = "
DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument
DTE.Find.MatchCase = False
DTE.Find.MatchWholeWord = False
DTE.Find.Backwards = False
DTE.Find.MatchInHiddenText = False
DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
DTE.Find.Action = vsFindAction.vsFindActionFind
If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then
Throw New System.Exception("vsFindResultNotFound")
End If
DTE.ActiveDocument.Selection.CharLeft()
DTE.ActiveDocument.Selection.EndOfLine(True)
DTE.ActiveDocument.Selection.Delete()
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "{"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "get { return "
DTE.ActiveDocument.Selection.Paste()
DTE.ActiveDocument.Selection.Text = "; }"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "set"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "{"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "if("
DTE.ActiveDocument.Selection.Paste()
DTE.ActiveDocument.Selection.Text = " != value)"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "{"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Paste()
DTE.ActiveDocument.Selection.Text = " = value;"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "OnPropertyChanged("""
DTE.ActiveDocument.Selection.Paste()
DTE.ActiveDocument.Selection.Text = """);"
DTE.ActiveDocument.Selection.StartOfLine(VsStartOfLineOptions.VsStartOfLineOptionsFirstText)
DTE.ExecuteCommand("Edit.Find")
DTE.Find.FindWhat = """"
DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument
DTE.Find.MatchCase = False
DTE.Find.MatchWholeWord = False
DTE.Find.Backwards = False
DTE.Find.MatchInHiddenText = False
DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
DTE.Find.Action = vsFindAction.vsFindActionFind
If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then
Throw New System.Exception("vsFindResultNotFound")
End If
DTE.ActiveDocument.Selection.CharRight()
DTE.ActiveDocument.Selection.CharRight(True)
DTE.ActiveDocument.Selection.ChangeCase(VsCaseOptions.VsCaseOptionsUppercase)
DTE.ActiveDocument.Selection.Collapse()
DTE.ActiveDocument.Selection.EndOfLine()
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "}"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "}"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.Text = "}"
DTE.ActiveDocument.Selection.NewLine()
DTE.ActiveDocument.Selection.LineDown()
DTE.ActiveDocument.Selection.EndOfLine()
End Sub
I'd use PropertyChanged.Fody NuGet package. It's as simple as:
[PropertyChanged.ImplementPropertyChanged]
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
All public properties will raise PropertyChanged event under the hood.
P.S. Syntax changed in newer versions. This example is for version 1.52.1
Related
If the property event will trigger I want a method that will be called
For example. If the name of person is changed the MethodOne() will be also called. How to implement this with INotifyPropertyChanged on another class for example on WPF MainWindow.xaml.cs? Thank You
public class ObservableObject : INotifyPropertyChanged
{
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
}
...
There a few ways that you can get notified of property changed.
Generally you should always define properties using DependencyProperty.Register and then add access methods.
In the example that follows change YourClass to be your class name.
Declare the property like this, note that the new PropertyMetadata specifies a static method to call when the property is changed. This can be useful but is optional and can be omitted.
/// <summary>
/// Backs the IsBusy dependency property.
/// </summary>
public static readonly DependencyProperty IsBusyProperty =
DependencyProperty.Register(
"IsBusy",
typeof( bool ),
typeof( YourClass ),
new PropertyMetadata( false, new PropertyChangedCallback( OnIsBusyChanged ) ) );
then provide an access method that will use the underlying backing property, so that any changes can be tracked.
/// <summary>
/// Gets or sets a value indicating whether the busy indicator should show.
/// </summary>
public bool IsBusy
{
get
{
return ( bool )GetValue( IsBusyProperty );
}
set
{
SetValue( IsBusyProperty, value );
}
}
The following is the static interface to the dependency property that is required to be static. In here we will receive the dependency object that will usually be an instance of your class and thus we can invoke an object method from that class.
/// <summary>
/// IsBusyProperty property changed handler.
/// </summary>
/// <remarks>
/// This is the static version that is invoked by the dependency property
/// with the appropriate object, so see if d is our class and if so then
/// we can invoke the method on the class
/// </remarks>
/// <param name="d">Instance of class that changed its IsBusy.</param>
/// <param name="e">Event arguments.</param>
private static void OnIsBusyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
if (d is YourClass)
{
var bb = d as YourClass;
bb.OnIsBusyChanged(e);
}
}
and finally the method that will be called when the property is changed in the object
/// <summary>
/// IsBusyProperty property changed handler.
/// </summary>
/// <param name="e">Event arguments.</param>
protected virtual void OnIsBusyChanged(DependencyPropertyChangedEventArgs e)
{
// your code
// you could notify a parent or another event
// handler
}
As an alternative you can directly attach to the PropertyChanged event.
In your constructor (or somewhere)
PropertyChanged += somePropertyChanged;
this will then call the method somePropertyChanged which is defined as follows:
private void somePropertyChanged(object sender, PropertyChangedEventArgs e)
{
// sender is the object that caused the change
// and is usually your class
Console.WriteLine("Property {0} changed", e.PropertyName);
}
The first approach of specifying a callback in DependencyProperty.Register can be useful when you have a set of properties that can be considered as a group and when a similar action will be taken.
I am having some trouble with initializing a new variable as the object being sent by the global property changed. I have two classes BeltConfiguration and BeltProperty (both classes have INotifyPropertyChanged). I have a globalpropertychanged method in the BeltConfiguration class as seen here.
private void BeltProperty_GlobalPropertyChanged(object sender, PropertyChangedEventArgs e)
{
BeltProperty validBelt = sender as BeltProperty;
if (validBelt != null)
{
this.Validation = validBelt.Validation;
}
switch (e.PropertyName)
{
case "Value":
this.Validation.ValidState = ConfigurationValid.Unknown;
OnPropertyChanged("Validate");
break;
case "ConfigurationInvalid":
this.Validation.ValidState = ConfigurationValid.False;
OnPropertyChanged("Validate");
break;
}
}
In the BeltProperty class, I call this with OnGlobalPropertyChanged("ConfigurationInvalid");
However, when I call it, no matter what I do, validBelt always results in being null. I looked at the object sender by stepping through the code and it says that the declaring method, GenericParametersAttributes, and GenericParametersPosition threw an exception of System.InvalidOperationException. I don't know if that has anything to do with why validBelt won't accept sender as BeltProperty. Thanks for any help or advice you can give me.
This is where I called BeltProperty_GlobalPropertyChanged in the Belt Property class.
private ConfigurationValidation _Validation = new ConfigurationValidation(ConfigurationValid.Unknown, "", "", null);
/// <summary>
/// Stores information as to wether this belt property is valid or invalid, and the details.
/// </summary>
internal ConfigurationValidation Validation
{
get { return _Validation; }
set {
_Validation = value;
if(_Validation.ValidState == ConfigurationValid.False)
{
OnGlobalPropertyChanged("ConfigurationInvalid");
}
}
}
/// <summary>
/// A global on property change that runs for any instantiated object of this type.
/// </summary>
/// <param name="name"></param>
static void OnGlobalPropertyChanged(string name)
{
GlobalPropertyChanged(
typeof(BeltProperty),
new PropertyChangedEventArgs(name));
}
Since you are using a safe cast here:
BeltProperty validBelt = sender as BeltProperty
When validBelt is null after this assignment it means sender cannot be cast to an instance of BeltProperty.
Looking at your calling code it looks like you are not passing an instance of BeltProperty into your event handler.
Assuming GlobalPropertyChanged is your PropertyChangedEventHandler delegate change your OnGlobalPropertyChanged code to this:
/// <summary>
/// A global on property change that runs for any instantiated object of this type.
/// </summary>
/// <param name="name"></param>
static void OnGlobalPropertyChanged(string name)
{
GlobalPropertyChanged(
this,
new PropertyChangedEventArgs(name)
);
}
I want to change an image source from different classes (pages) including a settings flyout
I was acknowledged that i have to make the image as a global variable. But couldn't figure out how to do it. Is there any other way ?
Since you want a change to affect all pages - a shared view model is your best option. Create your view model class first:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace App.ViewModel
{
public sealed class MyViewModel : INotifyPropertyChanged
{
#region BindableBase implementation
/// <summary>
/// Multicast event for property change notifications.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Notifies listeners that a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers
/// that support <see cref="CallerMemberNameAttribute"/>.</param>
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region MyImage
/// <summary>
/// Backing field for the MyImage property.
/// </summary>
private ImageSource myImage;
/// <summary>
/// Gets or sets a value indicating ....
/// </summary>
public string MyImage
{
get { return this.myImage; }
set { this.SetProperty(ref this.myImage, value); }
}
#endregion
}
}
Then in App.xaml instantiate it as a resource:
<Application
x:Class="App.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="using:App.ViewModel">
<Application.Resources>
<viewModel:MyViewModel
x:Key="MyViewModel"/>
</Application.Resources>
</Application>
Then anywhere you want to use that image put something like:
<Image
Source="{Binding MyImage, Source={StaticResource MyViewModel}"/>
Then when you want to change the image you can do something like this:
((MyViewModel)App.Current.Resources["MyViewModel"]).MyImage = new BitmapImage();
Are you using ViewModel classes to do your codes?
If yes, you can create a BaseViewModel class and there you can place your global property.
And all your viewmodel classes must have to inherit BaseViewModel. So that from any viewmodel you can change or set the Image source by accessing the base class property.
Public class BaseViewModel
{
//place your global properties here
Public string Name {get;set;}
}
Public class ViewModel1:BaseViewModel
{
//you can access the global property here
void Method1()
{
Name="something";
}
}
Public class ViewModel2:BaseViewModel
{
//you can access the global property here
void Method2()
{
Name="something";
}
}
Hope this helps.
Thanks
I have an observable collection I am trying to serialize to disk. The error that is received is :
Type 'VisuallySpeaking.Data.GrammarList' with data contract name
'GrammarList:http://schemas.datacontract.org/2004/07/VisuallySpeaking.Data'
is not expected. Consider using a DataContractResolver or add any
types not known statically to the list of known types - for example,
by using the KnownTypeAttribute attribute or by adding them to the
list of known types passed to
DataContractSerializer."} System.Exception
{System.Runtime.Serialization.SerializationException}
Here is my data object:
namespace VisuallySpeaking.Data
{
[CollectionDataContract]
public class GrammarList : ObservableCollection<GrammarDataObject>
{
public GrammarList() : base()
{
Add(new GrammarDataObject("My Name", "My name is","Assets/SampleAssets/MyName.png"));
Add(new GrammarDataObject("Where is", "Where is",""));
Add(new GrammarDataObject("Dog", "I have a dog","/Assets/SampleAssets/westie.jpg"));
}
}
[DataContract]
public class GrammarDataObject : VisuallySpeaking.Common.BindableBase
{
private string _Name;
private string _SpeakingText;
private string _ImagePath;
public GrammarDataObject(string Name, string SpeakingText, string ImagePath)
{
this.Name = Name;
this.SpeakingText = SpeakingText;
this.ImagePath = ImagePath;
}
[DataMember]
public string Name
{
get { return _Name; }
set
{
if (this._Name != value)
{
this._Name = value;
this.OnPropertyChanged("Name");
}
}
}
[DataMember]
public string SpeakingText
{
get { return _SpeakingText; }
set
{
if (this._SpeakingText != value)
{
this._SpeakingText = value;
this.OnPropertyChanged("SpeakingText");
}
}
}
[DataMember]
public string ImagePath
{
get { return _ImagePath; }
set
{
if (this._ImagePath != value)
{
this._ImagePath = value;
this.OnPropertyChanged("ImagePath");
}
}
}
}
Based on Fresh's comments, I have added BindableBase in here as well.
namespace VisuallySpeaking.Common
{
/// <summary>
/// Implementation of <see cref="INotifyPropertyChanged"/> to simplify models.
/// </summary>
[Windows.Foundation.Metadata.WebHostHidden]
[DataContract(IsReference = true)]
public abstract class BindableBase : INotifyPropertyChanged
{
/// <summary>
/// Multicast event for property change notifications.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Notifies listeners that a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers
/// that support <see cref="CallerMemberNameAttribute"/>.</param>
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I assume that I have somehow marked my GrammarList class incorrectly, but it escapes me as to how to resolve.
UPDATE:
Following the error message (of course), I added the KnowTypeAttribute and it appeared to work:
[CollectionDataContract(Name = "GrammarList"),KnownType(typeof(GrammarList))]
public class GrammarList : ObservableCollection<GrammarDataObject>
Again, thanks to Fresh, I updated the CollectionDataContract with the Name="GrammarList", but now the issue comes when I rehydrate the XML file from disk. I get the following error message:
Expecting element 'GrammarList' from namespace
'http://schemas.datacontract.org/2004/07/VisuallySpeaking.Data'..
Encountered 'Element' with name 'GrammarDataObject', namespace
'http://schemas.datacontract.org/2004/07/VisuallySpeaking.Data'.
The serialized XML looks like this:
<?xml version="1.0"?>
<GrammarDataObject xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/VisuallySpeaking.Data" i:type="GrammarList">
<GrammarDataObject xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="i1">
<ImagePath>Assets/SampleAssets/MyName.png</ImagePath>
<Name>My Name</Name>
<SpeakingText>My name is</SpeakingText>
</GrammarDataObject>
<GrammarDataObject xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="i3">
<ImagePath/>
<Name>Where is</Name>
<SpeakingText>Where is</SpeakingText>
</GrammarDataObject>
<GrammarDataObject xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" z:Id="i4">
<ImagePath>/Assets/SampleAssets/westie.jpg</ImagePath>
<Name>Dog</Name>
<SpeakingText>I have a dog</SpeakingText>
</GrammarDataObject>
</GrammarDataObject>
Why is the XML outer tags not listed as "GrammarList"? I would assume that is what the deserializer is looking for. When I manually edit the serialized xml to place GrammarList as the outside tags, it deserializes appropriately. I feel sure I am missing something again!
UPDATE AGAIN
When I was serializing I had the following code:
DataContractSerializer serializer = new DataContractSerializer(typeof(GrammarDataObject));
I changed it to serialize to GrammarList and presto, fixed!!! thanks for the help Fresh.
DataContractSerializer serializer = new DataContractSerializer(typeof(GrammarList));
Looking at the XML output, it looks like the name of the collection is lost when its de-serialized.
Try setting the Name property on the CollectionDataContract i.e.
[CollectionDataContract(Name="GrammarList"),KnownType(typeof(GrammarList))]
public class GrammarList : ObservableCollection<GrammarDataObject>
I don't know how I can make a generic settings class and hope that you can help me.
First of all I want a single settings file solution. For this I have created a Singleton like this:
public sealed class Settings
{
private static readonly Lazy<Settings> _instance = new Lazy<Settings>(() => new Settings());
private Dictionary<string, object> m_lProperties = new Dictionary<string, object>();
public void Load(string fileName)
{
throw new NotImplementedException();
}
public void Save(string fileName)
{
throw new NotImplementedException();
}
public void Update()
{
throw new NotImplementedException();
}
/// <summary>
/// Gets the propery.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public string GetPropery(string propertyName)
{
return m_lProperties[propertyName].ToString() ?? String.Empty;
}
/// <summary>
/// Gets the propery.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns></returns>
public string GetPropery(string propertyName, string defaultValue)
{
if (m_lProperties.ContainsKey(propertyName))
{
return m_lProperties[propertyName].ToString();
}
else
{
SetProperty(propertyName, defaultValue);
return defaultValue;
}
}
/// <summary>
/// Sets the property.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="value">The value.</param>
public void SetProperty(string propertyName, string value)
{
if (m_lProperties.ContainsKey(propertyName))
m_lProperties[propertyName] = value;
else
m_lProperties.Add(propertyName, value);
}
}
But I think the better way is that the properties are in the classes and I can get the properties through reflection.
- Can you help me to implement something like this?
- Is it possible to give properties attributes like "encrypted = true"?
- Whats the best way to save / load the settings in a xml file?
Updated
Here is a example how to use the settings actual:
class Test()
{
private string applicationPath;
private string configurationPath;
private string configurationFile;
public Test()
{
applicationPath = Settings.Instance.GetPropery("ApplicationPath", AppDomain.CurrentDomain.BaseDirectory);
configurationPath = Settings.Instance.GetPropery("ConfigurationPath", "configurations");
configurationFile = Settings.Instance.GetPropery("ConfigurationFile", "application.xml");
// ... Load file with all settings from all classes
}
This here is a rather relevant bit from my own code.
public class MyObject
{
public string StringProperty {get; set;}
public int IntProperty {get; set;}
public object this[string PropertyName]
{
get
{
return GetType().GetProperty(PropertyName).GetGetMethod().Invoke(this, null);
}
set
{
GetType().GetProperty(PropertyName).GetSetMethod().Invoke(this, new object[] {value});
}
}
}
what it allows, is this:
MyObject X = new MyObject();
//Set
X["StringProperty"] = "The Answer Is: ";
X["IntProperty"] = 42;
//Get - Please note that object is the return type, so casting is required
int thingy1 = Convert.ToInt32(X["IntProperty"]);
string thingy2 = X["StringProperty"].ToString();
Updated: More Explanation
The way this works is to reflectively access properties, properties are different from fields in that they use getters and setters, as opposed to being directly declared and accessed. You can use this same method to get fields, or to also get fields, if you null check the return from GetProperty instead of simply assuming it works. Also, as was pointed out in another comment, this will break if you call it as is with a property that doesn't exist, because it lacks any form of error catching. I showed the code in its simplest possible form, not its most robust form.
As far as property attributes....that indexer needs to be created inside the class you want to use it with (or a parent class, I have it on my BaseObject), so internally you can implement attributes on given properties and then apply switches or checks against the properties when they are accessed. Maybe make all the properties some other custom class where you implement Object Value; Bool Encrypted; then work on it as needed from there, it really just depends on how fancy you want to get and how much code you want to write.
I not reccommend use Reflection in places where it possible do without it, as it very slow.
My example without reflection and Encryption prototype:
public sealed class Settings
{
private static readonly HashSet<string> _propertiesForEncrypt = new HashSet<string>(new string[] { "StringProperty", "Password" });
private static readonly Lazy<Settings> _instance = new Lazy<Settings>(() => new Settings());
private Dictionary<string, object> m_lProperties = new Dictionary<string, object>();
public void Load(string fileName)
{
// TODO: When you deserialize property which contains into "_propertiesForEncrypt" than Decrypt this property.
throw new NotImplementedException();
}
public void Save(string fileName)
{
// TODO: When you serialize property which contains into "_propertiesForEncrypt" than Encrypt this property.
throw new NotImplementedException();
}
public void Update()
{
throw new NotImplementedException();
}
/// <summary>
/// Gets the propery.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <returns></returns>
public object GetPropery(string propertyName)
{
if (m_lProperties.ContainsKey(propertyName))
return m_lProperties[propertyName];
return null;
}
/// <summary>
/// Gets the propery.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns></returns>
public object GetPropery(string propertyName, object defaultValue)
{
if (m_lProperties.ContainsKey(propertyName))
{
return m_lProperties[propertyName].ToString();
}
else
{
SetProperty(propertyName, defaultValue);
return defaultValue;
}
}
/// <summary>
/// Sets the property.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="value">The value.</param>
public void SetProperty(string propertyName, object value)
{
if (m_lProperties.ContainsKey(propertyName))
m_lProperties[propertyName] = value;
else
m_lProperties.Add(propertyName, value);
}
// Sample of string property
public string StringProperty
{
get
{
return GetPropery("StringProperty") as string;
}
set
{
SetProperty("StringProperty", value);
}
}
// Sample of int property
public int IntProperty
{
get
{
object intValue = GetPropery("IntProperty");
if (intValue == null)
return 0; // Default value for this property.
return (int)intValue;
}
set
{
SetProperty("IntProperty", value);
}
}
}
Use a dynamic class like this:https://gist.github.com/3914644 so you could access your properties as: yourObject.stringProperty or yourObject.intProperty
One of the biggest issues is that there is no clean way to de-serialize an Object into an Object. if you dont know ahead of time what the Type of the object needs to be, its very hard to work with. So we have an alternate solution, store the type information.
Given that its not listed, I will provide what I consider an example XML, as well as a method of using it, and a method of accessing the properties themselves. The functions you are using for Get and Set properties are functional as is, and require no changes.
In the individual classes, you need to make sure the relevant properties in that class reference the Settings class in their own get/set methods
public int? MyClassProperty
{
get
{
return (int?)Settings.Instance.GetProperty("MyClassProperty");
}
set
{
Settings.Instance.SetProperty("MyClassProperty", value);
}
}
In your load and save functions, you will want to use Serialization, specifically, XmlSerializer. To do this, you need to declare your list of settings appropriately. For this I would actually use a custom class.
Updated to allow proper loading
public class AppSetting
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlAttribute("pType")]
public string pType{ get; set; }
[XmlIgnore()]
public object Value{ get; set; }
[XmlText()]
public string AttributeValue
{
get { return Value.ToString(); }
set {
//this is where you have to have a MESSY type switch
switch(pType)
{ case "System.String": Value = value; break;
//not showing the whole thing, you get the idea
}
}
}
Then, instead of just a dictionary, you would have something like:
public sealed class Settings
{
private static readonly Lazy<Settings> _instance = new Lazy<Settings>(() => new Settings());
private Dictionary<string, object> m_lProperties = new Dictionary<string, object>();
private List<AppSetting> mySettings = new List<AppSetting>();
your load function would be a simple de-serialize
public void Load(string fileName)
{//Note: the assumption is that the app settings XML will be defined BEFORE this is called, and be under the same name every time.
XmlSerializer ser = new XmlSerializer(typeof(List<AppSetting>));
FileStream fs = File.Open(fileName);
StreamReader sr = new StreamReader(fs);
mySettings = (List<AppSetting>)ser.DeSerialize(sr);
sr.Close();
fs.Close();
//skipping the foreach loop that will add all the properties to the dictionary
}
the save function would essentially need to reverse it.
public void Save(string fileName)
{
//skipping the foreach loop that re-builds the List from the Dictionary
//Note: make sure when each AppSetting is created, you also set the pType field...use Value.GetType().ToString()
XmlSerializer ser = new XmlSerializer(typeof(List<AppSetting>));
FileStream fs = File.Open(fileName, FileMode.Create);
StreamWriter sw = new StreamWriter(fs);
//get rid of those pesky default namespaces
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
ser.Serialize(sw, mySettings, ns);
sw.Flush();
sw.Close();
fs.Close();
mySettings = null;//no need to keep it around
}
and the xml would resemble something like this:
updated
<ArrayOfAppSetting>
<AppSetting Name="ApplicationPath" pType="System.String">C:\Users\ME\Documents\Visual Studio 2010\Projects\WindowsFormsApplication1\WindowsFormsApplication1\bin\Debug\</AppSetting>
<AppSetting Name="ConfigurationPath" pType="System.String">configurations</AppSetting>
<AppSetting Name="ConfigurationFile" pType="System.String">application.xml</AppSetting>
<AppSetting Name="prop" pType="System.Int32">1</AppSetting>
</ArrayOfAppSetting>
I showed this example using the intermediate List<> because as it turns out you can't use anything that implements IDictionary with XmlSerializer. It will fail to initialize, it just doesn't work.
You can either create and maintain the list alongside the dictionary, or you can replace the dictionary with the List...make sure you have checks to verify that "Name" is unique, or you can simply ignore the list except during the Save and Load operations (which is how I wrote this example)
Update
This really only works well with Primitive types (int, double, string, etc..), but because you directly store the type, you can use any custom type you want, because you know what it is and what to do with it, you just have to handle it in the set method of AttributeValue
Another Update: If you are only storing strings, instead of objects of all types...it gets ridiculously simpler. get rid of the XmlIgnore value AND the pType, then auto-implement AttributeValue. Boom, done. That will limit you to strings and other primitives though, make sure that the Get/Set for the values in other classes cast them appropriately...but it is a much simpler and easier implementation.