Groups | Blog | Home
all groups > dotnet windows forms databinding > june 2007 >

dotnet windows forms databinding : bind to elements in a NameValueCollection, hashtable, or Dictionary?


Steve Alpert
6/24/2007 12:22:28 PM
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,
Marc Gravell
6/25/2007 12:00:00 AM
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;
}
}
}

Steve Alpert
6/25/2007 11:36:58 AM
Marc:
Thanks! I'll give it a shot.

/steveA

[quoted text, click to view]
Steve Alpert
6/26/2007 8:49:42 AM
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]
Steve Alpert
6/26/2007 9:10:12 AM
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
6/26/2007 2:41:29 PM
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
6/27/2007 7:53:19 PM
Marc: That worked fine - thanks for the help.

/steveA

[quoted text, click to view]

--
Steve Alpert
AddThis Social Bookmark Button