@ -5,11 +5,15 @@ using System;
@@ -5,11 +5,15 @@ using System;
using System.Collections ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Diagnostics ;
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Runtime.Serialization ;
using System.Text ;
using System.Threading ;
using System.Xml ;
using System.Xml.Linq ;
using System.Xml.Serialization ;
namespace ICSharpCode.Core
@ -34,116 +38,462 @@ namespace ICSharpCode.Core
@@ -34,116 +38,462 @@ namespace ICSharpCode.Core
}
/// <summary>
/// Description of PropertyGroup.
/// A container for settings - key/value pairs where keys are strings, and values are arbitrary objects.
/// Instances of this class are thread-safe.
/// </summary>
public class Properties
public sealed class Properties : INotifyPropertyChanged , ICloneable
{
/// <summary> Needed for support of late deserialization </summary>
class SerializedValue {
string content ;
public string Content {
get { return content ; }
// Properties instances form a tree due to the nested properties containers.
// All nodes in such a tree share the same syncRoot in order to simplify synchronization.
// When an existing node is added to a tree, its syncRoot needs to change.
object syncRoot ;
Properties parent ;
// Objects in the dictionary are one of:
// - string: value stored using TypeConverter
// - XElement: serialized object
// - object[]: a stored list (array elements are null, string or XElement)
// - Properties: nested properties container
Dictionary < string , object > dict = new Dictionary < string , object > ( ) ;
#region Constructor
public Properties ( )
{
this . syncRoot = new object ( ) ;
}
private Properties ( Properties parent )
{
this . parent = parent ;
this . syncRoot = parent . syncRoot ;
}
#endregion
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged ;
void OnPropertyChanged ( string key )
{
var handler = Volatile . Read ( ref PropertyChanged ) ;
if ( handler ! = null )
handler ( this , new PropertyChangedEventArgs ( "key" ) ) ;
}
#endregion
#region IsDirty
bool isDirty ;
/// <summary>
/// Gets/Sets whether this properties container is dirty.
/// IsDirty automatically gets set to <c>true</c> when a property in this container (or a nested container)
/// changes.
/// </summary>
public bool IsDirty {
get { return isDirty ; }
set {
lock ( syncRoot ) {
if ( value )
MakeDirty ( ) ;
else
CleanDirty ( ) ;
}
}
public T Deserialize < T > ( )
{
XmlSerializer serializer = new XmlSerializer ( typeof ( T ) ) ;
return ( T ) serializer . Deserialize ( new StringReader ( content ) ) ;
}
void MakeDirty ( )
{
// called within syncroot
if ( ! isDirty ) {
isDirty = true ;
if ( parent ! = null )
parent . MakeDirty ( ) ;
}
public SerializedValue ( string content )
{
this . content = content ;
}
void CleanDirty ( )
{
if ( isDirty ) {
isDirty = false ;
foreach ( var properties in dict . Values . OfType < Properties > ( ) ) {
properties . CleanDirty ( ) ;
}
}
}
#endregion
Dictionary < string , object > properties = new Dictionary < string , object > ( ) ;
#region Keys/Contains
public IReadOnlyList < string > Keys {
get {
lock ( syncRoot ) {
return dict . Keys . ToArray ( ) ;
}
}
}
public string this [ string property ] {
/// <summary>
/// Gets whether this properties instance contains any entry (value, list, or nested container)
/// with the specified key.
/// </summary>
public bool Contains ( string key )
{
lock ( syncRoot ) {
return dict . ContainsKey ( key ) ;
}
}
#endregion
#region Get and Set
/// <summary>
/// Retrieves a string value from this Properties-container.
/// Using this indexer is equivalent to calling <c>Get(key, string.Empty)</c>.
/// </summary>
public string this [ string key ] {
get {
return Convert . ToString ( Get ( property ) , CultureInfo . InvariantCulture ) ;
lock ( syncRoot ) {
object val ;
dict . TryGetValue ( key , out val ) ;
return val as string ? ? string . Empty ;
}
}
set {
Set ( property , value ) ;
Set ( ke y, value ) ;
}
}
public string [ ] Elements
/// <summary>
/// Retrieves a single element from this Properties-container.
/// </summary>
/// <param name="key">Key of the item to retrieve</param>
/// <param name="defaultValue">Default value to be returned if the key is not present.</param>
public T Get < T > ( string key , T defaultValue )
{
get {
lock ( properties ) {
return properties . Keys . ToArray ( ) ;
lock ( syncRoot ) {
object val ;
if ( dict . TryGetValue ( key , out val ) ) {
return ( T ) Deserialize ( val , typeof ( T ) ) ;
} else {
return defaultValue ;
}
}
}
/// <summary>
/// Sets a single element in this Properties-container.
/// The element will be serialized using a TypeConverter if possible, or DataContractSerializer otherwise.
/// </summary>
/// <remarks>Setting a key to <c>null</c> has the same effect as calling <see cref="Remove"/>.</remarks>
public void Set < T > ( string key , T value )
{
object serializedValue = Serialize ( value , typeof ( T ) , key ) ;
SetSerializedValue ( key , serializedValue ) ;
}
void SetSerializedValue ( string key , object serializedValue )
{
if ( serializedValue = = null ) {
Remove ( key ) ;
return ;
}
lock ( syncRoot ) {
object oldValue ;
if ( dict . TryGetValue ( key , out oldValue ) ) {
if ( object . Equals ( serializedValue , oldValue ) )
return ;
HandleOldValue ( oldValue ) ;
}
dict [ key ] = serializedValue ;
}
OnPropertyChanged ( key ) ;
}
#endregion
public object Get ( string property )
#region GetList/SetList
/// <summary>
/// Retrieves the list of items stored with the specified key.
/// If no entry with the specified key exists, this method returns an empty list.
/// </summary>
/// <remarks>
/// This method returns a copy of the list used internally; you need to call
/// <see cref="SetList"/> if you want to store the changed list.
/// </remarks>
public IReadOnlyList < T > GetList < T > ( string key )
{
lock ( properties ) {
lock ( syncRoot ) {
object val ;
properties . TryGetValue ( property , out val ) ;
return val ;
if ( dict . TryGetValue ( key , out val ) ) {
object [ ] serializedArray = val as object [ ] ;
if ( serializedArray ! = null ) {
T [ ] array = new T [ serializedArray . Length ] ;
for ( int i = 0 ; i < array . Length ; i + + ) {
array [ i ] = ( T ) Deserialize ( serializedArray [ i ] , typeof ( T ) ) ;
}
return array ;
} else {
LoggingService . Warn ( "Properties.GetList(" + key + ") - this entry is not a list" ) ;
}
}
return new T [ 0 ] ;
}
}
public void Set < T > ( string property , T value )
/// <summary>
/// Sets a list of elements in this Properties-container.
/// The elements will be serialized using a TypeConverter if possible, or DataContractSerializer otherwise.
/// </summary>
/// <remarks>Passing <c>null</c> or an empty list as value has the same effect as calling <see cref="Remove"/>.</remarks>
public void SetList < T > ( string key , IEnumerable < T > value )
{
if ( value = = null ) {
Remove ( key ) ;
return ;
}
T [ ] array = value . ToArray ( ) ;
if ( array . Length = = 0 ) {
Remove ( key ) ;
return ;
}
object [ ] serializedArray = new object [ array . Length ] ;
for ( int i = 0 ; i < array . Length ; i + + ) {
serializedArray [ i ] = Serialize ( array [ i ] , typeof ( T ) , null ) ;
}
SetSerializedValue ( key , serializedArray ) ;
}
[Obsolete("Use the GetList method instead", true)]
public T [ ] Get < T > ( string key , T [ ] defaultValue )
{
throw new InvalidOperationException ( ) ;
}
[Obsolete("Use the SetList method instead", true)]
public void Set < T > ( string key , T [ ] value )
{
throw new InvalidOperationException ( ) ;
}
[Obsolete("Use the GetList method instead", true)]
public List < T > Get < T > ( string key , List < T > defaultValue )
{
throw new InvalidOperationException ( ) ;
}
[Obsolete("Use the SetList method instead", true)]
public void Set < T > ( string key , List < T > value )
{
throw new InvalidOperationException ( ) ;
}
[Obsolete("Use the GetList method instead", true)]
public ArrayList Get < T > ( string key , ArrayList defaultValue )
{
throw new InvalidOperationException ( ) ;
}
[Obsolete("Use the SetList method instead", true)]
public void Set < T > ( string key , ArrayList value )
{
throw new InvalidOperationException ( ) ;
}
#endregion
#region Serialization
object Serialize ( object value , Type sourceType , string key )
{
if ( property = = null )
throw new ArgumentNullException ( "property" ) ;
if ( value = = null )
throw new ArgumentNullException ( "value" ) ;
T oldValue = default ( T ) ;
lock ( properties ) {
if ( ! properties . ContainsKey ( property ) ) {
properties . Add ( property , value ) ;
} else {
oldValue = Get < T > ( property , value ) ;
properties [ property ] = value ;
return null ;
TypeConverter c = TypeDescriptor . GetConverter ( sourceType ) ;
if ( c ! = null & & c . CanConvertTo ( typeof ( string ) ) & & c . CanConvertFrom ( typeof ( string ) ) ) {
return c . ConvertToInvariantString ( value ) ;
}
var element = new XElement ( "SerializedObject" ) ;
if ( key ! = null ) {
element . Add ( new XAttribute ( "key" , key ) ) ;
}
var s = new DataContractSerializer ( sourceType ) ;
using ( var xmlWriter = element . CreateWriter ( ) ) {
s . WriteObject ( xmlWriter , value ) ;
}
return element ;
}
object Deserialize ( object serializedVal , Type targetType )
{
if ( serializedVal = = null )
return null ;
XElement element = serializedVal as XElement ;
if ( element ! = null ) {
var s = new DataContractSerializer ( targetType ) ;
using ( var xmlReader = element . CreateReader ( ) ) {
xmlReader . MoveToContent ( ) ;
xmlReader . Read ( ) ; // skip <SerializedObject>
xmlReader . MoveToContent ( ) ;
return s . ReadObject ( xmlReader ) ;
}
} else {
string text = serializedVal as string ;
if ( text = = null )
throw new InvalidOperationException ( "Cannot read a properties container as a single value" ) ;
TypeConverter c = TypeDescriptor . GetConverter ( targetType ) ;
return c . ConvertFromInvariantString ( text ) ;
}
OnPropertyChanged ( new PropertyChangedEventArgs ( this , property , oldValue , value ) ) ;
}
#endregion
public bool Contains ( string property )
#region Remove
/// <summary>
/// Removes the entry (value, list, or nested container) with the specified key.
/// </summary>
public bool Remove ( string key )
{
lock ( properties ) {
return properties . ContainsKey ( property ) ;
bool removed = false ;
lock ( syncRoot ) {
object oldValue ;
if ( dict . TryGetValue ( key , out oldValue ) ) {
removed = true ;
HandleOldValue ( oldValue ) ;
MakeDirty ( ) ;
}
}
if ( removed )
OnPropertyChanged ( key ) ;
return removed ;
}
public int Count {
#endregion
#region Nested Properties
/// <summary>
/// Gets the parent property container.
/// </summary>
public Properties Parent {
get {
lock ( properties ) {
return properties . Count ;
lock ( syncRoot ) {
return parent ;
}
}
}
[Obsolete("Use the NestedProperties method instead", true)]
public Properties Get ( string key , Properties defaultValue )
{
throw new InvalidOperationException ( ) ;
}
[Obsolete("Use the SetNestedProperties method instead", true)]
public void Set ( string key , Properties value )
{
throw new InvalidOperationException ( ) ;
}
/// <summary>
/// Retrieves a nested property container; creating a new one on demand.
/// Multiple calls to this method will return the same instance (unless the entry at this key
/// is overwritten by one of the Set-methods).
/// Changes performed on the nested container will be persisted together with the parent container.
/// </summary>
public Properties NestedProperties ( string key )
{
bool isNewContainer = false ;
Properties result ;
lock ( syncRoot ) {
object oldValue ;
dict . TryGetValue ( key , out oldValue ) ;
result = oldValue as Properties ;
if ( result = = null ) {
result = new Properties ( this ) ;
dict [ key ] = result ;
result . MakeDirty ( ) ;
}
}
if ( isNewContainer )
OnPropertyChanged ( key ) ;
return result ;
}
public bool Remove ( string property )
void HandleOldValue ( object oldValue )
{
lock ( properties ) {
return properties . Remove ( property ) ;
Properties p = oldValue as Properties ;
if ( p ! = null ) {
Debug . Assert ( p . parent = = this ) ;
p . parent = null ;
}
}
public override string ToString ( )
/// <summary>
/// Attaches the specified properties container as nested properties.
///
/// This method is intended to be used in conjunction with the <see cref="IMementoCapable"/> pattern
/// where a new unattached properties container is created and then later attached to a parent container.
/// </summary>
public void SetNestedProperties ( string key , Properties properties )
{
lock ( properties ) {
StringBuilder sb = new StringBuilder ( ) ;
sb . Append ( "[Properties:{" ) ;
foreach ( KeyValuePair < string , object > entry in properties ) {
sb . Append ( entry . Key ) ;
sb . Append ( "=" ) ;
sb . Append ( entry . Value ) ;
sb . Append ( "," ) ;
if ( properties = = null ) {
Remove ( key ) ;
return ;
}
lock ( syncRoot ) {
for ( Properties ancestor = this ; ancestor ! = null ; ancestor = ancestor . parent ) {
if ( ancestor = = properties )
throw new InvalidOperationException ( "Cannot add a properties container to itself." ) ;
}
object oldValue ;
if ( dict . TryGetValue ( key , out oldValue ) ) {
if ( oldValue = = properties )
return ;
HandleOldValue ( oldValue ) ;
}
lock ( properties . syncRoot ) {
if ( properties . parent ! = null )
throw new InvalidOperationException ( "Cannot attach nested properties that already have a parent." ) ;
MakeDirty ( ) ;
properties . SetSyncRoot ( syncRoot ) ;
properties . parent = this ;
dict [ key ] = properties ;
}
sb . Append ( "}]" ) ;
return sb . ToString ( ) ;
}
OnPropertyChanged ( key ) ;
}
public static Properties ReadFromAttributes ( XmlReader reader )
void SetSyncRoot ( object newSyncRoot )
{
this . syncRoot = newSyncRoot ;
foreach ( var properties in dict . Values . OfType < Properties > ( ) ) {
properties . SetSyncRoot ( newSyncRoot ) ;
}
}
#endregion
#region Clone
/// <summary>
/// Creates a deep clone of this Properties container.
/// </summary>
public Properties Clone ( )
{
lock ( syncRoot ) {
return CloneWithParent ( null ) ;
}
}
Properties CloneWithParent ( Properties parent )
{
Properties copy = parent ! = null ? new Properties ( parent ) : new Properties ( ) ;
foreach ( var pair in dict ) {
Properties child = pair . Value as Properties ;
if ( child ! = null )
copy . dict . Add ( pair . Key , child . CloneWithParent ( copy ) ) ;
else
copy . dict . Add ( pair . Key , pair . Value ) ;
}
return copy ;
}
object ICloneable . Clone ( )
{
return Clone ( ) ;
}
#endregion
#region ReadFromAttributes
internal static Properties ReadFromAttributes ( XmlReader reader )
{
Properties properties = new Properties ( ) ;
if ( reader . HasAttributes ) {
@ -159,231 +509,109 @@ namespace ICSharpCode.Core
@@ -159,231 +509,109 @@ namespace ICSharpCode.Core
}
return properties ;
}
#endregion
internal void ReadProperties ( XmlReader reader , string endElement )
#region Load/Save
public static Properties Load ( string fileName )
{
if ( reader . IsEmptyElement ) {
return ;
}
while ( reader . Read ( ) ) {
switch ( reader . NodeType ) {
case XmlNodeType . EndElement :
if ( reader . LocalName = = endElement ) {
return ;
}
break ;
case XmlNodeType . Element :
string propertyName = reader . LocalName ;
if ( propertyName = = "Properties" ) {
propertyName = reader . GetAttribute ( 0 ) ;
Properties p = new Properties ( ) ;
p . ReadProperties ( reader , "Properties" ) ;
properties [ propertyName ] = p ;
} else if ( propertyName = = "Array" ) {
propertyName = reader . GetAttribute ( 0 ) ;
properties [ propertyName ] = ReadArray ( reader ) ;
} else if ( propertyName = = "SerializedValue" ) {
propertyName = reader . GetAttribute ( 0 ) ;
properties [ propertyName ] = new SerializedValue ( reader . ReadInnerXml ( ) ) ;
} else {
properties [ propertyName ] = reader . HasAttributes ? reader . GetAttribute ( 0 ) : null ;
}
break ;
}
}
return Load ( XDocument . Load ( fileName ) . Root ) ;
}
ArrayList ReadArray ( XmlReader reader )
public static Properties Load ( XElement element )
{
if ( reader . IsEmptyElement )
return new ArrayList ( 0 ) ;
ArrayList l = new ArrayList ( ) ;
while ( reader . Read ( ) ) {
switch ( reader . NodeType ) {
case XmlNodeType . EndElement :
if ( reader . LocalName = = "Array" ) {
return l ;
}
Properties properties = new Properties ( ) ;
properties . LoadContents ( element . Elements ( ) ) ;
return properties ;
}
void LoadContents ( IEnumerable < XElement > elements )
{
foreach ( var element in elements ) {
string key = ( string ) element . Attribute ( "key" ) ;
if ( key = = null )
continue ;
switch ( element . Name . LocalName ) {
case "Property" :
dict [ key ] = element . Value ;
break ;
case XmlNodeType . Element :
l . Add ( reader . HasAttributes ? reader . GetAttribute ( 0 ) : null ) ;
case "Array" :
dict [ key ] = LoadArray ( element . Elements ( ) ) ;
break ;
case "SerializedObject" :
dict [ key ] = new XElement ( element ) ;
break ;
case "Properties" :
Properties child = new Properties ( this ) ;
child . LoadContents ( element . Elements ( ) ) ;
dict [ key ] = child ;
break ;
}
}
return l ;
}
public void WriteProperties ( XmlWriter writer )
{
lock ( properties ) {
List < KeyValuePair < string , object > > sortedProperties = new List < KeyValuePair < string , object > > ( properties ) ;
sortedProperties . Sort ( ( a , b ) = > StringComparer . OrdinalIgnoreCase . Compare ( a . Key , b . Key ) ) ;
foreach ( KeyValuePair < string , object > entry in sortedProperties ) {
object val = entry . Value ;
if ( val is Properties ) {
writer . WriteStartElement ( "Properties" ) ;
writer . WriteAttributeString ( "name" , entry . Key ) ;
( ( Properties ) val ) . WriteProperties ( writer ) ;
writer . WriteEndElement ( ) ;
} else if ( val is Array | | val is ArrayList ) {
writer . WriteStartElement ( "Array" ) ;
writer . WriteAttributeString ( "name" , entry . Key ) ;
foreach ( object o in ( IEnumerable ) val ) {
writer . WriteStartElement ( "Element" ) ;
WriteValue ( writer , o ) ;
writer . WriteEndElement ( ) ;
}
writer . WriteEndElement ( ) ;
} else if ( TypeDescriptor . GetConverter ( val ) . CanConvertFrom ( typeof ( string ) ) ) {
writer . WriteStartElement ( entry . Key ) ;
WriteValue ( writer , val ) ;
writer . WriteEndElement ( ) ;
} else if ( val is SerializedValue ) {
writer . WriteStartElement ( "SerializedValue" ) ;
writer . WriteAttributeString ( "name" , entry . Key ) ;
writer . WriteRaw ( ( ( SerializedValue ) val ) . Content ) ;
writer . WriteEndElement ( ) ;
} else {
writer . WriteStartElement ( "SerializedValue" ) ;
writer . WriteAttributeString ( "name" , entry . Key ) ;
XmlSerializer serializer = new XmlSerializer ( val . GetType ( ) ) ;
serializer . Serialize ( writer , val , null ) ;
writer . WriteEndElement ( ) ;
}
}
}
}
void WriteValue ( XmlWriter writer , object val )
static object [ ] LoadArray ( IEnumerable < XElement > elements )
{
if ( val ! = null ) {
if ( val is string ) {
writer . WriteAttributeString ( "value" , val . ToString ( ) ) ;
} else {
TypeConverter c = TypeDescriptor . GetConverter ( val . GetType ( ) ) ;
writer . WriteAttributeString ( "value" , c . ConvertToInvariantString ( val ) ) ;
List < object > result = new List < object > ( ) ;
foreach ( var element in elements ) {
switch ( element . Name . LocalName ) {
case "Null" :
result . Add ( null ) ;
break ;
case "Element" :
result . Add ( element . Value ) ;
break ;
case "SerializedObject" :
result . Add ( new XElement ( element ) ) ;
break ;
}
}
return result . ToArray ( ) ;
}
public void Save ( string fileName )
{
XmlTextWriter writer = new XmlTextWriter ( fileName , Encoding . UTF8 ) ;
writer . Formatting = Formatting . Indented ;
Save ( writer ) ;
}
public void Save ( XmlWriter writer )
{
using ( writer ) {
writer . WriteStartElement ( "Properties" ) ;
WriteProperties ( writer ) ;
writer . WriteEndElement ( ) ;
}
new XDocument ( Save ( ) ) . Save ( fileName ) ;
}
// public void BinarySerialize(BinaryWriter writer)
// {
// writer.Write((byte)properties.Count);
// foreach (KeyValuePair<string, object> entry in properties) {
// writer.Write(AddInTree.GetNameOffset(entry.Key));
// writer.Write(AddInTree.GetNameOffset(entry.Value.ToString()));
// }
// }
public static Properties Load ( string fileName )
public XElement Save ( )
{
if ( ! File . Exists ( fileName ) ) {
return null ;
lock ( syncRoot ) {
return new XElement ( "Properties" , SaveContents ( ) ) ;
}
XmlTextReader reader = new XmlTextReader ( fileName ) ;
return Load ( reader ) ;
}
public static Properties Load ( XmlReader reader )
IReadOnlyList < XElement > SaveContents ( )
{
using ( reader ) {
while ( reader . Read ( ) ) {
if ( reader . IsStartElement ( ) ) {
switch ( reader . LocalName ) {
case "Properties" :
Properties properties = new Properties ( ) ;
properties . ReadProperties ( reader , "Properties" ) ;
return properties ;
}
}
}
}
return null ;
}
public T Get < T > ( string property , T defaultValue )
{
lock ( properties ) {
object o ;
if ( ! properties . TryGetValue ( property , out o ) ) {
properties . Add ( property , defaultValue ) ;
return defaultValue ;
}
if ( o is string & & typeof ( T ) ! = typeof ( string ) ) {
TypeConverter c = TypeDescriptor . GetConverter ( typeof ( T ) ) ;
try {
o = c . ConvertFromInvariantString ( o . ToString ( ) ) ;
} catch ( Exception ex ) {
MessageService . ShowWarning ( "Error loading property '" + property + "': " + ex . Message ) ;
o = defaultValue ;
}
properties [ property ] = o ; // store for future look up
} else if ( o is ArrayList & & typeof ( T ) . IsArray ) {
ArrayList list = ( ArrayList ) o ;
Type elementType = typeof ( T ) . GetElementType ( ) ;
Array arr = System . Array . CreateInstance ( elementType , list . Count ) ;
TypeConverter c = TypeDescriptor . GetConverter ( elementType ) ;
try {
for ( int i = 0 ; i < arr . Length ; + + i ) {
if ( list [ i ] ! = null ) {
arr . SetValue ( c . ConvertFromInvariantString ( list [ i ] . ToString ( ) ) , i ) ;
}
List < XElement > result = new List < XElement > ( ) ;
foreach ( var pair in dict ) {
XAttribute key = new XAttribute ( "key" , pair . Key ) ;
Properties child = pair . Value as Properties ;
if ( child ! = null ) {
var contents = child . SaveContents ( ) ;
if ( contents . Count > 0 )
result . Add ( new XElement ( "Properties" , key , contents ) ) ;
} else if ( pair . Value is object [ ] ) {
object [ ] array = ( object [ ] ) pair . Value ;
XElement [ ] elements = new XElement [ array . Length ] ;
for ( int i = 0 ; i < array . Length ; i + + ) {
XElement obj = array [ i ] as XElement ;
if ( obj ! = null ) {
elements [ i ] = new XElement ( obj ) ;
} else if ( array [ i ] = = null ) {
elements [ i ] = new XElement ( "Null" ) ;
} else {
elements [ i ] = new XElement ( "Element" , ( string ) array [ i ] ) ;
}
o = arr ;
} catch ( Exception ex ) {
MessageService . ShowWarning ( "Error loading property '" + property + "': " + ex . Message ) ;
o = defaultValue ;
}
properties [ property ] = o ; // store for future look up
} else if ( ! ( o is string ) & & typeof ( T ) = = typeof ( string ) ) {
TypeConverter c = TypeDescriptor . GetConverter ( typeof ( T ) ) ;
if ( c . CanConvertTo ( typeof ( string ) ) ) {
o = c . ConvertToInvariantString ( o ) ;
} else {
o = o . ToString ( ) ;
}
} else if ( o is SerializedValue ) {
try {
o = ( ( SerializedValue ) o ) . Deserialize < T > ( ) ;
} catch ( Exception ex ) {
MessageService . ShowWarning ( "Error loading property '" + property + "': " + ex . Message ) ;
o = defaultValue ;
}
properties [ property ] = o ; // store for future look up
}
try {
return ( T ) o ;
} catch ( NullReferenceException ) {
// can happen when configuration is invalid -> o is null and a value type is expected
return defaultValue ;
result . Add ( new XElement ( "Array" , key , elements ) ) ;
} else if ( pair . Value is XElement ) {
result . Add ( new XElement ( ( XElement ) pair . Value ) ) ;
} else {
result . Add ( new XElement ( "Property" , key , ( string ) pair . Value ) ) ;
}
}
return result ;
}
protected virtual void OnPropertyChanged ( PropertyChangedEventArgs e )
{
if ( PropertyChanged ! = null ) {
PropertyChanged ( this , e ) ;
}
}
public event PropertyChangedEventHandler PropertyChanged ;
#endregion
}
}