Trying to bind a combobox to an enum in C# - c#

I have a class that contains a property that is an enum:
public RaTypes RaBucket1Type { get; set; }
My enum is:
public enum RaTypes
{
Red,
Yellow
}
I was able to bind a form's combobox data-source to the enum so that when I click on the drop-down, I see the enumerations:
cmbBucket1Type.DataSource = Enum.GetValues(typeof(RaTypes));
When I load the form, I would like to populate the combo-box with the existing value. I have tried the following:
cmbBucket1Type.DisplayMember = "TradeType";
cmbBucket1Type.ValueMember = "TradeEnumID";
cmbBucket1Type.SelectedValue = EditedAlgorithm.RaBucket1Type;
But this did not work.
Also, I'm not sure I have implemented the ValueChanged event handler correctly either:
EditedAlgorithm.RaBucket1Type = (RaTypes)((ComboBox)sender).SelectedItem;
Can someone help me understand:
How to set the combobox to current value, and
How to handle the event handler so I can set the property to whatever was selected?
Thanks
-Ed
UPDATES
I have tried
cmbBucket1Type.SelectedIndex = cmbBucket1Type.FindString(EditedAlgorithm.RaBucket1Type.ToString());
and
cmbBucket1Type.SelectedItem = EditedAlgorithm.RaBucket1Type;
Neither works.

I think you're using the terminology a little differently than normal, which makes it difficult to understand.
Normally, the terms Add, Populate, and Select are used to mean the following:
Add - Add an item to the existing set of items in the combo box.
Populate - Initialize the combo box with a set of items.
Select (Display) - Choose one among many items in the combo box as the selected item. Normally this item will be displayed in the combo box visible area.
Having cleared that up, I assume following is what you want to do.
Initially populate the ComboBox with a set of values. In your case, values of RaType Enum.
Create an instance of your class which contains the property mentioned. Since you didn't name that class I'll simply name it SomeClass.
Initialize the RaBucket1Type property of the said class instance with an enum value of your choice. I'll initialize it to Yellow.
Have the ComboBox select the said value at start up.
After Form_Load, at any given time, if the user changes the value of the ComboBox, have the change reflected in your class instance property.
For that, I would do something like this:
public partial class MainForm : Form
{
// Your class instance.
private SomeClass InstanceOfSomeClass = null;
public MainForm()
{
InitializeComponent();
// Initialize the RaBucket1Type property with Yellow.
InstanceOfSomeClass = new SomeClass(RaTypes.Yellow);
// Populating the ComboBox
comboBox1.DataSource = Enum.GetValues(typeof(RaTypes));
}
// At selected index changed event
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
// Get the selected value.
var selected = comboBox1.SelectedValue;
// Change the `RaBucket1Type` value of the class instance according to the user choice.
InstanceOfSomeClass.RaBucket1Type = (RaTypes)selected;
}
private void MainForm_Load(object sender, EventArgs e)
{
// At form load time, set the `SelectedItem` of the `ComboBox` to the value of `RaBucket1Type` of your class instance.
// Since we initialized it to `Yellow`, the `ComboBox` will show `Yellow` as the selected item at load time.
if (InstanceOfSomeClass != null)
{
comboBox1.SelectedItem = InstanceOfSomeClass.RaBucket1Type;
}
}
}
public enum RaTypes
{
Red,
Yellow
}
public class SomeClass
{
public RaTypes RaBucket1Type { get; set; }
public SomeClass(RaTypes raTypes) { RaBucket1Type = raTypes; }
}
Please do keep in mind this is a basic example to show you how to handle the situation and not a complete finished code. You'll need to do a bunch of error checks to make sure class instances and selected items are not null etc.

I FOUND MY ANSWER:
I had the SelectedIndexChanged event pointing to my event handler which means that when I "added" items to the ComboBox using:
comboBox1.DataSource = Enum.GetValues(typeof(RaTypes));
it was triggering the event handler, and resetting my class property. My event handler was this:
var selectedValue = cmbBucket1Type.SelectedValue;
So the simple solution was to:
Remove the hard-coded event handler from the Visual Studio GUI.
Add the following event handler in code AFTER I assign the DataSource
bucketType1.SelectedIndexChanged += BucketTypeChanged;
This worked.
THANK YOU ALL FOR HELPING!!
-Ed

You can set the selectedValue like this:
cmbBucket1Type.SelectedValue = EditedAlgorithm.RaBucket1Type;
And you can handle the selected value when the combo change like this:
private void cmbBucket1Type_SelectedValueChanged(object sender, EventArgs e)
{
var selectedValue = cmbBucket1Type.SelectedValue;
}

Related

