I have a business object I want to use for binding. There are a series of combobox on the form that are populated via a bindingsource which sets the DataSource, DataMember, ValueMemeber. THis populates the combo with available values. Now, I bind all the controls on teh form to my business object. The BO has a property which I set to be bound to the SelectedValue of the combo. The property in question is defined as an enum. It seems like the binding mechanism cannot read/write from the property when defined as an enum. If I change it to Integer, it all works correctly.
Does anyone know the cause of this issue? Is there a work around or something I need to do to make this work? If I has to use integers for my properites, it will not be pretty, I have dozens of enums and the code will get very ugly fast.
Thanks in advance for any assistance you may provide.
Hi,
Thanks for your reply.
So, what is happening is that I am not binding the combobox to the enum values. I am binding the combo box to a List object that contains the values loaded from the database. However, to represent the values loaded in the database, I use enums in the application.
So, for example I have,
Public Enum MyEnum
Red
White
Blue
End Enum
And in my business object, I have a property
public property SomeColor as MyEnum
Get...
Set...
End property.
I have a binding source that is set to an Object of a ReadOnlyList that is used to populate the combobox. Then, in my business object for the overall form, I have a property like above. I bind teh SelectedValue to the property in the Business Object so that when an object is being edited, the combobox shows the right value.
I put a breakpoint on teh Get and Set routine in teh property. When I change the value in the combobox, the set never gets called, but the Get is getting called 2 or 3 times. Then the form goes into a state where I cannot even take teh focus of the combobox in question, can't even close the form with the controlbox.
I am going to do some isolated testing today, but there seems to be a limitation here...hoping I am wrong though.
Rick.
I'm a bit confused.
Enums are a static construct, and building a list of values from the database is, by definition, a dynamic thing to do. So I'm concerned about keeping the list of values in the enum the same as the list of items in the database. It just feels like a design mismatch to me.
Why not load an NVList (from NameValueListBase) from the database and bind it to the combobox? That's what it's for! :)
Hello,
Ok, so binding a list of items to the combobox is not the issue.
Yes, we have enums that are kept in sync with values from the database, good or bad, that is how it was done.
I bind the combobox based on an id that will give me a group of related possible values for a particular domain from the database (also b/c we are supporting multiple languages, this is in teh database). This is fine.
My issue is with the business object that has attributes of those enum types.
The business object has a property, for example, a car object, with a color property, wher the only options are red,white,blue as the enum above. So, when declaring the property on the Car object, it is delcared as the enum type. Why? So that when developers are coding against the property, they immediatly see the options for the enum...this is very common practice throughout the entire .Net framework.
So, I have a winform to manage my 'car' object. I use a combobox to give the user the limited choices they have on colors. I load the available values from the database in teh appropriate language for the user. I bind all the controls on my winform to by Car object. The combobox selected value is bound to the color property whose datatype is defined as the colors enum above.
The winform is not behaving nicely in this situation. It is not calling the Set on teh property when the value is changed. It is calling the get,but not changing the value in the combobox based on teh value of the property when the property datatype is the enum.
Please note that my issue has nothing to do with loading the combobox, it has to do with binding the comboboxes on the form to properties of my business object.
I know this is down the path of a general .Net thing now, but I am trying to do a proof of concept with the CSLA framework with our existing application. And this is how I came accross this. Here is a sample app I whipped up that will demonstrate the problem.
Public
Enum MyEnumOne = 1
Two
Three
Four
End
Enum Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Dim sortedList As Collections.ArrayList = New ArrayListsortedList.Add(
New myItems(-1, ""))sortedList.Add(
New myItems(1, "One"))sortedList.Add(
New myItems(2, "Two"))sortedList.Add(
New myItems(3, "Three"))sortedList.Add(
New myItems(4, "Four"))ComboBox1.DataSource = sortedList
ComboBox1.DisplayMember =
"Value"ComboBox1.ValueMember =
"Key" Dim _bo As MyBO = New MyBO()MyBOBindingSource.DataSource = _bo
End Sub Public Class myItems Private Dim _key As Integer Private Dim _value As String Public Property Key() As Integer Get Return _key End Get Set(ByVal value As Integer)_key = value
End Set End Property Public Property Value() As String Get Return _value End Get Set(ByVal value As String)_value = value
End Set End Property Public Sub New(ByVal key As Integer, ByVal value As String)_key = key
_value = value
End Sub End ClassEnd
Class
Public
Class MyBO Public Property X() As EnumClassLibrary.MyEnum Get Return _x End Get Set(ByVal value As EnumClassLibrary.MyEnum)_x = value
End Set End Property Private _x As EnumClassLibrary.MyEnum Public Sub New()X = EnumClassLibrary.MyEnum.Four
End Subdavid.wendelken:I'm a bit confused.
Enums are a static construct, and building a list of values from the database is, by definition, a dynamic thing to do. So I'm concerned about keeping the list of values in the enum the same as the list of items in the database. It just feels like a design mismatch to me.
Why not load an NVList (from NameValueListBase) from the database and bind it to the combobox? That's what it's for! :)
Enums are a great idea for static data (Closed sets of choices) that other tables/objects feed off. Drives me mad when I see applications with magic numbers.
GetAllOrdersByType(1);
What does it mean???
Much better to use
GetAllOrdersByType(OrderType.CeaseOrder);
Rick,
I have a similar setup to yours where I have a "lookup" table in the database with values to populate combo boxes. I also have corresponding enums with these "lookup" values. As far as I know, it is not possible to bind a combobox directly to an enum property. I'm assuming that your business object also stores the database id of the lookup item that was selected so that it can be persisted back to the database. What I do is bind the combobox to this lookup id property on the business object. Then in the get accessor of the enum property I call a static/shared helper method that "maps" the database id of the lookup item to the correct value in the enum. So your get accessor for the enum property on the business object would look something like this (c#):
public MyEnum SomeColor
{
get
{
return EnumHelper.GetMyEnumValue(m_colorId);
}
}
The implementation is a little tedious, but it gets the job done. It's also much better than looking at code like this:
if (m_colorId == 1) && 1 stands for Red
If anybody knows a better way, please let us know.
Dustin
Why not wrap the enum in a NVL? In other words, create an object that inherits from NVLB, where the DataPortal_Fetch() method merely loads the list from the enum. You can enumerate the values in an enum (using reflection if nothing else).
Given an enum like
public enum MyEnum
{
Red,
Blue,
Green
}
You'd have a class like:
[Serializable]
public class MyEnumList : Csla.NameValueListBase<MyEnum,string>
{
public static MyEnumList GetList()
{
return Csla.DataPortal.Fetch<MyEnumList>(new Criteria(typeof(MyEnumList)));
}
private MyEnumList()
{ /* require use of factory method */ }
[Csla.RunLocal]
protected override void DataPortal_Fetch(object criteria)
{
this.IsReadOnly = false;
foreach (MyEnum item in Enum.GetValues(typeof(MyEnum)))
Add(new Csla.NameValueListBase<MyEnum, string>.NameValuePair(
item, Enum.GetName(typeof(MyEnum), item)));
this.IsReadOnly = true;
}
}
Then you can bind it like you'd bind any other object/list to a combobox, as long as the object's property is of type MyEnum:
MyEnum _color;
public MyEnum Color
{
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
get
{
CanReadProperty(true);
return _color;
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
set
{
CanWriteProperty(true);
if (!_color.Equals(value))
{
_color = value;
PropertyHasChanged();
}
}
}
Hey Rocky,
In summary, that code worked perfectly. After analyzing my problem set more after I got a sample project working with it, I added a few requirements to my list. I follow the pattern you posted. But, in my case, I have a 'master' enum which is an enum of all the available enums that we have. When I say enums in this context I am referring to different domain of lookup values. So, I have an enum of all lookup value types, and enums of each individual lookup. In total, dozens upon dozens of enums -- lots of entities managed in this system.Protected Overrides Sub OnCreateControl()
MyBase.OnCreateControl()
For Each bindings As System.Windows.Forms.Binding In DataBindings
bindings.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged
Next
If Not Me.DesignMode ThenMe.DataSource = AMSCSLA.ValueList.GetValueList(AMSDomain) Me.DisplayMember = "Value" Me.ValueMember = "Key" End If End Sub Public Property AMSDomain() As AMS.Framework.AMSDomains Get Return _amsDomain End Get Set(ByVal value As AMS.Framework.AMSDomains)
_amsDomain = value
End Set End Property
In here, pass the master domain type to my NVL. In my NL I do this:
Public
Shared Function GetValueList(ByVal domain As AMS.Framework.AMSDomains) As ValueList Return Csla.DataPortal.Fetch(Of ValueList)(New DomainCriteria(domain)) End Function Protected Overloads Sub DataPortal_Fetch(ByVal thecriteria As DomainCriteria) Me.IsReadOnly = False Dim enumName As String = [Enum].GetName(GetType(AMSDomains), CType(thecriteria.Domain, Integer)) Dim enumTypeHandle As System.Runtime.Remoting.ObjectHandle = System.Activator.CreateInstance("AMS.Framework", "AMS.Framework.AMSDomainValues." & enumName) Dim enumType As [Enum] = CType(enumTypeHandle.Unwrap(), [Enum]) For Each item As Object In [Enum].GetValues(enumType.GetType)Add(
New Csla.NameValueListBase(Of [Enum], String).NameValuePair( _item, [Enum].GetName(enumType.GetType, item)))
Next Me.IsReadOnly = True End Sub Protected Class DomainCriteria Public Property Domain() As AMS.Framework.AMSDomains Get Return _domain End Get Set(ByVal value As AMS.Framework.AMSDomains)_domain = value
End Set End Property Private _domain As AMS.Framework.AMSDomains Public Sub New(ByVal domain As AMS.Framework.AMSDomains)_domain = domain
End Sub End ClassEnd Class
I use a little reflection to create an insance of the type of enum specified by the domain (this makes a requirment that the text of the master domain match the actual domain, but that will be managable). I will have left to do after this is add in a call to my lookup table to get the string text for the NVL in the users culture so the UI displays correctly and to sort the values by that text.
Thanks very much for your post, you definitely got me down the right path.
Regards,
Rick.
Wow. That's a pretty good idea. I'll have to try to modify my system to use something like this. It's quite a bit less tedious than what I'm currently using.
Dustin
Here's another suggestion:
I created a class called EnumNVL which creates a NameValueList from any Enum Type.
Sample Usage in form:
EnumNVL envlDateRangeTypes = EnumNVL.GetEnumNVL(typeof(DateRangeType));
// The parameter for the GetEnumNVL() method is any valid Enum, ie., GetEnumNVL(typeof(AnyValidEnum)).
// The substitutable parameter in this example uses the Enum type "DateRangeType".
cbxDateRangeTypes.DataSource = envlDateRangeTypes
cbxDateRangeTypes.DisplayMember = "Value";
cbxDateRangeTypes.ValueMember = "Key";
cbxDateRangeTypes.SelectedIndex = 0;
Here's is the EnumNVL class:
[Serializable]
public class EnumNVL : Csla.NameValueListBase<Enum, string>
{
private static Type _Type = null;
public static EnumNVL GetEnumNVL(Type type)
{
_Type = type;
return Csla.DataPortal.Fetch<EnumNVL>(new Criteria(typeof(EnumNVL)));
}
private EnumNVL()
{ /* require use of factory method */ }
[Csla.RunLocal]
protected override void DataPortal_Fetch(object criteria)
{
this.IsReadOnly = false;
foreach (Enum item in Enum.GetValues(_Type))
Add(new Csla.NameValueListBase<Enum, string>.NameValuePair(
item, Enum.GetName(_Type, item)));
this.IsReadOnly = true;
}
}
-- Update:
Cine's brilliant generic version below is a great modification. Here is an example of how I use his code:
EnumNVL<DateRangeType> enumNVL = EnumNVL<DateRangeType>.GetEnumNVL();
// The KEY POINT is that with your more general code, I can cast the type to int as follows:
int dbColumnValue = (int)enumNVL[0].Key;
// I could not do that cast with my code!
Thanks!
Here it is in VB:
<Serializable()> _
#Region
" Constructor " Private Sub New()#End
Region#Region
" Factory Methods " Public Shared Function GetEnumNVL(ByVal type As Type) As EnumNVL#End
Region#Region
" Data Access " <RunLocal()> _
IsReadOnly =
True#End
Region End Class
That’s a good idea – using the Description
attribute.
However, a Dictionary isn’t a valid data binding source,
so there’s probably still some value in having a list type that is
bindable that auto-populates based on the enum type. Perhaps a subclass of
NameValueListBase that wraps your code in its DataPortal_Fetch() method?
Rocky
Web Forms has a very low set of requirements for its binding
sources. Windows Forms, WPF and Silverlight have richer functionality, and thus
have a higher set of requirements.
Rocky
I am binding using the propeties window - Data - Data Bindings - SelectedValue and setting the SelectedValue to MyBOBindingSource.PropertyName.
So, from the SelectedValue property under data bindings, a data source has been created bound to my business object. I then use this binding source to bind to all the controls.
Copyright (c) Marimer LLC