I am passing information between a SQL database and a PLC using 3rd party OPC libraries.
There are essentially two transactions.
Information passed from the PLC to the SQL server is statically typed. Very specific data is captured by the PLC and passed to the SQL database.
Information passed from the SQL server to the PLC is dynamically typed and may be limited to a single property or hundreds.
ITransaction.cs
public interface ITransaction : INotifyPropertyChanged
{
short Response { get; set; }
bool Request { get; set; }
void Reset();
}
BaseTransaction.cs
internal abstract class BaseTransaction<T> : IDisposable
where T : class, INotifyPropertyChanged
{
private T _opcClient;
protected T OpcClient
{
get { return _opcClient; }
set
{
if (_opcClient != value)
{
OnOpcClientChanging();
_opcClient = value;
OnOpcClientChanged();
}
}
}
protected abstract void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e);
private void OnOpcClientChanged()
{
if (_opcClient != null)
{
_opcClient.PropertyChanged += OnOpcClientPropertyChanged;
OpcManager = new OpcManager(_opcClient);
}
}
private void OnOpcClientChanging()
{
if (_opcClient != null)
_opcClient.PropertyChanged -= OnOpcClientPropertyChanged;
}
}
StaticTransaction.cs
internal abstract class StaticTransaction<T> : BaseTransaction<T>
where T : class, ITransaction, new()
{
public StaticTransaction()
{
OpcClient = new T();
}
protected override void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Response":
ProcessResponse(OpcClient.Response);
break;
case "Request":
ProcessRequest(OpcClient.Request);
break;
}
}
}
DynamicTransaction.cs
internal abstract class DynamicTransaction : BaseTransaction<ExpandoObject>
{
protected new dynamic OpcClient
{
get { return base.OpcClient as dynamic; }
}
public DynamicTransaction()
{
dynamic opcClient = new ExpandoObject();
opcClient.Request = false;
opcClient.Response = 0;
// Access database, use IDictionary interface to add properties to ExpandoObject.
opcClient.Reset = new Action(Reset);
base.OpcClient = opcClient;
}
protected override void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Response":
ProcessResponse(OpcClient.Response);
break;
case "Request":
ProcessRequest(OpcClient.Request);
break;
}
}
private void Reset()
{
// Use IDictionary interface to reset dynamic properties to defaults.
OpcClient.Request = false;
OpcClient.Response = 0;
}
}
As shown both StaticTransaction and DynamicTransaction have identical implementations of OnOpcClientPropertyChanged among other methods not shown. I would like to bring OnOpcClientPropertyChanged and the other methods into the base class but am prevented from doing so because the base class is unaware of the Response and Request properties found in the OpcClient. Can I bring the interface ITransaction into the base class somehow and still accommodate the dynamic implementation?
You can subclass DynamicObject (which acts just like ExpandoObject) and make your own version that implements ITransaction. This lets you move the ITransaction constraint up to the base class.
BaseTransaction.cs
internal abstract class BaseTransaction<T> : IDisposable where T : class, ITransaction
{
private T _opcClient;
protected T OpcClient
{
get { return _opcClient; }
set
{
if (_opcClient != value)
{
OnOpcClientChanging();
_opcClient = value;
OnOpcClientChanged();
}
}
}
private void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "Response":
ProcessResponse(OpcClient.Response);
break;
case "Request":
ProcessRequest(OpcClient.Request);
break;
}
}
protected abstract void ProcessResponse(short opcClientResponse);
protected abstract void ProcessRequest(bool opcClientRequest);
private void OnOpcClientChanged()
{
if (_opcClient != null)
{
_opcClient.PropertyChanged += OnOpcClientPropertyChanged;
OpcManager = new OpcManager(_opcClient);
}
}
private void OnOpcClientChanging()
{
if (_opcClient != null)
_opcClient.PropertyChanged -= OnOpcClientPropertyChanged;
}
}
StaticTransaction.cs
internal abstract class StaticTransaction<T> : BaseTransaction<T>
where T : class, ITransaction, new()
{
public StaticTransaction()
{
OpcClient = new T();
}
}
DynamicTransactionObject.cs
internal class DynamicTransactionObject : DynamicObject, ITransaction, IDictionary<string, object>
{
private readonly Dictionary<string, object> _data = new Dictionary<string, object>();
public DynamicTransactionObject()
{
//Set initial default values for the two properties to populate the entries in the dictionary.
_data[nameof(Response)] = default(short);
_data[nameof(Request)] = default(bool);
}
public short Response
{
get
{
return (short)_data[nameof(Response)];
}
set
{
if (Response.Equals(value))
return;
_data[nameof(Response)] = value;
OnPropertyChanged();
}
}
public bool Request
{
get
{
return (bool)_data[nameof(Request)];
}
set
{
if (Request.Equals(value))
return;
_data[nameof(Request)] = value;
OnPropertyChanged();
}
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return _data.Keys;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return _data.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
object oldValue;
_data.TryGetValue(binder.Name, out oldValue)
_data[binder.Name] = value;
if(!Object.Equals(oldValue, value)
OnPropertyChanged(binder.Name);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#region IDictionary<string,object> members
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_data).GetEnumerator();
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
((ICollection<KeyValuePair<string, object>>)_data).Add(item);
}
void ICollection<KeyValuePair<string, object>>.Clear()
{
_data.Clear();
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
return ((ICollection<KeyValuePair<string, object>>)_data).Contains(item);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<string, object>>)_data).CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
return ((ICollection<KeyValuePair<string, object>>)_data).Remove(item);
}
int ICollection<KeyValuePair<string, object>>.Count
{
get { return _data.Count; }
}
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get { return ((ICollection<KeyValuePair<string, object>>)_data).IsReadOnly; }
}
bool IDictionary<string, object>.ContainsKey(string key)
{
return _data.ContainsKey(key);
}
void IDictionary<string, object>.Add(string key, object value)
{
_data.Add(key, value);
}
bool IDictionary<string, object>.Remove(string key)
{
return _data.Remove(key);
}
bool IDictionary<string, object>.TryGetValue(string key, out object value)
{
return _data.TryGetValue(key, out value);
}
object IDictionary<string, object>.this[string key]
{
get { return _data[key]; }
set { _data[key] = value; }
}
ICollection<string> IDictionary<string, object>.Keys
{
get { return _data.Keys; }
}
ICollection<object> IDictionary<string, object>.Values
{
get { return _data.Values; }
}
#endregion
}
DynamicTransaction.cs
internal abstract class DynamicTransaction : BaseTransaction<DynamicTransactionObject>
{
protected new dynamic OpcClient
{
get { return base.OpcClient as dynamic; }
}
public DynamicTransaction()
{
var opcClient = new DynamicTransactionObject();
// Access database, use IDictionary<string,object> interface to add properties to DynamicObject.
base.OpcClient = opcClient;
}
}
Related
I have the following protoc3 message:
message LocalizedString {
map<string, string> translations = 1
}
When compiled into C#, I get the following autogenerated code:
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
namespace PKIo {
/// <summary>Holder for reflection information generated from io/common/localization.proto</summary>
public static partial class LocalizationReflection {
#region Descriptor
/// <summary>File descriptor for io/common/localization.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static LocalizationReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"Chxpby9jb21tb24vbG9jYWxpemF0aW9uLnByb3RvEgJpbyKDAQoPTG9jYWxp",
"emVkU3RyaW5nEjsKDHRyYW5zbGF0aW9ucxgBIAMoCzIlLmlvLkxvY2FsaXpl",
"ZFN0cmluZy5UcmFuc2xhdGlvbnNFbnRyeRozChFUcmFuc2xhdGlvbnNFbnRy",
"eRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQi1aJHN0YXNoLnBh",
"c3NraXQuY29tL2lvL21vZGVsL3Nkay9nby9pb6oCBFBLSW9iBnByb3RvMw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::PKIo.LocalizedString), global::PKIo.LocalizedString.Parser, new[]{ "Translations" }, null, null, new pbr::GeneratedClrTypeInfo[] { null, })
}));
}
#endregion
}
#region Messages
/// <summary>
/// Localized strings are optionally used to provide translated values for each of supported language.
/// </summary>
public sealed partial class LocalizedString : pb::IMessage<LocalizedString> {
private static readonly pb::MessageParser<LocalizedString> _parser = new pb::MessageParser<LocalizedString>(() => new LocalizedString());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public static pb::MessageParser<LocalizedString> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public static pbr::MessageDescriptor Descriptor {
get { return global::PKIo.LocalizationReflection.Descriptor.MessageTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString(LocalizedString other) : this() {
translations_ = other.translations_.Clone();
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public LocalizedString Clone() {
return new LocalizedString(this);
}
/// <summary>Field number for the "translations" field.</summary>
public const int TranslationsFieldNumber = 1;
private static readonly pbc::MapField<string, string>.Codec _map_translations_codec
= new pbc::MapField<string, string>.Codec(pb::FieldCodec.ForString(10), pb::FieldCodec.ForString(18), 10);
private readonly pbc::MapField<string, string> translations_ = new pbc::MapField<string, string>();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public pbc::MapField<string, string> Translations {
get { return translations_; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override bool Equals(object other) {
return Equals(other as LocalizedString);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public bool Equals(LocalizedString other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (!Translations.Equals(other.Translations)) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override int GetHashCode() {
int hash = 1;
hash ^= Translations.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void WriteTo(pb::CodedOutputStream output) {
translations_.WriteTo(output, _map_translations_codec);
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public int CalculateSize() {
int size = 0;
size += translations_.CalculateSize(_map_translations_codec);
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(LocalizedString other) {
if (other == null) {
return;
}
translations_.Add(other.translations_);
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 10: {
translations_.AddEntriesFrom(input, _map_translations_codec);
break;
}
}
}
}
}
#endregion
}
I have another message that has a LocalizedString property that I am using for as an input to an rpc function, but I cannot figure out how to set the properties for Translations. In the generated code, Translations is marked as read only.
How does one construct a protobuf message object that contains a map like this in C#?
So after digging through the documentation, I found that Google.Protobuf.Collections.MapField< TKey, TValue > has getters and setters.
The values can by either passing a key, value pair or a dictionary to the Add method of the map.
var localizedName = new LocalizedString();
localizedName.Translations.Add(new Dictionary<string, string>(){
{"ES","Hola"},
{"FR","Bonjour"},
{"JA","こんにちは"},
{"TH","สวัสดี"},
});
localizedName.Translations.Add("ZH_HANS", "你好");
To retrive a value there is a TryGetValue method:
var translation = "";
localizedName.Translations.TryGetValue("TH", out translation);
// translation == "สวัสดี"
In the method MergeFrom, it is giving option to add values in translations in line translations_.Add(other.translations_); :
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(LocalizedString other) {
if (other == null) {
return;
}
translations_.Add(other.translations_);
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
To answer "How does one construct a protobuf message object that contains a map like this in C#", Please refer:
https://developers.google.com/protocol-buffers/docs/reference/csharp-generated
https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/collections/map-field-t-key-t-value-
Also there is pull request in github that provides "Proto3 map support for C#" to get the idea how it is implemented:
https://github.com/protocolbuffers/protobuf/pull/543
Also refer these to check if it helps:
Dictionary in protocol buffers
How does Protobuf-net support for Dictionary/KeyValuePair works?
(I really tried to come up with a better title, feel free to edit)
Suppose I have a generic event handler interface and implementations:
public interface IEventHandler<T>
{
void HandleEvent(T t);
}
public class SlowButAccurateEventHandler<T> : IEventHandler<T>
{
// To emphasize that the implementation depends on T
private void SomeHelperClass<T> helperClass;
public void HandleEvent(T t) { ... }
}
public class FastEventHandler<T> : IEventHandler<T>
{
// To emphasize that the implementation depends on T
private void SomeHelperClass<T> helperClass;
public void HandleEvent(T t) { ... }
}
and another class which I'd like to hold instances of EventHandlers, but cannot have generic methods since it's a WCF service:
public class MyService : MyContract
{
// Pseudo (doesn't compile)
private Dictionary<Type, IEventHandler<T>> eventHandlers;
public MyService()
{
// More pseudo...
eventHandlers = new Dictionary<Type, IEventHandler<T>>()
{
{ typeof(string), new SlowButAccurateEventHandler<string>() },
{ typeof(int), new FastEventHandler<int>() },
};
}
public void RouteToEventHandler(object userEvent)
{
var handler = eventHandlers[typeof(userEvent))];
handler.HandleEvent(userEvent); // I realize that userEvent needs to be converted here
}
}
So basically, I have some service (MyService) that I'd like to hold IEventHandlers and dispatch the correct handler when some event arrives.
To do that, I'd like to keep a dictionary that holds a mapping between the CLR type and the suitable IEventHandler. Is that possible?
Another implementation, however I would stay at my previous answer:
public interface IEventHandler
{
void HandleEvent(object value);
}
public interface IEventHandler<T> : IEventHandler
{
void HandleEvent(T value);
}
public abstract class EventHandler<T> : IEventHandler<T>
{
public void HandleEvent(object value)
{
if (value == null || !Type.Equals(value.GetType(), typeof(T)))
{
return;
}
HandleEvent(Convert(value));
}
private T Convert(object value)
{
try
{
return (T)value;
}
catch
{
return default(T);
}
}
public abstract void HandleEvent(T value);
}
public class FastEventHandler<T> : EventHandler<T>
{
public override void HandleEvent(T value)
{
throw new NotImplementedException();
}
}
In the constructor you can initialize the event handlers:
var handlers = new Dictionary<Type, IEventHandler>()
{
{ typeof(string), new FastEventHandler<string>() },
{ typeof(int), new FastEventHandler<int>() }
};
Then:
public void RouteToEventHandler(object userEvent)
{
if (userEvent == null)
{
return;
}
var handler = handlers[userEvent.GetType()];
handler.HandleEvent(userEvent);
}
You should define your interface like this:
public interface IEventHandler<T>
{
void HandleEvent(object t);
}
And then in the implementation:
public class FastEventHandler<T> : IEventHandler<T>
{
// To emphasize that the implementation depends on T
private void SomeHelperClass<T> helperClass;
public void HandleEvent(object t)
{
if (t == null || !Type.Equals(t.GetType(), typeof(T)))
{
// We cannot handle the event.
return;
}
T typedValue = Convert(t);
// Here comes the rest of handling process.
}
private T Convert(object value)
{
try
{
return (T)value;
}
catch
{
return default(T);
}
}
}
One way of course would be to store the handlers inside Dictionary<Type, object> and then use reflection to call the method in interest. In case you are interested in single method (like in your example), you can build and store a delegate call to that method instead, like this:
public class MyService : MyContract
{
private Dictionary<Type, Action<object>> eventHandlers;
static Action<object> GetHandler<T>(IEventHandler<T> handler)
{
var parameter = Expression.Parameter(typeof(object), "t");
var body = Expression.Call(
Expression.Constant(handler),
"HandleEvent", null,
Expression.Convert(parameter, typeof(T)));
return Expression.Lambda<Action<object>>(body, parameter).Compile();
}
public MyService()
{
eventHandlers = new Dictionary<Type, Action<object>>()
{
{ typeof(string), GetHandler(new SlowButAccurateEventHandler<string>()) },
{ typeof(int), GetHandler(new FastEventHandler<int>()) },
};
}
public void RouteToEventHandler(object userEvent)
{
Action<object> handler;
if (eventHandlers.TryGetValue(userEvent.GetType(), out handler))
handler(userEvent);
}
}
I have a custom ConfigurationElementCollection called EnvironmentElementCollection to configure my different environments within a class library I'm writing. I have a static property in a class to get a collection of all the environments that are listed in that section. There is a property of the contained items (EnvironmentElement) that indicates the environment is a "login" environment. My goal is to be able to filter the collection with Linq to get only the "login" environments, but the Linq query always returns null. I implemented IEnumerable using this tutorial, but I can't figure out what I'm doing wrong.
Configuration Classes
public class EnvironmentSection : ConfigurationSection {
private static ConfigurationPropertyCollection properties;
private static ConfigurationProperty propEnvironments;
static EnvironmentSection() {
propEnvironments = new ConfigurationProperty(null, typeof(EnvironmentElementCollection), null, ConfigurationPropertyOptions.IsDefaultCollection);
properties = new ConfigurationPropertyCollection { propEnvironments };
}
protected override ConfigurationPropertyCollection Properties {
get {
return properties;
}
}
public EnvironmentElementCollection Environments {
get {
return this[propEnvironments] as EnvironmentElementCollection;
}
}
}
public class EnvironmentElementCollection : ConfigurationElementCollection, IEnumerable<EnvironmentElement> {
private static ConfigurationPropertyCollection properties;
public EnvironmentElementCollection() {
properties = new ConfigurationPropertyCollection();
}
protected override ConfigurationPropertyCollection Properties {
get {
return properties;
}
}
public override ConfigurationElementCollectionType CollectionType {
get {
return ConfigurationElementCollectionType.BasicMap;
}
}
public EnvironmentElement this[int index] {
get {
return (EnvironmentElement)base.BaseGet(index);
}
}
public EnvironmentElement this[string name] {
get {
return (EnvironmentElement)base.BaseGet(name);
}
}
protected override string ElementName {
get {
return "add";
}
}
protected override ConfigurationElement CreateNewElement() {
return new EnvironmentElement();
}
protected override object GetElementKey(ConfigurationElement element) {
var elm = element as EnvironmentElement;
if (elm == null) throw new ArgumentNullException();
return elm.Name;
}
//for implementing IEnumerable
public new IEnumerator<EnvironmentElement> GetEnumerator() {
int count = base.Count;
for (int i = 0; i < count; i++) {
yield return (EnvironmentElement)base.BaseGet(i);
}
}
}
public class EnvironmentElement : ConfigurationElement {
private static ConfigurationPropertyCollection properties;
private static ConfigurationProperty propLoginEnabled;
public EnvironmentElement() {
propLoginEnabled = new ConfigurationProperty("loginEnabled", typeof(bool), null, ConfigurationPropertyOptions.None);
properties = new ConfigurationPropertyCollection { propLoginEnabled };
}
[ConfigurationProperty("loginEnabled")]
public bool LoginEnabled {
get {
return (bool)base[propLoginEnabled];
}
}
}
And here is my class to get environments:
public class Environment {
//Works fine to get all environments
public static EnvironmentElementCollection All {
get {
EnvironmentSection dls = ConfigurationManager.GetSection("environments") as EnvironmentSection;
return dls.Environments;
}
}
//Returns null
public static EnvironmentElementCollection Login {
get {
EnvironmentSection dls = ConfigurationManager.GetSection("environments") as EnvironmentSection;
EnvironmentElementCollection envs = dls.Environments.Where<EnvironmentElement>(e => e.LoginEnabled == true) as EnvironmentElementCollection;
return envs;
}
}
}
So I can't tell which part is breaking, if my implementation of IEnumerable is bad, or my Linq query, or something else.
I have class with multiple properties. Sometimes a property (A) may be edited in a propertygrid. But sometimes property A may not be edited. This depends on a value of another property.
How can I do this?
EDIT:
I am sorry, I forget to mention that I want this in design-time.
Runtime property models are an advanced topic. For PropertyGrid the easiest route would be to write a TypeConverter, inheriting from ExpandableObjectConverter. Override GetProperties, and swap the property in question for a custom one.
Writing a PropertyDescriptor from scratch is a chore; but in this case you mainly just need to chain ("decorator") all the methods to the original (reflective) descriptor. And just override IsReadOnly to return the bool you want.
By no means trivial, but achievable.
using System;
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.Run(new Form { Text = "read only",
Controls = {
new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Foo { IsBarEditable = false }}
}
});
Application.Run(new Form { Text = "read write",
Controls = {
new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Foo { IsBarEditable = true }}
}
});
}
}
[TypeConverter(typeof(Foo.FooConverter))]
class Foo
{
[Browsable(false)]
public bool IsBarEditable { get; set; }
public string Bar { get; set; }
private class FooConverter : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var props = base.GetProperties(context, value, attributes);
if (!((Foo)value).IsBarEditable)
{ // swap it
PropertyDescriptor[] arr = new PropertyDescriptor[props.Count];
props.CopyTo(arr, 0);
for (int i = 0; i < arr.Length; i++)
{
if (arr[i].Name == "Bar") arr[i] = new ReadOnlyPropertyDescriptor(arr[i]);
}
props = new PropertyDescriptorCollection(arr);
}
return props;
}
}
}
class ReadOnlyPropertyDescriptor : ChainedPropertyDescriptor
{
public ReadOnlyPropertyDescriptor(PropertyDescriptor tail) : base(tail) { }
public override bool IsReadOnly
{
get
{
return true;
}
}
public override void SetValue(object component, object value)
{
throw new InvalidOperationException();
}
}
abstract class ChainedPropertyDescriptor : PropertyDescriptor
{
private readonly PropertyDescriptor tail;
protected PropertyDescriptor Tail { get {return tail; } }
public ChainedPropertyDescriptor(PropertyDescriptor tail) : base(tail)
{
if (tail == null) throw new ArgumentNullException("tail");
this.tail = tail;
}
public override void AddValueChanged(object component, System.EventHandler handler)
{
tail.AddValueChanged(component, handler);
}
public override AttributeCollection Attributes
{
get
{
return tail.Attributes;
}
}
public override bool CanResetValue(object component)
{
return tail.CanResetValue(component);
}
public override string Category
{
get
{
return tail.Category;
}
}
public override Type ComponentType
{
get { return tail.ComponentType; }
}
public override TypeConverter Converter
{
get
{
return tail.Converter;
}
}
public override string Description
{
get
{
return tail.Description;
}
}
public override bool DesignTimeOnly
{
get
{
return tail.DesignTimeOnly;
}
}
public override string DisplayName
{
get
{
return tail.DisplayName;
}
}
public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter)
{
return tail.GetChildProperties(instance, filter);
}
public override object GetEditor(Type editorBaseType)
{
return tail.GetEditor(editorBaseType);
}
public override object GetValue(object component)
{
return tail.GetValue(component);
}
public override bool IsBrowsable
{
get
{
return tail.IsBrowsable;
}
}
public override bool IsLocalizable
{
get
{
return tail.IsLocalizable;
}
}
public override bool IsReadOnly
{
get { return tail.IsReadOnly; }
}
public override string Name
{
get
{
return tail.Name;
}
}
public override Type PropertyType
{
get { return tail.PropertyType; }
}
public override void RemoveValueChanged(object component, EventHandler handler)
{
tail.RemoveValueChanged(component, handler);
}
public override void ResetValue(object component)
{
tail.ResetValue(component);
}
public override void SetValue(object component, object value)
{
tail.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return tail.ShouldSerializeValue(component);
}
public override bool SupportsChangeEvents
{
get
{
return tail.SupportsChangeEvents;
}
}
}
This answer assumes you are talking about WinForms. If you would like to change one property's readonly state based on another, you will need to have your object implement ICustomTypeDescriptor. This isn't a simple thing to do, but it will give you lots of flexibility about how your class is displayed in the propertygrid.
I've offered a similar solution in the past via this stack solution. It makes use of a custom property, and conditionally ignores an attempt to change at design-time vs run-time, but I'm sure could be altered in the SETter by applying your own "criteria" to allow it being changed...
Well BindingList and ObservableCollection work great to keep data updated and to notify when one of it's objects has changed. However, when notifying a property is about to change, I think these options are not very good.
What I have to do right now to solve this (and I warn this is not elegant AT ALL), is to implement INotifyPropertyChanging on the list's type object and then tie that to the object that holds the list PropertyChanging event, or something like the following:
// this object will be the type of the BindingList
public class SomeObject : INotifyPropertyChanging, INotifyPropertyChanged
{
private int _intProperty = 0;
private string _strProperty = String.Empty;
public int IntProperty
{
get { return this._intProperty; }
set
{
if (this._intProperty != value)
{
NotifyPropertyChanging("IntProperty");
this._intProperty = value;
NotifyPropertyChanged("IntProperty");
}
}
}
public string StrProperty
{
get { return this._strProperty; }
set
{
if (this._strProperty != value)
{
NotifyPropertyChanging("StrProperty");
this._strProperty = value;
NotifyPropertyChanged("StrProperty");
}
}
}
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void NotifyPropertyChanging(string propertyName)
{
if (this.PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ObjectThatHoldsTheList : INotifyPropertyChanging, INotifyPropertyChanged
{
public BindingList<SomeObject> BindingList { get; set; }
public ObjectThatHoldsTheList()
{
this.BindingList = new BindingList<SomeObject>();
}
// this helps notifie Changing and Changed on Add
private void AddItem(SomeObject someObject)
{
// this will tie the PropertyChanging and PropertyChanged events of SomeObject to this object
// so it gets notifies because the BindingList does not notify PropertyCHANGING
someObject.PropertyChanging += new PropertyChangingEventHandler(someObject_PropertyChanging);
someObject.PropertyChanged += new PropertyChangedEventHandler(someObject_PropertyChanged);
this.NotifyPropertyChanging("BindingList");
this.BindingList.Add(someObject);
this.NotifyPropertyChanged("BindingList");
}
// this helps notifies Changing and Changed on Delete
private void DeleteItem(SomeObject someObject)
{
if (this.BindingList.IndexOf(someObject) > 0)
{
// this unlinks the handlers so the garbage collector can clear the objects
someObject.PropertyChanging -= new PropertyChangingEventHandler(someObject_PropertyChanging);
someObject.PropertyChanged -= new PropertyChangedEventHandler(someObject_PropertyChanged);
}
this.NotifyPropertyChanging("BindingList");
this.BindingList.Remove(someObject);
this.NotifyPropertyChanged("BindingList");
}
// this notifies an item in the list is about to change
void someObject_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
NotifyPropertyChanging("BindingList." + e.PropertyName);
}
// this notifies an item in the list has changed
void someObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("BindingList." + e.PropertyName);
}
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void NotifyPropertyChanging(string propertyName)
{
if (this.PropertyChanging != null)
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Sorry, I know this is a lot of code, which takes me back to my main point IT'S A LOT OF CODE to implement this. So my question is, does anyone know a better, shorter, more elegant solution?
Thanks for your time and suggestions.
You can create a wrapper class, that implements ICustomTypeDescriptor. This wrapper will also implement necessary interfaces (such as INotifyPropertyChanging), intercept properties reads/writes on underlying object, and you will be able to call NotifyPropertyChanging() and NotifyPropertyChanged() methods implemented by a wrapper. The data consumers will work with wrapped objects same as they work with original objects.
But implementing such a wrapper will not be easy if you are not an experienced developer.
Here is the possible, yet not finished implementation of such a wrapper. It already supports INotifyPropertyChanged, and it's easy to understand how to implement INotifyPropertyChanging.
public class Wrapper : ICustomTypeDescriptor, INotifyPropertyChanged, IEditableObject, IChangeTracking
{
private bool _isChanged;
public object DataSource { get; set; }
public Wrapper(object dataSource)
{
if (dataSource == null)
throw new ArgumentNullException("dataSource");
DataSource = dataSource;
}
#region ICustomTypeDescriptor Members
public AttributeCollection GetAttributes()
{
return new AttributeCollection(
DataSource.GetType()
.GetCustomAttributes(true)
.OfType<Attribute>()
.ToArray());
}
public string GetClassName()
{
return DataSource.GetType().Name;
}
public string GetComponentName()
{
return DataSource.ToString();
}
public TypeConverter GetConverter()
{
return new TypeConverter();
}
public EventDescriptor GetDefaultEvent()
{
return null;
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
public object GetEditor(Type editorBaseType)
{
return Activator.CreateInstance(editorBaseType);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(DataSource, attributes);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(DataSource);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
private IEnumerable<PropertyDescriptor> _Properties;
public IEnumerable<PropertyDescriptor> Properties
{
get
{
if (_Properties == null)
_Properties = TypeDescriptor.GetProperties(DataSource)
.Cast<PropertyDescriptor>()
.Select(pd => new WrapperPropertyDescriptor(pd) as PropertyDescriptor)
.ToList();
return _Properties;
}
}
public PropertyDescriptorCollection GetProperties()
{
return new PropertyDescriptorCollection(Properties.ToArray());
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion ICustomTypeDescriptor
#region ToString, Equals, GetHashCode
public override string ToString()
{
return DataSource.ToString();
}
public override bool Equals(object obj)
{
var wrapper = obj as Wrapper;
if (wrapper == null)
return base.Equals(obj);
else
return DataSource.Equals(wrapper.DataSource);
}
public override int GetHashCode()
{
return DataSource.GetHashCode();
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (String.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("propertyName");
_isChanged = true;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public IDictionary<string, object> MakeDump()
{
var result = new Dictionary<String, object>();
foreach (var item in Properties)
result[item.Name] = item.GetValue(this);
return result;
}
#region IEditableObject Members
private IDictionary<string, object> LastDump;
public void BeginEdit()
{
LastDump = MakeDump();
}
public void CancelEdit()
{
if (LastDump != null)
{
foreach (var item in Properties)
item.SetValue(this, LastDump[item.Name]);
_isChanged = false;
}
}
public void EndEdit()
{
AcceptChanges();
}
#endregion IEditableObject
#region IChangeTracking
public void AcceptChanges()
{
LastDump = null;
_isChanged = false;
}
public bool IsChanged
{
get { return _isChanged; }
}
#endregion IChangeTracking
}
public class WrapperPropertyDescriptor : PropertyDescriptor
{
private Wrapper _wrapper;
private readonly PropertyDescriptor SourceDescriptor;
public WrapperPropertyDescriptor(PropertyDescriptor sourceDescriptor) :
base(sourceDescriptor)
{
if (sourceDescriptor == null)
throw new ArgumentNullException("sourceDescriptor");
SourceDescriptor = sourceDescriptor;
}
public override Type ComponentType
{
get
{
return SourceDescriptor.ComponentType;
}
}
public override bool IsReadOnly
{
get
{
return SourceDescriptor.IsReadOnly;
}
}
public override Type PropertyType
{
get
{
return SourceDescriptor.PropertyType;
}
}
public override object GetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
var value = SourceDescriptor.GetValue(wrapper.DataSource);
if (value == null)
return value;
var type = value.GetType();
// If value is user class or structure it should
// be wrapped before return.
if (type.Assembly != typeof(String).Assembly)
{
if (typeof(IEnumerable).IsAssignableFrom(type))
throw new NotImplementedException("Here we should construct and return wrapper for collection");
if (_wrapper == null)
_wrapper = new Wrapper(value);
else
_wrapper.DataSource = value;
return _wrapper;
}
return value;
}
public override void SetValue(object component, object value)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
var actualValue = value;
var valueWrapper = value as Wrapper;
if (valueWrapper != null)
actualValue = valueWrapper.DataSource;
// Make dump of data source's previous values
var dump = wrapper.MakeDump();
SourceDescriptor.SetValue(wrapper.DataSource, actualValue);
foreach (var item in wrapper.Properties)
{
var itemValue = item.GetValue(wrapper);
if (!itemValue.Equals(dump[item.Name]))
wrapper.OnPropertyChanged(item.Name);
}
}
public override void ResetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
SourceDescriptor.ResetValue(wrapper.DataSource);
}
public override bool ShouldSerializeValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
return SourceDescriptor.ShouldSerializeValue(wrapper.DataSource);
}
public override bool CanResetValue(object component)
{
var wrapper = component as Wrapper;
if (wrapper == null)
throw new ArgumentException("Unexpected component", "component");
return SourceDescriptor.CanResetValue(wrapper.DataSource);
}
}
Again, this is not a complete version, but it can already be used in simple scenarios. The possible usage can look like this:
IList<Customer> customers = CustomerRepository.GetAllCustomers();
IList<Wrapper> wrappedCustomers = customers.Select(c => new Wrapper(c)).ToList();
/* If you don't like LINQ in the line above you can use foreach to transform
list of Customer object to a list of Wrapper<Customer> objects */
comboBoxCustomers.DataSource = wrappedCustomers;
// or
dataGridViewCustomers.DataSource = wrappedCustomers;
So with one simple line of code you have a collection of objects that support INotifyPropertyChanged, IEditableObject, IChangeTracking interfaces!
Good luck!
This is a classic example for cross-cutting concern, which cries for AOP approach. Aspect Oriented Programming is a paradigm which extends classical OOP and lets you solve problems like "I want all method calls on this object to be logged".
There are several ways to do this in .NET, this is a nice list of most of them:
http://ayende.com/Blog/archive/2007/07/02/7-Approaches-for-AOP-in-.Net.aspx
One of the approaches listed is PostSharp, IL rewriter which let's you do AOP very easily. Here is an example of implementing INotifyPropertyChanged using this tool (another example comes with PostSharp, I think):
http://thetreeknowseverything.net/2009/01/21/auto-implement-inotifypropertychanged-with-aspects/