How can I have a WinForms TreeView update when the tagged objects change?

I have a WinForms TreeView to represent my calculator data. When I read my data in, I create my TreeView and tag each TreeView node with the corresponding object. Below is a sample TreeView.
SUM
SUM
TIMES
UnitsSold
UnitPrice
IncomeFromContracts
NEGATIVE SUM
Rent
Wages
TIMES
UnitsSold
UnitMaterialCost
I create the TreeView from a data structure such as:
class Operand {
bool IsNegated {get; set;}
}
class Operator : Operand {
enum OperatorEnum { SUM, DIFFERENCE, TIMES }
OperatorEnum MyEnum {get; set;}
IList<Operand> Children {get; set;}
}
class VariableName : Operand {
string Name {get; set;}
}
Each TreeNode is tagged with the corresponding Operand. When an item in the TreeView is selected, the tagged item is exposed in a panel so that you can change its properties. E.g. when I select an item, you get a checkbox where you can set the IsNegated value. I have successfully bound that control to the Operand object using:
negateCheckBox.DataBindings.Add("Checked", requirementTree.SelectedNode.Tag, "IsNegated");
Clicking the checkbox appropriately updates the underlying data, but it doesn't cause the TreeView to refresh its data, so even though the return value of the item's ToString() method has changed (it now has "NEGATIVE" prefixed), because the TreeNode was already created with that string, it doesn't update.
I also have some "Add" and "Remove" buttons which make additional nodes in the TreeView and remove them, but these all function manually -- update the underlying data structures, then rebuild the entire TreeView (this causes the view to refresh, so I can verify my other bindings are working).
What steps do I need to take so that the TreeView isn't populated manually, but is fully tied to my data structure, so that changes like this are immediately reflected? It seems like there are numerous ways to bind data in WinForms and I am unclear as to which apply in which situations.
Thanks so much for the help!
Based on Reza's comment that data binding is not supported for TreeView, I came up with a reasonable workaround.
Each "model" class implements INotifyPropertyChanged and raises the event when any of its properties change.
class Operand : INotifyPropertyChanged
{
public bool IsNegated
{
get { return m_isNegated; }
set { m_isNegated = value; RaisePropertyChanged(); }
}
private bool m_isNegated = false;
private void RaisePropertyChanged([CallerMemberName]string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
I then added a dummy controller which links the model to the TreeView node:
public class OperandTreeNodeController
{
private Requirement m_model;
private TreeNode m_view;
public OperandTreeNodeController(Requirement model, TreeNode view)
{
m_model = model;
m_view = view;
m_model.PropertyChanged += Model_PropertyChanged;
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
m_view.Text = m_model.ToString();
}
}
Now, whenever I create a TreeNode, I also create a controller. There's a bit of a memory "leak" here because these can keep being created and appended to the model's PropertyChanged event and the garbage collector may never clean them up, but I've handled that manually in my code by adding a Destroy() function and tracking/destroying them manually.
This seems to accomplish most of my goals, so I'm going to stick with it unless someone has another idea.

Using a DataSource with an object in Windows Forms

I have been trying to create a small form application and I wanted to try out binding a DataGridView directly to a collection of objects.
I created the following classes
public class MyClassRepository
{
public List<MyClass> MyClassList { get; set; } = new List<MyClass> { new MyClass { Name = "Test" } };
}
public class MyClass
{
public string Name { get; set; }
}
and I added the following code to a form to test. I based this off of the code in the designer after setting the BindingSource through the UI (while following this walk through https://msdn.microsoft.com/en-us/library/ms171892.aspx)
var tmp = new BindingSource();
tmp.DataMember = "MyClassList";
tmp.DataSource = typeof(MyClassRepository);
When this didn't work I started running through the code behind BindingSource to see what was happening. The setter calls ResetList which tries to create a dataSourceInstance by calling ListBindingHelper.GetListFromType. This call ultimately calls SecurityUtils.SecureCreateInstance(Type) where type is a BindingList<MyClassRepository>. This passes null to args which is passed Activator.CreateInstance which returns an empty collection.
After this ListBindingHelper.GetList(dataSourceInstance, this.dataMember) is called. This method calls ListBindingHelper.GetListItemProperties which results in a PropertyDescriptor for my MyClassList property and assigns it to dmProp.
At this point GetList calls GetFirstItemByEnumerable(dataSource as IEnumerable) where dataSource is the previously created (and empty) instance of BindingList<MyClassRepository> and returns (currentItem == null) ? null : dmProp.GetValue(currentItem);.
The value of dmProp/MyClassList is never accessed and the BindingSource is never populated with the instance I created. Am I doing something wrong? If not is there a bug in the source code? It seems to me like either SecureCreateInstance(Type type, object[] args) should be called and MyClassList should be passed via args instead of the existing call to SecureCreateInstance(Type type) or the value of dmProp should be used regardless?
If that is not correct how do I make the Designers automatically generated code set the DataSource to an instance of the object? Or do I have to inherit from BindingSource? If the latter why does it give you the option to choose a class that does not inherit from BindingSource?
As Reza Aghaei points out, in the designer, setting the BindingSource.DataSource to the “MyClassRepository” may work, however you still need to initialize (create a new) MyClassRepository object. I do not see this line of code anywhere in the posted code: MyClassRepository myRepositiory = new MyClassRepository(); The data source is empty because you have not created an instance of “MyClassRepository” and as Reza points out, this is usually done in the forms Load event.
To keep it simple, remove the DataSource for the BindingSource in the designer and simply set up the BindingSource’s data source in the form load event like below. First, create a new “instance” of the MyClassRepository then use its MyClassList property as a data source to the BindingSource. I hope this helps.
MyClassRepository repOfMyClass;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
repOfMyClass = new MyClassRepository();
bindingSource1.DataSource = repOfMyClass.MyClassList;
dataGridView1.DataSource = bindingSource1;
}
Edit-----
After further review… I agree that you should be able to do as you describe. I was able to get it working as expected with the code below.
BindingSource bindingSource1;
private void Form1_Load(object sender, EventArgs e) {
bindingSource1 = new BindingSource();
bindingSource1.DataSource = typeof(MyClassRepository);
bindingSource1.DataMember = "MyClassList";
dataGridView1.DataSource = bindingSource1;
}
I followed the same steps in the “designer” and it worked as expected. Is there something else I am missing? As you stated… using MyClassRepository mcr = new MyClassRepository() appears to be unnecessary. In addition, if you cannot get it to work using one of the two ways above… then something else is going on. If it does not work as above, what happens?
Edit 2
Without creating a “new” MyClassRepository, object was unexpected and I did not realize that new items added to the list/grid were going into the bindingSource1. The main point, is that without instantiating a “new” MyClassRepository object, the constructor will never run. This means that the property List<MyClass> MyClassList will never get instantiated. Nor will the default values get set.
Therefore, the MyClassList variable will be inaccessible in this context. Example in this particular case, if rows are added to the grid, then bindingSource1.Count property would return the correct number of rows. Not only will the row count be zero (0) in MyClassList but also more importantly… is “how” would you even access the MyClassList property without first instantiating a “new” MyClassRepository object? Because of this inaccessibility, MyClassList will never be used.
Edit 3 ---
What you are trying to achieve can be done in a myriad number of ways. Using your code and my posted code will not work if you want MyClassList to contain the real time changes made in the grid by the user. Example, in your code and mine… if the user adds a row to the grid, it will add that item to the “bindingSource” but it will NOT add it to MyClassList. I can only guess this is not what you want. Otherwise, what is the purpose of MyClassList. The code below “will” use MyClassList as expected. If you drop the “designer” perspective… you can do the same thing in three (3) lines of code... IF you fix the broken MyClassRepository class and create a new one on the form load event. IMHO this is much easier than fiddling with the designer.
Changes to MyClassRepository… added a constructor, added a size property and a method as an example.
class MyClassRepository {
public List<MyClass> MyClassList { get; set; }
public int MaxSize { get; set; }
public MyClassRepository() {
MyClassList = new List<MyClass>();
MaxSize = 1000;
}
public void MyClassListSize() {
MessageBox.Show("MyClassList.Count: " + MyClassList.Count);
}
// other list manager methods....
}
Changes to MyClass… added a property as an example.
class MyClass {
public string Name { get; set; }
public string Age { get; set; }
}
Finaly, the form load event to create a new MyClassRepository, set up the binding source to point to MyClassList and lastly set the binding source as a data source to the grid. NOTE: making myClassRepository and gridBindingSource global variables is unnecessary and is set this way to check that MyClassList is updated in real time with what the user does in the grid. This is done in the button click event below.
MyClassRepository myClassRepository;
BindingSource gridBindingSource;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
try {
myClassRepository = new MyClassRepository();
gridBindingSource = new BindingSource(myClassRepository.MyClassList, "");
dataGridView1.DataSource = gridBindingSource;
}
catch (Exception ex) {
MessageBox.Show("Error: " + ex.Message);
}
}
private void button1_Click_1(object sender, EventArgs e) {
MessageBox.Show("Binding source count:" + gridBindingSource.Count + Environment.NewLine +
"MyClassList count: " + myClassRepository.MyClassList.Count);
}
I hope this makes sense. ;-)
Designer sets DataSource = typeof(Something) for design-time support, for example to let you choose DataMember from a dropdown or to let you choose the data source property from dropdown while setting up data-bindings.
How do I make the Designers automatically generated code set the
DataSource to an instance of the object?
Forcing the designer to do that doesn't make much sense, because the designer doesn't have any idea about what the real data source you are going to use to load data. It can be a web service, a WCF service, a business logic layer class.
So at run-time you need to assign an instance of your list to DataSource. For example in Load event of the form.

