Dispaying and editing a sub property of a property in PropertyGrid - c#

I could no longer find an exact solution to my problem in the internet so I'm asking this question. Hope you may be able to help me.
I have the following classes:
public Item
{
public FieldType MyField { get; set; }
public string Description { get; set; }
public int Capacity { get; set; }
}
public FieldType
{
public string Value { get; set; }
public string FieldCode { get; set; }
public string TableCode { get; set; }
}
In my form, I created an instance of Item class. Which contains the following members:
MyField (type of FieldType)
Description (type of string)
Capacity (an int)
Is it possible to only show the Value member of MyField property in the PropertyGrid?
Below is how I assign the selected object property of the PropertyGrid.
void Form1(object sender, EventArgs e)
{
propertyGrid1.SelectedObject = new Item();
}

Yes, easy:
add a computed read only property to Item
public Item
{
public FieldType MyField { get; set; }
public string MyFieldValue => MyField.Value;
public string Description { get; set; }
public int Capacity { get; set; }
}

Im not really sure of what you are looking for but here are 2 answers
1.(as I understood it)
if you want it to show only Value when you try and view the properties of a MyField Instance then all you need to do is add a constructor to the MyField so you can assign the other two values and change the public property to private like so
public FieldType
{
public string Value { get; set; }
private string FieldCode { get; set; }
private string TableCode { get; set; }
}
2.(this will hide the MyField from your propertyGrid)
Override the ToString() method of FielType
like so
public override string ToString()
{
return Value;
}
then set your MyField to private and encapsulate it. returning the instance as a string. which would use the overridden value.
like so
private FieldType MyField;
public string value{ get{return MyField.ToString();}set;}
your MyField will return the overridden ToString value which returns Value.

Solution 1 - Add a property
You can add a property to Item class to get and set MyField.Value:
public string Value
{
get
{
if (MyField != null)
return MyField.Value;
return null;
}
set
{
if (MyField != null)
MyField.Value = value;
}
}
• Preferably define that property in a partial class.
• Use this option when you have access to codes of the classes. If those classes are not yours, use 3rd solution.
Solution 2 - Use ExpandableObjectConverter
You can decorate the MyField property of Item class with ExpandableObjectConverter. Also decorate FieldType with [Browsable(false)] of FieldType class to hide it in property grid if you want:
[TypeConverter(typeof(ExpandableObjectConverter))]
public FieldType MyField { get; set; }
• To customize the text which is shown in front of MyField, you can override ToString method of FieldType and return Value. Also you can do it using a custom TypeConverter and overriding its ConvertTo method.
Solution 3 - Use a custom TypeDescriptor
It's not as easy as the first solution, but the output is completely like what you get using the first solution. It's suitable for cases that you can not manipulate those classes.
You can use it this way:
var item = new Item() { MyField = new FieldType() { Value = "Some Value" } };
TypeDescriptor.AddProvider(new MyTypeDescriptionProvider(), item);
this.propertyGrid1.SelectedObject = item;
Or by decorating Item class with:
[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]
public class Item
Custom Property Descriptor
public class MyPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor subProperty;
private PropertyDescriptor parentProperty;
public MyPropertyDescriptor(PropertyDescriptor parent, PropertyDescriptor sub)
: base(sub, null)
{
subProperty = sub;
parentProperty = parent;
}
public override bool IsReadOnly { get { return subProperty.IsReadOnly; } }
public override void ResetValue(object component)
{
subProperty.ResetValue(parentProperty.GetValue(component));
}
public override bool CanResetValue(object component)
{
return subProperty.CanResetValue(parentProperty.GetValue(component));
}
public override bool ShouldSerializeValue(object component)
{
return subProperty.ShouldSerializeValue(parentProperty.GetValue(component));
}
public override Type ComponentType { get { return parentProperty.ComponentType; } }
public override Type PropertyType { get { return subProperty.PropertyType; } }
public override object GetValue(object component)
{
return subProperty.GetValue(parentProperty.GetValue(component));
}
public override void SetValue(object component, object value)
{
subProperty.SetValue(parentProperty.GetValue(component), value);
OnValueChanged(component, EventArgs.Empty);
}
}
Custom type Descriptor
public class MyTypeDescriptor : CustomTypeDescriptor
{
ICustomTypeDescriptor original;
public MyTypeDescriptor(ICustomTypeDescriptor originalDescriptor)
: base(originalDescriptor)
{
original = originalDescriptor;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = original.GetProperties().Cast<PropertyDescriptor>().ToList();
var parent = properties.Where(x => x.Name == "MyField").First();
var sub = TypeDescriptor.GetProperties(typeof(FieldType))["Value"];
properties.Remove(parent);
properties.Add(new MyPropertyDescriptor(parent, sub));
return new PropertyDescriptorCollection(properties.ToArray());
}
}
Custom TypeDescriptorProvider
public class MyTypeDescriptionProvider : TypeDescriptionProvider
{
public MyTypeDescriptionProvider()
: base(TypeDescriptor.GetProvider(typeof(object))) { }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance)
{
ICustomTypeDescriptor baseDes = base.GetTypeDescriptor(objectType, instance);
return new MyTypeDescriptor(baseDes);
}
}
• Use this option if Item and FieldType are not yours. If those classes are yours and you can change their code, use first solution.

