Making HTTP Post with Silverlight a little easier
In my earlier post, I talked about how to go about making an HTTP Post in Silverlight. The whole process is a little complicated to say the least. Silverlight does not come with an utility class like WebClient that makes making a HTTP Get a little easier. So, I created a class called ‘HTTPPostHelper’ that does for HTTP Post what WebClient does for HTTP Get. The ‘HTTPPostHelper’ class listed below, makes the whole process a lot easier. To use the class, do the following
- Create an instance of the HTTPPostHelper class with the type of the input class (T) and the type of the returned class (K) as the generic arguments.
- Listen to the ‘PostCompleted’ event that gets called when the Post operation completes successfully. The EventArgs of the event contains an instance of the return class type.
- Listen to the ‘PostFailed’ event that gets called when the Post operation fails. The EventArgs of the event contains the exception information.
- Call the ‘Post’ method on the HTTPPostHelper with the input object and the URL address to make the Post operation happen.
- The input object gets serialized by default using the DataContractSerializer. But this can be overridden by setting the Serializer property of the HTTPPostHelper with an appropriate delagate that serializes the input object to a stream.
- The output from the HTTP Post operation is deserialized by default using the DataContractSerializer. But this too can be overridden by the Deserializer property of the HTTPPostHelper.
- For example, if you want to send and receive JSON data to the HTTP endpoint, just override the default Serializer & Deserializer with delegates that use DataContractJsonSerializer instead or your own custom binary serializer if need be.
- The ‘ContentType’ used for the Post operation can also set before calling the Post method.
- The class handles all the thread hopping internally. The ‘PostCompleted’ and the ‘PostFailed’ events get raised on the same thread from which they were called from.
public class HTTPPostHelper<T, K>
{
SynchronizationContext syncContext;
HttpWebRequest _request;
public string ContentType
{
get;
set;
}
public event EventHandler<EventArgs<K>> PostCompleted;
public event EventHandler<ExceptionEventArgs> PostFailed;
public Action<T, Stream> Serializer = null;
public Func<Stream, K> Deserializer = null;
public HTTPPostHelper()
{
Serializer = DefaultSerializer;
Deserializer = DefaultDeserializer;
ContentType = “application/stream”;
}
void DefaultSerializer(T input, Stream output)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(output, input);
output.Flush();
}
K DefaultDeserializer(Stream input)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(K));
K result = (K)serializer.ReadObject(input);
return result;
}
public void Post(Uri address, T input)
{
syncContext = SynchronizationContext.Current;
_request = (HttpWebRequest)WebRequest.Create(address);
_request.Method = “POST”;
_request.ContentType = “application/stream”;
_request.BeginGetRequestStream(new AsyncCallback(PostRequestReady), new HttpWebRequestData<T>()
{
Request = _request,
Data = input
});
}
private void PostRequestReady(IAsyncResult asyncResult)
{
HttpWebRequestData<T> requestData = asyncResult.AsyncState as HttpWebRequestData<T>;
HttpWebRequest request = requestData.Request;
Stream requestStream = request.EndGetRequestStream(asyncResult);
Serializer(requestData.Data, requestStream);
requestStream.Close();
request.BeginGetResponse(new AsyncCallback(PostResponseCallback), request);
}
private void PostResponseCallback(IAsyncResult ar)
{
try
{
HttpWebRequest request = ar.AsyncState as HttpWebRequest;
HttpWebResponse response = request.EndGetResponse(ar) as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
K urls = DefaultDeserializer(responseStream);
syncContext.Post(PostExtractResponse, new HttpWebResponseData<K>()
{
Response = response,
Data = urls
});
}
catch (Exception ex)
{
syncContext.Post(RaiseException, ex);
}
}
private void RaiseException(object state)
{
Exception ex = state as Exception;
if (PostFailed != null)
PostFailed(this, new ExceptionEventArgs(ex));
}
private void PostExtractResponse(object state)
{
HttpWebResponseData<K> responseData = state as HttpWebResponseData<K>;
if (PostCompleted != null)
PostCompleted(this, new EventArgs<K>(responseData.Data));
}
}
public class ExceptionEventArgs : EventArgs
{
public Exception ExceptionInfo { get; set; }
public ExceptionEventArgs(Exception ex)
{
ExceptionInfo = ex;
}
}
public class EventArgs<T> : EventArgs
{
private T eventData;
public EventArgs(T eventData)
{
this.eventData = eventData;
}
public T EventData
{
get
{
return eventData;
}
}
}
Making a HTTP Post in Silverlight a.k.a thread hopping
Making a HTTP GET request is as easy as pie using the WebClient class that is available in the Silverlight library. The DownloadStringCompleted event callback of the WebClient class is invoked on the main UI thread thus allowing the developer to access UI elements in the callback method itself. If the web service call resulted in a failure, then that condition can be easily determined in the DownloadStringCompleted event callback method by checking if the Error property on the DownloadStringCompletedEventArgs argument passed into the callback method is NULL. The code snippet below illustrates one way to use the WebClient class.
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += (sender, e) =>
{
if (e.Error == null)
{
//Process web service result here
txtBox.Text = e.Result;
} else
{
//Process web service failure here
txtBox.Text = e.Error.Message;
} callback();
};
webClient.DownloadStringAsync( new Uri( “URL address that needs to be fetched” ) );
But when it comes to making a HTTP POST in Silverlight, things aren’t so simple. Silverlight doesn’t come with any helper classes like the WebClient (for making HTTP GET requests) to help take the hassle out of making a HTTP POST. So, to make a HTTP POST request, we must jump through two threads before we get back on the main UI thread where we will be able to access the UI elements and update them as needed. Here is a simple illustration of the process that we have to go through to make a HTTP POST.
Before we get into code snippets for actually accomplishing the above, let’s get into a little bit of why the above needs to happen. When the user initiates an UI event (button click etc) upon which we must make the HTTP request, we need to ask Silverlight/browser to open a HTTP request pipe to the specified URL. We get this request stream in a different non-UI thread. The reason for this being that Silverlight makes HTTP requests through the browser stack (Silverlight 3 can use its own HTTP stack if needed, but uses the browser stack by default). As you might know, browsers limit the number of outgoing HTTP requests to a given domain name. This limit used to be 2 but modern day browsers have increased this limit (8 in IE8 etc). So, given this limit on the outgoing HTTP requests, there is no guarantee that the browser can open a HTTP request pipe to the URL when demanded by Silverlight. So, we end up getting the request stream in a non-UI thread. We can write/post the necessary data to this request pipe and wait to get a response from the web service. The results from the web service is received on another different non-UI thread. Once we read all the information from the response stream, we can’t start updating the UI elements since we are still on a non-UI thread. So, we need to pass this response from the web service back to the UI thread before we can start updating the UI elements.
Since, we need to pass data between threads as explained above, I had written these tiny little classes to help us through the process of making the HTTP POST request.
public class HttpWebResponseData<T>
{
public HttpWebResponse Response { get; set; }
public T Data { get; set; }
}
public class HttpWebRequestData<T>
{
public HttpWebRequest Request { get; set; }
public T Data { get; set; }
}
public class ExceptionEventArgs : EventArgs
{
public Exception ExceptionInfo { get; set; }
public ExceptionEventArgs(Exception ex)
{
ExceptionInfo = ex;
}
}
public class EventArgs<T> : EventArgs
{
private T eventData;
public EventArgs(T eventData)
{
this.eventData = eventData;
}
public T EventData
{
get
{
return eventData;
}
}
}
We need to store away the SynchronizationContext of the UI thread before we begin making the HTTP POST request and start jumping threads. This is so that we can get back on the UI thread when the HTTP POST web service call has returned. So, create a variable to hold the in the SynchronizationContext Silverlight UserControl.
SynchronizationContext syncContext;
The following code snippet illustrates how to use the HTTPWebRequest and the HTTPWebResponse classes in Silverlight to perform the HTTP POST.
//This method will called on user action (button click etc); We are on the UI thread
private void DoHTTPPost(List<string> stuffToPost)
{
//Obtain and store away the SynchronizationContext of the UI thread in the private variable declared above
syncContext = SynchronizationContext.Current;
HttpWebRequest request = ( HttpWebRequest ) WebRequest.Create( “URL address to POST to” ) );
request.Method = “POST”;
request.ContentType = “text/xml”;
request.BeginGetRequestStream( new AsyncCallback( DoHTTPPostRequestReady ), new HttpWebRequestData<List<string>>()
{
Request = request,
Data = stuffToPost
} );
}
//This method will be called on the non-UI request thread
private void DoHTTPPostRequestReady(IAsyncResult asyncResult)
{
HttpWebRequestData<List<string>> requestData = asyncResult.AsyncState as HttpWebRequestData<List<string>>;
HttpWebRequest request = requestData.Request;
Stream requestStream = request.EndGetRequestStream(asyncResult);
DataContractSerializer serializer = new DataContractSerializer(typeof(List<string>));
//Write the string list as XML to the request stream
StreamWriter sw = new StreamWriter(requestStream);
serializer.WriteObject(requestStream, requestData.Data);
sw.Flush();
requestStream.Close();
request.BeginGetResponse(new AsyncCallback(DoHTTPPostResponseCallback), request);
}
//This methods will be called on the non-UI response thread
private void DoHTTPPostResponseCallback(IAsyncResult ar)
{
//Ignoring error handling here to keep things simple
HttpWebRequest request = ar.AsyncState as HttpWebRequest;
HttpWebResponse response = request.EndGetResponse(ar) as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
DataContractSerializer serializer = new DataContractSerializer(typeof(string));
string result = serializer.ReadObject(responseStream) as string;
//Post the response from the web service back to the UI thread using the SynchronizationContext obtained when initiating the HTTP POST request
syncContext.Post(DoHTTPPostExtractResponse, new HttpWebResponseData<string>()
{
Response = response,
Data = result
});
}
//We are back on the UI thread when this method is called
private void DoHTTPPostExtractResponse(object state)
{
string response = (state as HttpWebResponseData<string>).Data;
//We can update the UI elements with the response here since we are back on the UI thread
txtBox.Text = response;
}
I will be posting more helper methods for the above pretty soon. Also, I will delve more into making HTTP POST requests to WCF REST endpoints in future posts.
Generics support in XAML
Support for creating generic classes in XAML is still lacking. The only XAML element that lets you specify the generic type argument is only the root element of the user control. I couldn’t find any way to create an instance of a generic class in XAML yet. One had a class called “NameValue” in the project and wanted to be able to create an ObservableCollection<NameValue> in XAML. The only way I was able to do it in XAML was to create another empty class called NameValueCollection that inherited from ObservableCollection<NameValue> and then create an instance of the “NameValueCollection” in XAML instead.
public class NameValueCollection : ObservableCollection<NameValue>
{
}
I think one way generics could be supported in XAML is by creating new MarkupExtensions for that purpose.
Inserting spatial data in SQL Server 2008
I have been working on inserting data from FeatureClasses into SQL Server 2008 tables. There are a lot of examples online showing how spatial data can be inserted into SQL Server 2008 tables. Here is one from Hannes.
I took from cue from there and proceeded to do what every good programmer should do…parameterize the INSERT query. Here is a simplified version of the scenario I was trying to tackle…
sqlCommand.CommandText = INSERT INTO foo (id, geom) VALUES(1,@g);
sqlCommand.InsertParameter(“@g”, ‘POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))’);
The above INSERT command worked fine. But I also wanted to insert the geometry or geography with the WKID value from the FeatureClass. So I changed the above INSERT statement to use the geometry’s static method and provided the WKID as below
sqlCommand.InsertParameter(“@g”, ‘geometry::STGeomFromText(POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), 0)’);
But that didn’t go quite as expected. The INSERT statement was throwing an error saying that it did not recognize ‘geometry::STGeomFromText’ and was expecting ‘POINT, POLYGON, LINESTRING…’ etc.
System.FormatException: 24114: The label geometry::STGeomFrom in the input well-known text (WKT) is not valid. Valid labels are POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, or GEOMETRYCOLLECTION.
This led me to use the Udt SqlParamter type to get around the problem like below. This way I could use the SqlGeometry type in the ‘Microsoft.SqlServer.Types’ namespace and set the SRID directly on its ‘STSRID’ property. The ‘SqlGeometry’ and ‘SqlGeography’ types can be found in the Feature Pack for SQL Server 2008 that can be found here. The SqlGeometry reference can then be used as the value for the SqlParameter.
SqlParameter parameter = new SqlParameter( parameterName, value );
parameter.Direction = ParameterDirection.Input;
parameter.ParameterName = parameterName;
parameter.SourceColumn = sourceColumn;
parameter.SqlDbType = SqlDbType.Udt;
parameter.UdtTypeName = “GEOMETRY”;
parameter.SourceVersion = DataRowVersion.Current;command.Parameters.Add( parameter );
The ‘UdtTypeName’ should be set to ‘GEOGRAPHY’ for inserting geography fields.
And it worked like a charm.
Update: Heard from Morten saying that using the SqlGeography and SqlGeometry types are almost twice as fast as using WKB (Well Known Binary). Using WKB is by itself faster than using WKT (Well Known Text).
Simulating properties on a bindable list using ITypedList
In this blog post I am going to illustrate how to simulate properties on a bindable list using the ITypedList interface. The ITypedList interface is used by the .NET data-binding mechanism to discover the properties of the items in the list that is being bound. So in fact the ITypedList interface can be implemented by custom collections to customize the properties available for binding on the object to bind to. The .NET data-binding mechanisms use the GetItemProperties(…) method on the ITypedList interface to obtain a PropertyDescriptorCollection to obtain the list of properties available on the object in the list. So, the GetItemProperties(…) method can be overridden to return a custom collection of properties that will be used for data-binding.
Let us take for example, the ‘Distance’ class given below.
public class Distance
{
double _tolerance = 0;
esriUnits _units = esriUnits.esriFeet;
public Distance(){}
public Distance(double ptolerance, esriUnits punits)
{
_tolerance = ptolerance;
_units = punits;
}
public double Tolerance
{
get { return _tolerance; }
set { _tolerance = value; }
}
public esriUnits Units
{
get { return _units; }
set { _units = value; }
}
}
The ‘Distance’ class above contains a tolerance measure and its units as it is public properties. Let’s say that we also have a typed collection ‘DistanceCollection’ of the ‘Distance’ objects.
public class DistanceCollection
{
#region “CollectionBase Members”
public Distance this[int index]
{
get
{
return ((Distance)List[index]);
}
set
{
List[index] = value;
}
}
public int Add(Distance value)
{
return (List.Add(value));
}
public int IndexOf(Distance value)
{
return (List.IndexOf(value));
}
public void Insert(int index, Distance value)
{
List.Insert(index, value);
}
public void Remove(Distance value)
{
List.Remove(value);
}
public bool Contains(Distance value)
{
// If value is not of type Distance, this will return false.
return (List.Contains(value));
}
protected override void OnInsert(int index, Object value)
{
// Insert additional code to be run only when inserting values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnRemove(int index, Object value)
{
// Insert additional code to be run only when removing values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnSet(int index, Object oldValue, Object newValue)
{
// Insert additional code to be run only when setting values.
if (oldValue.GetType() != typeof(Distance))
throw new ArgumentException(“oldValue must be of type Distance.”, “value”);
if (newValue.GetType() != typeof(Distance))
throw new ArgumentException(“newValue must be of type Distance.”, “value”);
}
protected override void OnValidate(Object value)
{
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
#endregion
}
A new datasource could be added in Visual Studio for the ‘DistanceCollection’ type as shown below…
Our goal is to simulate a property called ‘ToleranceInMeters’ on the ‘DistanceCollection’ type so that we have an additional column on the DataGridView specifying the tolerance in meters. In order to achieve this we implement ITypedList on the ‘DistanceCollection’ type and override the GetItemProperties(…) method so that it returns a PropertyDescriptorCollection object with an added PropertyDescriptor object for the ‘ToleranceInMeters’ property.
To return an additional property in the PropertyDescriptorCollection that returns the tolerance in meters, we create a new class called ‘ToleranceInMetersPropertyDescriptor’. And in the GetValue(…) method of the new class we implement the logic to convert the tolerance value from the existing units into meters. In the below code, the ‘ToleranceInMetersPropertyDescriptor’ class has been implemented as an inner class inside ‘DistanceCollection’. We also build the new PropertyDescriptorCollection in the static constructor of ‘DistanceCollection’ and return it inside the GetItemProperties(…) method.
public class DistanceCollection : CollectionBase, ITypedList
{
static DistanceCollection()
{
CalculatePropertyDescriptors();
}
#region “CollectionBase Members”
public Distance this[int index]
{
get
{
return ((Distance)List[index]);
}
set
{
List[index] = value;
}
}
public int Add(Distance value)
{
return (List.Add(value));
}
public int IndexOf(Distance value)
{
return (List.IndexOf(value));
}
public void Insert(int index, Distance value)
{
List.Insert(index, value);
}
public void Remove(Distance value)
{
List.Remove(value);
}
public bool Contains(Distance value)
{
// If value is not of type Distance, this will return false.
return (List.Contains(value));
}
protected override void OnInsert(int index, Object value)
{
// Insert additional code to be run only when inserting values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnRemove(int index, Object value)
{
// Insert additional code to be run only when removing values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnSet(int index, Object oldValue, Object newValue)
{
// Insert additional code to be run only when setting values.
if (oldValue.GetType() != typeof(Distance))
throw new ArgumentException(“oldValue must be of type Distance.”, “value”);
if (newValue.GetType() != typeof(Distance))
throw new ArgumentException(“newValue must be of type Distance.”, “value”);
}
protected override void OnValidate(Object value)
{
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
#endregion
#region “Simulated Property”
private class ToleranceInMetersPropertyDescriptor : PropertyDescriptor
{
UnitConverterClass _converter = new UnitConverterClass();
public ToleranceInMetersPropertyDescriptor(string name)
: base(name, null)
{
}
public override bool CanResetValue(object component)
{
return false;
}
public override System.Type ComponentType
{
get { return typeof(Distance); }
}
public override bool IsReadOnly
{
get { return true; }
}
public override System.Type PropertyType
{
get { return typeof(double); }
}
public override void ResetValue(object component)
{
}
public override object GetValue(object component)
{
Distance d = (Distance)component;
return _converter.ConvertUnits(d.Tolerance, d.Units, esriUnits.esriMeters);
}
public override void SetValue(object component, object value)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
#endregion
#region ITypedList Members
private static PropertyDescriptorCollection propertyDescriptors;
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
return propertyDescriptors;
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
return “DistanceCollection”;
}
private static void CalculatePropertyDescriptors()
{
PropertyDescriptorCollection origProperties = TypeDescriptor.GetProperties(typeof(Distance));
ArrayList properties = new ArrayList();
foreach (PropertyDescriptor desc in origProperties)
{
properties.Add(desc);
}
properties.Add(new ToleranceInMetersPropertyDescriptor(“ToleranceInMeters”));
propertyDescriptors = new PropertyDescriptorCollection((PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor)));
}
#endregion
}
What do you achieve by all this? Well, we get a property on the ‘DistanceCollection’ type in the Data Sources window in Visual Studio 2005 and when it is dragged on to the form the DataGridView now appears with a an additional column called ‘ToleranceInMeters’.
In the constructor of the form, initialize the binding source for the DistanceCollection and the result will be what you see below.
DistanceCollection _Coll = new DistanceCollection();
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriFeet));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriInches));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriKilometers));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriMiles));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriNauticalMiles));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriDecimalDegrees));
distanceCollectionBindingSource.DataSource = _Coll;
‘goto’ statement in C# ??? !!!
I am actually really surprised to find out that there is a ‘goto’ statement in C#. This really does come as a surprise to me after years of being preached the evilness of the ‘goto’ statements. Maybe it is not all that bad. But I have not needed to use it before and hopefully never will.
Static constructors in generic types
Static constructors are a relatively little known feature .NET. It might probably be because they don’t have too many use cases where they fit in perfectly. But they did come in handy when I was looking for ways for make a list of all the properties in my class that were decorated in with a custom attribute. The static constructor was an ideal place to build the collection so that the collection can be exposed as a static property on my class.
But I did have my doubts on whether I will be able to use the static constructors effectively on generics types. Generics types are tricky when it comes to static constructors since the generic type can be customized with other type parameters. But turns out the static constructors do work effectively on generic types. The type parameters are available in the static constructor and the type parameter T in the static constructors for CustomType<int> and CustomType<string> refer to their corresponding type parameters even though they use the same CustomType<T> class.
The use of static constructors can be observed in the project attached to one of my previous blog post.
Databinding Enum values to combo boxes
Even though I dislike the use of enumerations, there are cases where you can use them to make your life a lot easier. In those cases, there might be a requirement to have the enumeration property data bound to a combo box which lists all possible values of the enumeration in some meaningful text and also be able to select the right enumeration text when the object is bound to it. I have written a small little utility that will do just that. It returns a Read-Only collection with some meaningful text as the “Name” and the enum value as the “Value” member of the collection. In the sample, the generic class EnumCollection<T> has a static DataSource property that returns a ReadOnlyICollection that is data bindable to a combo box. The type T represents an enumeration type. The text to describe the enumeration values can be added as custom attribute decorators in the definition of the enumeration as below.
1 public enum EnumSample
2 {
3 [Text("This is One.")]
4 One,
5 [Text("This is Two.")]
6 Two,
7 [Text("This is Three.")]
8 Three,
9 [Text("This is Four.")]
10 Four
11 }
The EnumCollection<T> class creates its collection in a static constructor for the generic class and thus is able to return the required collection in a static “DataSource” property. The definition of the EnumCollection<T> class is below.
1 public class EnumCollection<T> : ReadOnlyCollection<EnumClass<T>>
2 {
3 static EnumCollection<T> _val;
4
5 static EnumCollection()
6 {
7 if (!typeof(T).IsEnum)
8 throw new InvalidCastException(“sbEnumCollection only supports ‘enum’ types.”);
9
10 List<EnumClass<T>> vals = new List<EnumClass<T>>();
11 Array values = Enum.GetValues(typeof(T));
12 foreach (Object item in values)
13 {
14 EnumClass<T> entry = new EnumClass<T>(GetText<T>(item), (T)item);
15 vals.Add(entry);
16 }
17 _val = new EnumCollection<T>(vals);
18 }
19
20 private static string GetText<U>(object item) where U : T
21 {
22 if (!typeof(U).IsEnum)
23 throw new InvalidCastException(“GetText only supports ‘enum’ types.”);
24
25 FieldInfo fi = typeof(U).GetField(item.ToString());
26 string result = null;
27 if (fi != null)
28 {
29 object[] attrs = fi.GetCustomAttributes(typeof(TextAttribute), true);
30 if (attrs != null && attrs.Length > 0)
31 result = ((TextAttribute)attrs[0]).Text;
32 }
33
34 if (result == null)
35 result = Enum.GetName(typeof(T), item);
36
37 return result;
38 }
39
40 internal EnumCollection(IList<EnumClass<T>> list) : base(list) { }
41
42 public static EnumCollection<T> DataSource
43 {
44 get
45 {
46 return _val;
47 }
48 }
49 }
Enumerations can be data bound to combo boxes easily as shown below.
1 comboBox1.DataSource = EnumCollection<EnumSample>.DataSource;
2 comboBox1.DisplayMember = “Name”;
3 comboBox1.ValueMember = “Value”;
The complete project can be downloaded here.
Follow Me
Contact me
1 comment