how to change class member visibility?

I have a class with a combobox inside. I want to add items to this combobox from a different class, but I cannot see it.
I've instantiated the class (with the combobox) using 'new', i.e.:
check_reg _check_reg = new check_reg();
but in my second class I see only the
_form1.Choose_Quar_SelectedIndexChanged paramter, which is the handler shown when I double click the combo box in the form, it does not help me add the items.
I'm sure it's a basic question... so please help me with it.
Thx!
For sure you can create a public instance method inside the class "check_reg" to add items to the combobox.
Something like this:
public void AddItem(ListItem li)
{
ddl.Items.Add(li);
}
And you can use it like this:
check_reg _check_reg = new check_reg();
_check_reg.AddItem(new ListItem("Text", "Value"));
Hope this helps.
Cheers
You should use your combobox as a property of your class with public modifier
So first thing go to your YourPage.designer.cs and remove the declaration of the combobox e shift it to the code behind of the page.
change from
protected global::System.Web.UI.HtmlControls.HtmlGenericControl combobox;
to
public global::System.Web.UI.HtmlControls.HtmlGenericControl combobox;
after this you will be able to see the combobox as a public property of the class where it is declared

Simple Windows Forms data binding

Let's say I have a String property in my form (and the form does not implement INotifyPropertyChanged). I've also created a BindingSource and set its DataSource to the form. Then I bind a textbox to the String property on my form (indirectly, using the BindingSource).
Question 1: When I change the value in the textbox at runtime, why don't I hit a breakpoint in the setter of the String property? I thought that binding the control to the String property would allow updates in this direction (GUI -> member data) to occur automatically
Question 2: How can I trigger updates in the other direction (member data -> GUI) to occur when something other than the GUI changes the String property? I don't want to implement the INotifyPropertyChanged interface and add NotifyPropertyChanged to the setter. I thought that by using the BindingSource's ResetBindings I could at least trigger this manually
public partial class Form1 : Form
{
private String m_blah;
public String Blah
{
get
{
return m_blah;
}
set
{
m_blah = value;
}
}
public Form1()
{
InitializeComponent();
textBox1.DataBindings.Add(new Binding("Text", bindingSource1, "Blah",true,DataSourceUpdateMode.OnValidation));
}
private void button1_Click(object sender, EventArgs e)
{
Blah = "Clicked!";
this.bindingSource1.ResetBindings(false); //expecting the GUI to update and say "Clicked!"
}
}
this.bindingSource1.DataSource = this;
I think you forget to assign data source.