Related

C# two objects with the same reference

I have base class that save all property value in a Dictionary. but I want to property value and value in Dictionary has save value. if property value change then change dictionary value and if change value in dictionary , change property value. In Constructor I use reflection but after finish constructor I want not used reflection because of performance.
Type of dictionary value is following (in my project its more complex):
public class AgentProperty
{
public object Value;
public string Name;
public AgentProperty(string name, object value)
{
Value = value;
Name = name;
}
}
The Base Class is following:
public class BaseClass
{
private Dictionary<string,AgentProperty> Dictionary = new Dictionary<string, AgentProperty>();
public AgentProperty this[string key]
{
get { return (AgentProperty)Dictionary[key]; }
set { Dictionary[key] = value; }
}
public void Add(AgentProperty ap)
{
Dictionary.Add(ap.Name, ap);
}
public void SetDict(BaseClass o)
{
var objectType = o.GetType();
foreach (var property in objectType.GetProperties())
{
AgentProperty agentProperty = new AgentProperty(property.Name, property.GetValue(o));
Add(agentProperty);
}
}
}
The sample class that inherited from BaseClass. This class can have any type of property.
public class TmpClass : BaseClass
{
public TmpClass(){
SetDict(this);
}
public string X { get; set; }
public int y { get; set; }
public string Z { get; set; }
public TimeSpan T { get; set; }
}
Is there any way to do this?

Creating a generic root object for JSON.NET? [duplicate]

