all groups > dotnet windows forms databinding > june 2007 >
dotnet windows forms databinding :
bind to elements in a NameValueCollection, hashtable, or Dictionary?
I'd like to create an object that has a field derived from one of the objects in the title such that I can bind simple controls (textboxes, etc) to individual elements of the collection such that: 1. The binding can be done at either runtime or designtime 2. If the entry doesn't exist, the binding succeeds and "returns" a null string 3. If the user edits the control text, the entry is created in the field list. Think of it as a collection of random properties of an object that are not all known at design time of that object. This object is actually coming via a xml-string. Make sense? thanks,
Design-time is a pain, as you are unlikely to be able to really trust the contents. Run-time should be OK; I did some tests (code follows) with NameValueCollection, trying to use TypeDescriptionProvider to directly hook into TypeDescriptor, but it didn't want to use it (I didn't look into why). In the end I had to wrap NameValueCollection before it would let it in - hence the code is a little scrappy. In 1.1, you could do the same by having the wrapper implement ICustomTypeDescriptor directly. Re "returns a null string", note that binding works by PropertyDescriptors, and *normally* a missing property returns a null descriptor. You can create a bespoke PropertyDescriptorCollection, and override the Find etc and create an extra descriptor when an unknown property is queried, but I wouldn't recommend it - it isn't what is expected. Marc Code: using System; using System.Collections.Specialized; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows.Forms; using System.Collections; static class Program { static void Main() { NameValueCollection nvc = new NameValueCollection(); nvc["Fred"] = "Wilma"; nvc["Barney"] = "Betty"; WrappedNameValueCollection wrapper = new WrappedNameValueCollection(nvc); PropertyDescriptorCollection props = TypeDescriptor.GetProperties(wrapper); using (Form form = new Form()) using (TextBox tb = new TextBox()) { tb.DataBindings.Add("Text", wrapper, "Fred"); form.DataBindings.Add("Text", wrapper, "Barney"); form.Controls.Add(tb); Application.Run(form); } } } public class WrappedNameValueCollection { private readonly NameValueCollection _parent; internal NameValueCollection Parent { get { return _parent; } } public WrappedNameValueCollection(NameValueCollection parent) { if (parent == null) throw new ArgumentNullException("parent"); _parent = parent; } static WrappedNameValueCollection() { NameValueCollectionTypeDescriptionProvider.Init(); } public int Count { get { return Parent.Count; } } } public sealed class NameValueCollectionTypeDescriptionProvider : TypeDescriptionProvider { static readonly ICustomTypeDescriptor baseDescriptor; static NameValueCollectionTypeDescriptionProvider() { TypeDescriptionProvider provider = TypeDescriptor.GetProvider(typeof(WrappedNameValueCollection)); baseDescriptor = provider.GetTypeDescriptor(typeof(WrappedNameValueCollection)); provider = new NameValueCollectionTypeDescriptionProvider(provider); TypeDescriptor.AddProvider(provider, typeof(WrappedNameValueCollection)); } [MethodImpl(MethodImplOptions.NoInlining)] internal static void Init() { } // NOP to force static ctor private NameValueCollectionTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) { } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { return new NameValueCollectionTypeDescriptor(baseDescriptor, instance as WrappedNameValueCollection); } private sealed class NameValueCollectionTypeDescriptor : CustomTypeDescriptor { private readonly WrappedNameValueCollection collection; public NameValueCollectionTypeDescriptor(ICustomTypeDescriptor parent, WrappedNameValueCollection collection) : base(parent) { this.collection = collection; } public override PropertyDescriptorCollection GetProperties() { if (collection == null || collection.Count == 0) return PropertyDescriptorCollection.Empty; PropertyDescriptor[] props = new PropertyDescriptor[collection.Count]; int index = 0; foreach (string key in collection.Parent.Keys) { props[index++] = new NameValueCollectionPropertyDescriptor(key); } return new PropertyDescriptorCollection(props, true); } public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return GetProperties(); // incomplete; does not respect attributes } } private sealed class NameValueCollectionPropertyDescriptor : PropertyDescriptor { static readonly Attribute[] emptyAttribs = new Attribute[0]; public NameValueCollectionPropertyDescriptor(string name) : base(name, emptyAttribs) { } public override Type PropertyType { get { return typeof(string); } } public override Type ComponentType { get { return typeof(NameValueCollection); } } public override object GetValue(object component) { return ((WrappedNameValueCollection)component).Parent[Name]; } public override void SetValue(object component, object value) { ((WrappedNameValueCollection)component).Parent[Name] = (string)value; } public override bool IsReadOnly { get { return false; } } public override bool CanResetValue(object component) { return false; } public override void ResetValue(object component) { throw new NotSupportedException(); } public override bool ShouldSerializeValue(object component) { return true; } } }
Marc: Thanks! I'll give it a shot. /steveA [quoted text, click to view] Marc Gravell wrote: > Design-time is a pain, as you are unlikely to be able to really trust > the contents. Run-time should be OK; I did some tests (code follows) > with NameValueCollection, trying to use TypeDescriptionProvider to > directly hook into TypeDescriptor, but it didn't want to use it (I > didn't look into why). In the end I had to wrap NameValueCollection > before it would let it in - hence the code is a little scrappy. In > 1.1, you could do the same by having the wrapper implement > ICustomTypeDescriptor directly. > > Re "returns a null string", note that binding works by > PropertyDescriptors, and *normally* a missing property returns a null > descriptor. You can create a bespoke PropertyDescriptorCollection, and > override the Find etc and create an extra descriptor when an unknown > property is queried, but I wouldn't recommend it - it isn't what is > expected. > > Marc > > Code: > > using System; > using System.Collections.Specialized; > using System.ComponentModel; > using System.Runtime.CompilerServices; > using System.Windows.Forms; > using System.Collections; > > static class Program { > static void Main() { > > NameValueCollection nvc = new NameValueCollection(); > nvc["Fred"] = "Wilma"; > nvc["Barney"] = "Betty"; > WrappedNameValueCollection wrapper = new > WrappedNameValueCollection(nvc); > > PropertyDescriptorCollection props = > TypeDescriptor.GetProperties(wrapper); > using (Form form = new Form()) > using (TextBox tb = new TextBox()) { > tb.DataBindings.Add("Text", wrapper, "Fred"); > form.DataBindings.Add("Text", wrapper, "Barney"); > form.Controls.Add(tb); > Application.Run(form); > } > } > } > public class WrappedNameValueCollection { > private readonly NameValueCollection _parent; > internal NameValueCollection Parent { get { return _parent; } } > public WrappedNameValueCollection(NameValueCollection parent) { > if (parent == null) throw new ArgumentNullException("parent"); > _parent = parent; > } > static WrappedNameValueCollection() { > NameValueCollectionTypeDescriptionProvider.Init(); > } > public int Count { get { return Parent.Count; } } > } > public sealed class NameValueCollectionTypeDescriptionProvider : > TypeDescriptionProvider { > static readonly ICustomTypeDescriptor baseDescriptor; > static NameValueCollectionTypeDescriptionProvider() { > TypeDescriptionProvider provider = > TypeDescriptor.GetProvider(typeof(WrappedNameValueCollection)); > baseDescriptor = > provider.GetTypeDescriptor(typeof(WrappedNameValueCollection)); > provider = new > NameValueCollectionTypeDescriptionProvider(provider); > TypeDescriptor.AddProvider(provider, > typeof(WrappedNameValueCollection)); > } > [MethodImpl(MethodImplOptions.NoInlining)] > internal static void Init() { } // NOP to force static ctor > > private > NameValueCollectionTypeDescriptionProvider(TypeDescriptionProvider > parent) : base(parent) { } > > public override ICustomTypeDescriptor GetTypeDescriptor(Type > objectType, object instance) { > return new NameValueCollectionTypeDescriptor(baseDescriptor, > instance as WrappedNameValueCollection); > > } > private sealed class NameValueCollectionTypeDescriptor : > CustomTypeDescriptor { > private readonly WrappedNameValueCollection collection; > public NameValueCollectionTypeDescriptor(ICustomTypeDescriptor > parent, WrappedNameValueCollection collection) : base(parent) { > this.collection = collection; > } > > public override PropertyDescriptorCollection GetProperties() { > if (collection == null || collection.Count == 0) return > PropertyDescriptorCollection.Empty; > PropertyDescriptor[] props = new > PropertyDescriptor[collection.Count]; > int index = 0; > foreach (string key in collection.Parent.Keys) { > props[index++] = new > NameValueCollectionPropertyDescriptor(key); > } > return new PropertyDescriptorCollection(props, true); > } > public override PropertyDescriptorCollection > GetProperties(Attribute[] attributes) { > return GetProperties(); // incomplete; does not respect > attributes > } > } > private sealed class NameValueCollectionPropertyDescriptor : > PropertyDescriptor { > static readonly Attribute[] emptyAttribs = new Attribute[0]; > public NameValueCollectionPropertyDescriptor(string name) : > base(name, emptyAttribs) { } > public override Type PropertyType { > get { return typeof(string); } > } > public override Type ComponentType { > get { return typeof(NameValueCollection); } > } > public override object GetValue(object component) { > return > ((WrappedNameValueCollection)component).Parent[Name]; > } > public override void SetValue(object component, object value) > { > ((WrappedNameValueCollection)component).Parent[Name] = > (string)value; > } > public override bool IsReadOnly { > get { return false; } > } > public override bool CanResetValue(object component) { > return false; > } > public override void ResetValue(object component) { > throw new NotSupportedException(); > } > public override bool ShouldSerializeValue(object component) { > return true; > } > } > } >
Marc: The program has a "bug" in that if I try to bind to an element that is NOT in the NameValueCollection, I get a binding error whereas I really want either null or an empty string returned. For example, if you bind the text text to "Dino" rather than "Fred", it will throw an error! The point of my request was to not only not fail these conditions but allow user entry of a value. /steveA [quoted text, click to view] Marc Gravell wrote: > Design-time is a pain, as you are unlikely to be able to really trust > the contents. Run-time should be OK; I did some tests (code follows) > with NameValueCollection, trying to use TypeDescriptionProvider to > directly hook into TypeDescriptor, but it didn't want to use it (I > didn't look into why). In the end I had to wrap NameValueCollection > before it would let it in - hence the code is a little scrappy. In > 1.1, you could do the same by having the wrapper implement > ICustomTypeDescriptor directly. > > Re "returns a null string", note that binding works by > PropertyDescriptors, and *normally* a missing property returns a null > descriptor. You can create a bespoke PropertyDescriptorCollection, and > override the Find etc and create an extra descriptor when an unknown > property is queried, but I wouldn't recommend it - it isn't what is > expected. > > Marc > > Code: > > using System; > using System.Collections.Specialized; > using System.ComponentModel; > using System.Runtime.CompilerServices; > using System.Windows.Forms; > using System.Collections; > > static class Program { > static void Main() { > > NameValueCollection nvc = new NameValueCollection(); > nvc["Fred"] = "Wilma"; > nvc["Barney"] = "Betty"; > WrappedNameValueCollection wrapper = new > WrappedNameValueCollection(nvc); > > PropertyDescriptorCollection props = > TypeDescriptor.GetProperties(wrapper); > using (Form form = new Form()) > using (TextBox tb = new TextBox()) { > tb.DataBindings.Add("Text", wrapper, "Fred"); > form.DataBindings.Add("Text", wrapper, "Barney"); > form.Controls.Add(tb); > Application.Run(form); > } > } > } > public class WrappedNameValueCollection { > private readonly NameValueCollection _parent; > internal NameValueCollection Parent { get { return _parent; } } > public WrappedNameValueCollection(NameValueCollection parent) { > if (parent == null) throw new ArgumentNullException("parent"); > _parent = parent; > } > static WrappedNameValueCollection() { > NameValueCollectionTypeDescriptionProvider.Init(); > } > public int Count { get { return Parent.Count; } } > } > public sealed class NameValueCollectionTypeDescriptionProvider : > TypeDescriptionProvider { > static readonly ICustomTypeDescriptor baseDescriptor; > static NameValueCollectionTypeDescriptionProvider() { > TypeDescriptionProvider provider = > TypeDescriptor.GetProvider(typeof(WrappedNameValueCollection)); > baseDescriptor = > provider.GetTypeDescriptor(typeof(WrappedNameValueCollection)); > provider = new > NameValueCollectionTypeDescriptionProvider(provider); > TypeDescriptor.AddProvider(provider, > typeof(WrappedNameValueCollection)); > } > [MethodImpl(MethodImplOptions.NoInlining)] > internal static void Init() { } // NOP to force static ctor > > private > NameValueCollectionTypeDescriptionProvider(TypeDescriptionProvider > parent) : base(parent) { } > > public override ICustomTypeDescriptor GetTypeDescriptor(Type > objectType, object instance) { > return new NameValueCollectionTypeDescriptor(baseDescriptor, > instance as WrappedNameValueCollection); > > } > private sealed class NameValueCollectionTypeDescriptor : > CustomTypeDescriptor { > private readonly WrappedNameValueCollection collection; > public NameValueCollectionTypeDescriptor(ICustomTypeDescriptor > parent, WrappedNameValueCollection collection) : base(parent) { > this.collection = collection; > } > > public override PropertyDescriptorCollection GetProperties() { > if (collection == null || collection.Count == 0) return > PropertyDescriptorCollection.Empty; > PropertyDescriptor[] props = new > PropertyDescriptor[collection.Count]; > int index = 0; > foreach (string key in collection.Parent.Keys) { > props[index++] = new > NameValueCollectionPropertyDescriptor(key); > } > return new PropertyDescriptorCollection(props, true); > } > public override PropertyDescriptorCollection > GetProperties(Attribute[] attributes) { > return GetProperties(); // incomplete; does not respect > attributes > } > } > private sealed class NameValueCollectionPropertyDescriptor : > PropertyDescriptor { > static readonly Attribute[] emptyAttribs = new Attribute[0]; > public NameValueCollectionPropertyDescriptor(string name) : > base(name, emptyAttribs) { } > public override Type PropertyType { > get { return typeof(string); } > } > public override Type ComponentType { > get { return typeof(NameValueCollection); } > } > public override object GetValue(object component) { > return > ((WrappedNameValueCollection)component).Parent[Name]; > } > public override void SetValue(object component, object value) > { > ((WrappedNameValueCollection)component).Parent[Name] = > (string)value; > } > public override bool IsReadOnly { > get { return false; } > } > public override bool CanResetValue(object component) { > return false; > } > public override void ResetValue(object component) { > throw new NotSupportedException(); > } > public override bool ShouldSerializeValue(object component) { > return true; > } > } > } >
In looking at this a bit more, it seems that the call down to the GetTypeDescriptor doesn't allow the class to see what the BindingManagerBase wants to bind to, so it can say "yeh, I got that!" In order to allow arbitrary binding and soft-fail a return, doesn't it need to know what is being asked for? /steveA [quoted text, click to view] Marc Gravell wrote: > Design-time is a pain, as you are unlikely to be able to really trust > the contents. Run-time should be OK; I did some tests (code follows) > with NameValueCollection, trying to use TypeDescriptionProvider to > directly hook into TypeDescriptor, but it didn't want to use it (I > didn't look into why). In the end I had to wrap NameValueCollection > before it would let it in - hence the code is a little scrappy. In > 1.1, you could do the same by having the wrapper implement > ICustomTypeDescriptor directly. > > Re "returns a null string", note that binding works by > PropertyDescriptors, and *normally* a missing property returns a null > descriptor. You can create a bespoke PropertyDescriptorCollection, and > override the Find etc and create an extra descriptor when an unknown > property is queried, but I wouldn't recommend it - it isn't what is > expected. >
I did mention this before... along with a proposed solution, but again: this isn't normal usage... however, the following works: private sealed class NastyPropertyDescriptorCollection : PropertyDescriptorCollection { public NastyPropertyDescriptorCollection(PropertyDescriptor[] properties) : base(properties, false) { } public override PropertyDescriptor Find(string name, bool ignoreCase) { PropertyDescriptor prop = base.Find(name, ignoreCase); if (prop == null) { prop = new NameValueCollectionPropertyDescriptor(name); Add(prop); } return prop; } } and change GetProperties() last line to: return new NastyPropertyDescriptorCollection(props); Marc
Marc: That worked fine - thanks for the help. /steveA [quoted text, click to view] Marc Gravell wrote: > I did mention this before... along with a proposed solution, but > again: this isn't normal usage... however, the following works: > > private sealed class NastyPropertyDescriptorCollection : > PropertyDescriptorCollection { > public NastyPropertyDescriptorCollection(PropertyDescriptor[] > properties) : base(properties, false) { } > public override PropertyDescriptor Find(string name, bool > ignoreCase) { > PropertyDescriptor prop = base.Find(name, ignoreCase); > if (prop == null) { > prop = new > NameValueCollectionPropertyDescriptor(name); > Add(prop); > } > return prop; > } > } > > and change GetProperties() last line to: > return new NastyPropertyDescriptorCollection(props); > > Marc > >
-- Steve Alpert
Don't see what you're looking for? Try a search.
|
|
|