Property setter not getting called with CollectionEditor

I have a custom control that has an Items property. I Have applied an EditorAttribute with a UITypeEditor of type CollectionEditor.
Collection Type:
[Serializable]
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public class ListItemsCollection : CollectionBase
{
// methods
}
Property Declaration In The Control:
private new ListItemsCollection _Items;
[Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
public new ListItemsCollection Items
{
get
{
return _Items;
}
set
{
_Items = value;
// do other UI changes
}
}
Problem:
When I drop this control to the designer surface, I am able to add items to the Items property using the PropertyGrid. But, the when I click the Ok button of the CollectionEditor the setter of the Items property is not getting called.
AFAIK when a value is returned from the EditValue method of a UITypeEditor class the setter block of the property is supposed to be called.
This is driving me insane. I even tried adding Event's to the ListItemsCollection, so that when Items are added, I can whatever I want with the control's ui.
This is not supposed to be hard. What am I doing wrong?
I try to reprodeuce your situation: using following code, I get a message box showing whenever I edit the list from VS property window. Beware that you have to create the list by yourself. If you don't create it, VS create a temp list which you can edit from property window, but does not set your property to this list (so your setter will never be called)
public UserControl1()
{
InitializeComponent();
list = new BindingList<ListViewItem>();
list.ListChanged += new ListChangedEventHandler(list_ListChanged);
}
void list_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show(e.ListChangedType.ToString());
}
private BindingList<ListViewItem> list;
public BindingList<ListViewItem> List1
{
get { return list; }
}
Collection properties should be read-only. It's the collection that is retrieved through the getter, and adjusted. The setter never enters into it, because that would mean setting a new collection.

Categories

Resources