Is there a way to change name of Data property during serialization, so I can reuse this class in my WEB Api.
For an example, if i am returning paged list of users, Data property should be serialized as "users", if i'm returning list of items, should be called "items", etc.
Is something like this possible:
public class PagedData
{
[JsonProperty(PropertyName = "Set from constructor")]??
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
EDIT:
I would like to have a control over this functionality, such as passing name to be used if possible. If my class is called UserDTO, I still want serialized property to be called Users, not UserDTOs.
Example
var usersPagedData = new PagedData("Users", params...);
You can do this with a custom ContractResolver. The resolver can look for a custom attribute which will signal that you want the name of the JSON property to be based on the class of the items in the enumerable. If the item class has another attribute on it specifying its plural name, that name will then be used for the enumerable property, otherwise the item class name itself will be pluralized and used as the enumerable property name. Below is the code you would need.
First let's define some custom attributes:
public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}
public class JsonPluralNameAttribute : Attribute
{
public string PluralName { get; set; }
public JsonPluralNameAttribute(string pluralName)
{
PluralName = pluralName;
}
}
And then the resolver:
public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type itemType = prop.PropertyType.GetGenericArguments().First();
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
}
return prop;
}
protected string Pluralize(string name)
{
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
return name.Substring(0, name.Length - 1) + "ies";
if (name.EndsWith("s"))
return name + "es";
return name + "s";
}
}
Now you can decorate the variably-named property in your PagedData<T> class with the [JsonPropertyNameBasedOnItemClass] attribute:
public class PagedData<T>
{
[JsonPropertyNameBasedOnItemClass]
public IEnumerable<T> Data { get; private set; }
...
}
And decorate your DTO classes with the [JsonPluralName] attribute:
[JsonPluralName("Users")]
public class UserDTO
{
...
}
[JsonPluralName("Items")]
public class ItemDTO
{
...
}
Finally, to serialize, create an instance of JsonSerializerSettings, set the ContractResolver property, and pass the settings to JsonConvert.SerializeObject like so:
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
string json = JsonConvert.SerializeObject(pagedData, settings);
Fiddle: https://dotnetfiddle.net/GqKBnx
If you're using Web API (looks like you are), then you can install the custom resolver into the pipeline via the Register method of the WebApiConfig class (in the App_Start folder).
JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();
Another Approach
Another possible approach uses a custom JsonConverter to handle the serialization of the PagedData class specifically instead using the more general "resolver + attributes" approach presented above. The converter approach requires that there be another property on your PagedData class which specifies the JSON name to use for the enumerable Data property. You could either pass this name in the PagedData constructor or set it separately, as long as you do it before serialization time. The converter will look for that name and use it when writing out JSON for the enumerable property.
Here is the code for the converter:
public class PagedDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
if (string.IsNullOrEmpty(dataPropertyName))
{
dataPropertyName = "Data";
}
JObject jo = new JObject();
jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
{
jo.Add(prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use this converter, first add a string property called DataPropertyName to your PagedData class (it can be private if you like), then add a [JsonConverter] attribute to the class to tie it to the converter:
[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
private string DataPropertyName { get; set; }
public IEnumerable<T> Data { get; private set; }
...
}
And that's it. As long as you've set the DataPropertyName property, it will be picked up by the converter on serialization.
Fiddle: https://dotnetfiddle.net/8E8fEE
UPD Sep 2020: #RyanHarlich pointed that proposed solution doesn't work out of the box. I found that Newtonsoft.Json doesn't initialize getter-only properties in newer versions, but I'm pretty sure it did ATM I wrote this answer in 2016 (no proofs, sorry :).
A quick-n-dirty solution is to add public setters to all properties ( example in dotnetfiddle ). I encourage you to find a better solution that keeps read-only interface for data objects. I haven't used .Net for 3 years, so cannot give you that solution myself, sorry :/
Another option with no need to play with json formatters or use string replacements - only inheritance and overriding (still not very nice solution, imo):
public class MyUser { }
public class MyItem { }
// you cannot use it out of the box, because it's abstract,
// i.e. only for what's intended [=implemented].
public abstract class PaginatedData<T>
{
// abstract, so you don't forget to override it in ancestors
public abstract IEnumerable<T> Data { get; }
public int Count { get; }
public int CurrentPage { get; }
public int Offset { get; }
public int RowsPerPage { get; }
public int? PreviousPage { get; }
public int? NextPage { get; }
}
// you specify class explicitly
// name is clear,.. still not clearer than PaginatedData<MyUser> though
public sealed class PaginatedUsers : PaginatedData<MyUser>
{
// explicit mapping - more agile than implicit name convension
[JsonProperty("Users")]
public override IEnumerable<MyUser> Data { get; }
}
public sealed class PaginatedItems : PaginatedData<MyItem>
{
[JsonProperty("Items")]
public override IEnumerable<MyItem> Data { get; }
}
Here is a solution that doesn't require any change in the way you use the Json serializer. In fact, it should also work with other serializers. It uses the cool DynamicObject class.
The usage is just like you wanted:
var usersPagedData = new PagedData<User>("Users");
....
public class PagedData<T> : DynamicObject
{
private string _name;
public PagedData(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
_name = name;
}
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
public override IEnumerable<string> GetDynamicMemberNames()
{
yield return _name;
foreach (var prop in GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.Name != nameof(Data)))
{
yield return prop.Name;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == _name)
{
result = Data;
return true;
}
return base.TryGetMember(binder, out result);
}
}
The following is another solution tested in .NET Standard 2.
public class PagedResult<T> where T : class
{
[JsonPropertyNameBasedOnItemClassAttribute]
public List<T> Results { get; set; }
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("total_count")]
public long TotalCount { get; set; }
[JsonProperty("current_page")]
public long CurrentPage { get; set; }
[JsonProperty("per_page")]
public long PerPage { get; set; }
[JsonProperty("pages")]
public long Pages { get; set; }
}
I am using Humanizer for pluralization.
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type[] arguments = property.DeclaringType.GenericTypeArguments;
if(arguments.Length > 0)
{
string name = arguments[0].Name.ToString();
property.PropertyName = name.ToLower().Pluralize();
}
return property;
}
return base.CreateProperty(member, memberSerialization);
}
There's a package called SerializationInterceptor. Here's the GitHub link: https://github.com/Dorin-Mocan/SerializationInterceptor/wiki. You can also install the package using Nuget Package Manager.
The example from below uses Syste.Text.Json for serialization. You can use any other serializer(except Newtonsoft.Json). For more info on why Newtonsoft.Json not allowed, please refer to GitHub documentation.
You can create an interceptor
public class JsonPropertyNameInterceptorAttribute : InterceptorAttribute
{
public JsonPropertyNameInterceptorAttribute(string interceptorId)
: base(interceptorId, typeof(JsonPropertyNameAttribute))
{
}
protected override void Intercept(in AttributeParams originalAttributeParams, object context)
{
string theNameYouWant;
switch (InterceptorId)
{
case "some id":
theNameYouWant = (string)context;
break;
default:
return;
}
originalAttributeParams.ConstructorArgs.First().ArgValue = theNameYouWant;
}
}
And put the interceptor on the Data prop
public class PagedData<T>
{
[JsonPropertyNameInterceptor("some id")]
[JsonPropertyName("during serialization this value will be replaced with the one passed in context")]
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
And then you can serialize the object like this
var serializedObj = InterceptSerialization(
obj,
objType,
(o, t) =>
{
return JsonSerializer.Serialize(o, t, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve });
},
context: "the name you want");
Hope this will be of use to you.
have a look here:
How to rename JSON key
Its not done during serialization but with a string operation.
Not very nice (in my eyes) but at least a possibility.
Cheers Thomas

Use custom property attribute to map value to another type

I"m trying to put together an abstract class that various models in my MVVM will derive from. Part of this is for abstracting IEditableObject/IChangeTracking details.
For my IEditableObject, I want to store a shallow copy of "core data" (a struct that defines data that will be serialized, typically for database storage) so that it can be cancelled or committed. What I don't want to do is type this out for each new Model that I come up with.
I've defined a [DataCoreItem] custom attribute that I thought to use on the derived class's applicable properties. For some unrelated reasons, the abstract class takes a generic DataCoreType and IDType:
public abstract class ModelObject<T, DataCoreType, IDType> : INotifyPropertyChanged, IEditableObject
{
public abstract DataCoreType Load(IDType id);
public abstract bool Save(DataCoreType dataCore);
public abstract bool Delete(IDType id);
// etc...
Here's an example for my CompanyModel
the data core:
public struct CompanyDataCore
{
public int? ID;
public string Code;
public string Name;
public string PrimaryWebsite;
public string PrimaryPhone;
public string PrimaryEmail;
}
the derived class:
public class CompanyModel : ModelObject<CompanyModel, CompanyDataCore, int> {
CompanyDataCore dataCore;
[DataCoreMember]
public int? ID { get { return dataCore.ID; } set { SetProperty(ref dataCore.ID, value); } }
[DataCoreMember]
public string Name { get { return dataCore.Name; } set {SetProperty(ref dataCore.Name, value); } }
[DataCoreMember]
public string Code { get { return dataCore.Code; } set { SetProperty(ref dataCore.Code, value); } }
[DataCoreMember]
public string PrimaryPhone { get { return dataCore.PrimaryPhone; } set {SetProperty(ref dataCore.PrimaryPhone, value); } }
[DataCoreMember]
public string PrimaryEmail { get { return dataCore.PrimaryEmail; } set { SetProperty(ref dataCore.PrimaryEmail, value); } }
[DataCoreMember]
public string PrimaryWebsite { get { return dataCore.PrimaryWebsite; } set { SetProperty(ref dataCore.PrimaryWebsite, value); } }
Ok, finally... what I'd like for my abstract class is to use the BeginEdit(), EndEdit() and CancelEdit() methods to handle storage of a backup copy of the data core automatically. Here's how I envision it:
[DataCoreMember(MemberName="ID")]
public int? ID { get { return dataCore.ID; } set { SetProperty(ref dataCore.ID, value); } }
// etc etc
and in my abstract class:
public virtual void BeginEdit() {
Type t = typeof(T);
var props = t.GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(DataCoreMemberAttribute)));
// WHAT TO DO HERE??? everything else looks good up to here
foreach (object o in props) {
this.dataCoreBackup.???? = o.value;
}
IsEditing = true;
}
How to map the property to which the DataCoreMember is applied to the property of the struct as specified?
I'm inexperienced with reflection (and working generic types as well for that matter), but I gather that this can be done. I've found examples (as of yet untried) for how to get a list of those properties with the attribute applied to them, but I'm unsure how to ultimately reference the DataCore's property based on that. Can anyone show me how? Much appreciated.
Turns out it was a fairly easy task with Reflection. Explanation below (with much of the non-pertinent code stripped out)
This holds the data backup so CancelEdit is supported:
public struct CompanyDataCore
{
public int? ID;
public string Code;
public string Name;
public string PrimaryWebsite;
public string PrimaryPhone;
public string PrimaryEmail;
public string RootPath;
}
Here's the attribute class to denote which fields get backed up:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class DataCoreMemberAttribute : Attribute
{
public string MemberName { get; set; }
}
This is the derived class:
public class CompanyModel : ModelObject<CompanyModel, CompanyDataCore, int>
{
[Identifier]
[DataCoreMember(MemberName="ID")]
public int? ID { get { return dataCore.ID; } set { SetProperty(ref dataCore.ID, value); } }
[DataCoreMember(MemberName="Name")]
public string Name { get { return dataCore.Name; } set {SetProperty(ref dataCore.Name, value); } }
[DataCoreMember(MemberName="Code")]
public string Code { get { return dataCore.Code; } set { SetProperty(ref dataCore.Code, value); } }
[DataCoreMember(MemberName="PrimaryPhone")]
public string PrimaryPhone { get { return dataCore.PrimaryPhone; } set {SetProperty(ref dataCore.PrimaryPhone, value); } }
[DataCoreMember(MemberName="PrimaryEmail")]
public string PrimaryEmail { get { return dataCore.PrimaryEmail; } set { SetProperty(ref dataCore.PrimaryEmail, value); } }
[DataCoreMember(MemberName="PrimaryWebsite")]
public string PrimaryWebsite { get { return dataCore.PrimaryWebsite; } set { SetProperty(ref dataCore.PrimaryWebsite, value); } }
}
And here's the abstract class:
public abstract class ModelObject<T, DataCoreType, IDType> : INotifyPropertyChanged, IEditableObject
{
protected DataCoreType dataCoreBackup;
public virtual void BeginEdit() {
Type t = typeof(T);
// get a the properties with the attribute
var props = t.GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(DataCoreMemberAttribute)));
// backup needs to be boxed because it's a struct
object boxedBackup = this.dataCoreBackup;
foreach (var prop in props) {
foreach (CustomAttributeData attribData in prop.GetCustomAttributesData()) {
if (attribData.Constructor.DeclaringType == typeof(DataCoreMemberAttribute)) {
object origValue = prop.GetValue(this);
FieldInfo field = boxedBackup.GetType().GetField(attribData.NamedArguments[0].TypedValue.Value.ToString());
field.SetValue(boxedBackup, origValue);
}
}
}
this.dataCoreBackup = (DataCoreType)boxedBackup;
IsEditing = true;
}
... and now I can get INotifiyPropertyChanged and IEditbaleObject handled in an abstract class so I don't have to write a bunch of plumbing in each specific model that I'm going to use.
Hopefully someone else can find this useful.

XAML serialization of public get/private set properties

I'm using System.Xaml.XamlServices.Save method to serialize an object which has properties with public getters/private setters and by design these properties are ignored. I tried to implement advice of how to override default XAML bindings and get private properties serialized, but it doesn't work for some reason - those properties are still ignored. Could anyone point out what's wrong:
public class CustomXamlSchemaContext : XamlSchemaContext
{
protected override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
{
var type = base.GetXamlType(xamlNamespace, name, typeArguments);
return new CustomXamlType(type.UnderlyingType, type.SchemaContext, type.Invoker);
}
}
public class CustomXamlType : XamlType
{
public CustomXamlType(Type underlyingType, XamlSchemaContext schemaContext, XamlTypeInvoker invoker) : base(underlyingType, schemaContext, invoker)
{
}
protected override bool LookupIsConstructible()
{
return true;
}
protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
{
var member = base.LookupMember(name, skipReadOnlyCheck);
return new CustomXamlMember(member.Name, member.DeclaringType, member.IsAttachable);
}
}
public class CustomXamlMember : XamlMember
{
public CustomXamlMember(string name, XamlType declaringType, bool isAttachable) : base(name, declaringType, isAttachable)
{
}
protected override bool LookupIsReadOnly()
{
return false;
}
}
public static string Save(object instance)
{
var stringWriter1 = new StringWriter(CultureInfo.CurrentCulture);
var stringWriter2 = stringWriter1;
var settings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };
using (var writer = XmlWriter.Create(stringWriter2, settings))
{
Save(writer, instance);
}
return stringWriter1.ToString();
}
public static void Save(XmlWriter writer, object instance)
{
if (writer == null)
throw new ArgumentNullException("writer");
using (var xamlXmlWriter = new XamlXmlWriter(writer, new CustomXamlSchemaContext()))
{
XamlServices.Save(xamlXmlWriter, instance);
}
}
Having above infrastructure code and a class
public class Class1
{
public string Property1 { get; private set; }
public string Property2 { get; set; }
public DateTime AddedProperty { get; set; }
}
and serializing an instance of this class with
var obj = new Class1 { Property1 = "value1", Property2 = "value2" };
var objString = Save(obj);
I get the result
<Class1 AddedProperty="0001-01-01" Property2="value2" xmlns="clr-namespace:TestNamespace;assembly=Tests" />
where there is no entry for Property1.
What's even more interesting, that none of the overloads are called during serialization.
It turned out couple tweaks to my initial code solves the problem. Here's final solution:
private class CustomXamlSchemaContext : XamlSchemaContext
{
public override XamlType GetXamlType(Type type)
{
var xamlType = base.GetXamlType(type);
return new CustomXamlType(xamlType.UnderlyingType, xamlType.SchemaContext, xamlType.Invoker);
}
}
private class CustomXamlType : XamlType
{
public CustomXamlType(Type underlyingType, XamlSchemaContext schemaContext, XamlTypeInvoker invoker)
: base(underlyingType, schemaContext, invoker)
{
}
protected override bool LookupIsConstructible()
{
return true;
}
protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
{
var member = base.LookupMember(name, skipReadOnlyCheck);
return member == null ? null : new CustomXamlMember((PropertyInfo)member.UnderlyingMember, SchemaContext, member.Invoker);
}
protected override IEnumerable<XamlMember> LookupAllMembers()
{
foreach (var member in base.LookupAllMembers())
{
var value = new CustomXamlMember((PropertyInfo)member.UnderlyingMember, SchemaContext, member.Invoker);
yield return value;
}
}
protected override bool LookupIsPublic()
{
return true;
}
}
private class CustomXamlMember : XamlMember
{
public CustomXamlMember(PropertyInfo propertyInfo, XamlSchemaContext schemaContext, XamlMemberInvoker invoker)
: base(propertyInfo, schemaContext, invoker)
{
}
protected override bool LookupIsReadOnly()
{
return false;
}
protected override bool LookupIsWritePublic()
{
return true;
}
}
This customization allows to serialize/deserialize properties with public getter and public/internal/protected/private setters. It ignores all the other properties. It also serializes instances of internal classes.
The problem here is that you are attempting to write readonly and private properties.
According to the XAML standard the only readonly properties that are syntactically correct are for List, Dictionary and static members:
3.3.1.6. Only List, Dictionary, or Static Members may be Read-only
If neither [value type][is list] nor [value type][is dictionary], nor [is static] is True, [is read only] MUST be False.
Have a look here for MSDN syntax detail.
And the standard itself can be downloaded here.
You'll also note that only public properties have any relevance here (from msdn linked above):
In order to be set through attribute syntax, a property must be public and must be writeable. The value of the property in the backing type system must be a value type, or must be a reference type that can be instantiated or referenced by a XAML processor when accessing the relevant backing type.
For WPF XAML events, the event that is referenced as the attribute
name must be public and have a public delegate.
The property or event must be a member of the class or structure that
is instantiated by the containing object element.
and if you think about it you can see why.
The whole C# standard is really built around using classes that interact by using public properties and methods. By doing so other classes don't need to know what resides within a class beyond them. Each class can be treated as a black box where the public properties and methods are the class's interface to other code.
Here's an informative blog regarding XAML serialization.
Personally I would ask myself why I need to serialize/deserialize private member properties.
I'm not quite sure what is wrong with the above code, but if you like I can provide an alternative.
This is kind of a hacky way to do it, but it works (tested it). First, you can throw away all that custom XAML stuff. Then, just change your Class1 to be:
public class Class1
{
private string _Property1;
public string Property2 { get; set; }
public DateTime AddedProperty { get; set; }
public Class1()
{
}
public Class1(string prop1, string prop2)
{
_Property1 = prop1;
Property2 = prop2;
}
public string Property1
{
get { return _Property1; }
set { }
}
}
While the set accessor is accessible, it doesn't do anything so in effect this is the same as a public getter/private setter setup. Proper documentation will also help if someone else needs to use your Class1 and is wondering why the 'set' isn't working for Property1.
This could be a plan B in case no one posts a fix for your above code.
Update: If you need to deserialize the object as well, you could create another object that acts as a go-between for your Class1 and the serialization process. The whole setup would look like this:
public class Class1
{
public string Property1 { get; private set; }
public string Property2 { get; set; }
public DateTime AddedProperty { get; set; }
public Class1()
{
}
public Class1(string prop1, string prop2) : this()
{
Property1 = prop1;
Property2 = prop2;
}
public Class1(Class1DTO dto)
{
Property1 = dto.Property1;
}
public Class1DTO CreateDTO()
{
return new Class1DTO
{
AddedProperty = AddedProperty,
Property1 = Property1,
Property2 = Property2
};
}
}
public class Class1DTO
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public DateTime AddedProperty { get; set; }
}
The whole serialization/deserialization process would be like this:
var obj = new Class1("value1", "value2");
var dto = obj.CreateDTO();
var objString = Save(dto);
using (var stringReader = new StringReader(objString))
{
using (var reader = new XamlXmlReader(stringReader))
{
var deserializedDTO = XamlServices.Load(reader);
var originalObj = new Class1(dto);
}
}
You can then change access modifiers to fine tune the amount of access other people would have on your whole setup (you could create static Serialize/Deserialize methods on your Class1 type and push the Class1DTO type into a private nested class so people can't access it etc.).

Binding to interface and displaying properties in base interface

This question (along with its answer) explains why you can't easily bind a DataGridView to an interface type and get columns for properties inherited from a base interface.
The suggested solution is to implement a custom TypeConverter. My attempt is below. However, creating a DataSource and DataGridView bound to ICamel still only results in one column (Humps). I don't think that my converter is being used by .NET to decide which properties it can see for ICamel. What am I doing wrong?
[TypeConverter(typeof(MyConverter))]
public interface IAnimal
{
string Name { get; set; }
int Legs { get; set; }
}
[TypeConverter(typeof(MyConverter))]
public interface ICamel : IAnimal
{
int Humps { get; set; }
}
public class MyConverter : TypeConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
if(value is Type && (Type)value == typeof(ICamel))
{
List<PropertyDescriptor> propertyDescriptors = new List<PropertyDescriptor>();
foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(typeof(ICamel)))
{
propertyDescriptors.Add(pd);
}
foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(typeof(IAnimal)))
{
propertyDescriptors.Add(pd);
}
return new PropertyDescriptorCollection(propertyDescriptors.ToArray());
}
return base.GetProperties(context, value, attributes);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
}
DataGridView does not use TypeConverter; PropertyGrid uses TypeConverter.
If it relates to list-controls like DataGridView, then the other answer is wrong.
To provide custom properties on a list, you need one of:
ITypedList on the data-source
TypeDescriptionProvider on the type
Both are non-trivial.
My Workaround happens in the binding of the dgv.
I do need that the base interfaces and the inheriting interfaces remain in the same structure, just because i do other things width the final concerete class, not only show the data on a DataGridView. So, for example:
interface IGenericPerson
{
int ID { get; set; }
string Name { get; set; }
}
interface IOperator : IGenericPerson
{
bool IsAdmin { get; set; }
}
the concrete class:
class Operator : IOperator
{
public Operator(){}
public Operator(int id, string name, bool isAdmin)
{
this.ID = id;
this.Name = name;
thsi.IsAdmin = isAdmin;
}
public int ID { get; set; }
public string name { get; set; }
public bool IsAdmin { get; set; }
}
and in a Gateway Class:
public IList<IOperator> GetOperators()
{
IList<IOperator> list = new List<IOperator>();
list.add(new Operator(112, "Mark Twain", false);
list.add(new Operator(112, "Charles Manson", false);
list.add(new Operator(112, "Richard Nixon", true);
return list;
}
Now, if i try to bind a datagridView like this:
Gateway gt = new Gateway();
dgv.DataSource = gt.GetOperators();
I get a DataGridView with the only bool IsAdmin column from the IOperator Interface, not the ID, neither the Name propertys from its base interface.
but if I do this:
Gateway gt = new Gateway();
IList<IOperator> list = gt.GetOperators();
IList<Operator> ds = new List<Operator>();
foreach(IOperator op in list)
ds.add((Operator)op);
dgv.DataSource = ds;
Everything works in the right way.
In this way i don't need to change the structure of the intarfaces chain, useful for other purposes, and only qhen displaying data i just insert the snippet above.
My Suggestion would be to create a Interface that "reimplements" the propertys you want:
Let's say you have two interfaces:
public interface IHasName1
{
String Name1 { get; set; }
}
public interface IHasName2 : IHasName1
{
String Name2 { get; set; }
}
And a class that implements IHasName2:
public class HasTwoNames : IHasName2
{
#region IHasName1 Member
public string Name1 { get; set; }
#endregion
#region IHasName2 Member
public string Name2 {get; set; }
#endregion
}
Now, thx for figuring that out btw., if you have a List with objects of concrete type HasTwoNames and you bind that list to a dgv, it only displays the member (Name2) of IHasName2.
A "workaround" is to create a new interface "IHasEverything" that inherits from IHasName2 and therefore from IHasName1 and reimplements the Propertys you need in your binding (you can do that with the new statement
public interface IHasEverything : IHasName2
{
new String Name1 { get; set; }
new String Name2 { get; set; }
}
Now your concrete class "HasTwoNames" needs to implement IHasEverything, too:
public class HasTwoNames : IHasName2, IHasEverything
{
...
}
You can bind this List to a datagridview:
public List<IHasEverything> elements = new List<IHasEverything> {
new HasTwoNames { Name1 = "Name1", Name2 = "Name2"},
new HasTwoNames { Name1 = "Name3", Name2 = "Name4"},
};
I know that this is just a workaround and only possible if you can modify the implementing class. But it works.
(If you remove a property from IHasName2, the code will still compile but you get a warning that IHasEverything does not need the new keyword.

Categories

Resources