Custom group box not binding to bindingsource - c#

I need to bind a GroupBox to a BindingSource, which in turn is bound to the following object:
public class CustomerType
{
public int Id {get; set;}
public string Name {get; set;}
public MemberType MemberType {get; set;}
}
public enum MemberType {Adult, Child}
I followed this answer to create a custom GroupBox. I also set the data bindings as follows:
groupBoxMemberType.DataBindings.Add("Selected", this.bindingSource, "MemberType");
However, when loading an existing object, I get the following exception:
DataBinding cannot find a row in the list that is suitable for all bindings.
The exception occurs when setting the data source:
customerType = customerTypeRequest.Load(id);
bindingSource.DataSource = customerType; //raises exception
What am I missing? Is there an alternative to get radio buttons to bind to a datasource, specifically a BindingSource?

This is the changed code:
[DefaultBindingProperty("Selected")]
public class RadioGroupBox : GroupBox
{
#region events
[Description("Occurs when the selected value changes.")]
public event SelectedChangedEventHandler SelectedChanged;
public class SelectedChangedEventArgs : EventArgs
{
public int Selected { get; private set; }
internal SelectedChangedEventArgs(int selected)
{
this.Selected = selected;
}
}
public delegate void SelectedChangedEventHandler(object sender, SelectedChangedEventArgs e);
#endregion
private int selected;
[Browsable(false)]
[Bindable(BindableSupport.Yes, BindingDirection.TwoWay)]
[Description("The selected value associated with this control."), Category("Data")]
public int Selected
{
get { return selected; }
set
{
int val = 0;
var radioButton = this.Controls.OfType<RadioButton>()
.FirstOrDefault(radio =>
radio.Tag != null
&& int.TryParse(radio.Tag.ToString(), out val) && val == value);
if (radioButton != null)
{
radioButton.Checked = true;
selected = val;
}
}
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
var radioButton = e.Control as RadioButton;
if (radioButton != null)
radioButton.CheckedChanged += radioButton_CheckedChanged;
}
protected void OnSelectedChanged(SelectedChangedEventArgs e)
{
if (SelectedChanged != null)
SelectedChanged(this, e);
}
private void radioButton_CheckedChanged(object sender, EventArgs e)
{
var radio = (RadioButton)sender;
int val = 0;
if (radio.Checked && radio.Tag != null
&& int.TryParse(radio.Tag.ToString(), out val))
{
selected = val;
OnSelectedChanged(new SelectedChangedEventArgs(selected));
}
}
}
Further to setting the Tag property to the corresponding int value of the enum, you need to subscribe to the SelectedChanged event in your form, eg:
private void radioGroupBoxMemberType_SelectedChanged(object sender, SelectedChangedEventArgs e)
{
customerType.MemberType = (MemberType)e.Selected;
}
Improvements to this class would be:
Inherit from RadioButton and use a new property instead of the Tag property.
Access and set the bindingsource property directly in the control to avoid subscribing to the event.

Related

c# Wpf mvvm - combo box with select all option

I have datagrid of objects (CopyObject). Each object contains combo box with list of another objects (PGroupGridObject) and a checkbox.
To each PGroupGridObject list in CopyObject i add dummy PGroupGridObject with the name "All".
In PGroupGridObject class i add an event that triggered if the "All" group is checked.
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
if (PGroupName.Equals("All"))
{
pGroupGridObjectEvent.pGroupGridObject = this;
MyCustomEvent?.Invoke(this, pGroupGridObjectEvent);
}
}
}
the event in PGroupGridObject:
public delegate void MyEventHandlerPGroup(object sender, MyEventArgsPGroup args);
public class MyEventArgsPGroup : EventArgs
{
public PGroupGridObject pGroupGridObject { get; set; }
}
public class PGroupGridObject : ViewModelBase
{
public string coName;
public event EventHandler<MyEventArgsPGroup> MyCustomEvent;
MyEventArgsPGroup pGroupGridObjectEvent = new MyEventArgsPGroup();
...
...
}
In CopyObject constructor I'm checking this event when add groups to the list.
foreach (PGroupGridObject pGroup in pGroups)
{
PGroupGridObject p = new PGroupGridObject(pGroup.Object);
p.MyCustomEvent += (o, e) =>
{
foreach (PGroupGridObject curpg in pGroups)
curpg.IsChecked = true;
};
this.PGroups.Add(p);
}
When i'm debugging it i can see that at the end of the action the groups IsChecked field is set to true , but i don't see it in the grid. seems that the OnPropertyChanged("IsChecked") not working in that case.
What i'm missing here ?

USer Control Custom event and set get property

I have 2 labels and a property in user control:
Here is property:
private int _SelectIndex;
[Browsable(true)]
public int SelectIndex { get; set; }
and 2 labels:
Label lbl1, lbl2;
void iControl()
{
lbl1 = new Label();
lbl2 = new Label();
lbl1.Name = "lbl1";
lbl2.Name = "lbl2";
lbl1.Click += lbl_Click;
lbl2.Click += lbl_Click;
this.Controls.Add(lbl1);
this.Controls.Add(lbl2);
}
Click:
void lbl_Click(object sender, EventArgs e)
{
Label selectedlbl = sender as Label;
if(selectedlbl.Name == "lbl1")
this.Select = 1;
else
this.Select = 2;
}
Class Event:
public class SelectEventArgs : EventArgs
{
private int index;
public SelectEventArgs(int index)
{
this.index = index;
}
public int ItemIndex
{
get
{
return index;
}
}
}
Custom event in my control:
public event EventHandler SelectEvent;
protected virtual void OnSelectEvent()
{
if (SelectEvent!= null)
SelectEvent(this, new SelectEventArgs(this._SelectIndex));
}
I need an event to get and set property value in MainForm as following:
int index = 0;
public Form1()
{
InitializeComponent();
this.icontrol = new iControl();
this.SelectEvent += Select();
}
void Select(object sender, SelectItem e)
{
//use this to set value of Select
this.icontrol.SelectIndex = index;
//and this to get value of Select
index = this.icontrol.SelectIndex;
}
Select is empty.
How to get it to work?
I post here for any one need it:
1.Declare a delegate:
public delegate void SelectIndexEventHandler(object sender, SelectEventArgs e);
public class SelectEventArgs : EventArgs
{
private int index;
public SelectEventArgs(int index)
{
this.index = index;
}
public int ItemIndex
{
get { return index; }
set { index = value; }
}
}
2. declare an event SelectIndexChanged and a method OnSelectIndexChanged:
public event SelectIndexEventHandler SelectIndexChanged;
protected virtual void OnSelectIndexChanged(SelectEventArgs e)
{
if (SelectIndexChanged != null)
SelectIndexChanged(this, e);
}
3.Call it in setter:
public int SelectIndex
{
get { return _SelectIndex; }
set {
_SelectIndex = value;
OnSelectIndexChanged(new SelectEventArgs(value));
}
}
and then MainForm:
this.gListBox1.SelectIndexChanged += icontrol_SelectIndexChanged;
void icontrol_SelectIndexChanged(object sender, SelectEventArgs e)
{
var current = e.ItemIndex;
}
thank again jbmintjb Reza Aghaei.
The code has multiple issues. Consider these tips to solve the issues:
SelecetEvent does't belong to the Form. The event belongs to icontrol.
this.SelectEvent += Select(); is incorrect, you should use:
icontrol.SelectEvent += Select;
When you have a custom event args, you should define the event this way:
public event EventHandler<SelectEventArgs> SelectEvent;
You should raise the event in setter of your property, using OnSelectEvent method which you created.
To learn more about events take a look at C# Handling and Raising Events.
Take a look at the SelectedIndexChanged event on the listbox control, think that is what you are looking for

How do I change boolean properties with one click in PropertyGrid

We have a windows form PropertyGrid that we use to display all the properties. We have drawn a checkbox on Boolean property that checks it self and unchecks itself based on the value. this all works fine.
the issue is, that user wants to change the check box value in single click, whereas property grid changes it on a double click and I cant figure out a way to handle clicks or change property value on single click when property type is Boolean.
How to change property value in single click?
PropertyGrid internally has methods which allows us to use them with reflection to get the GridItem under mouse when you click on its PropertyGridView internal control.
In below code, I handled mouse click on its PropertyGridView control and checked if the item under mouse position is a boolean property, I reversed it's value. The event will fire for the label of property, also for icon area of the property editor:
PropertyGrid
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
public class ExPropertyGrid : PropertyGrid
{
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
var grid = this.Controls[2];
grid.MouseClick += grid_MouseClick;
}
void grid_MouseClick(object sender, MouseEventArgs e)
{
var grid = this.Controls[2];
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
var invalidPoint = new Point(-2147483648, -2147483648);
var FindPosition = grid.GetType().GetMethod("FindPosition", flags);
var p = (Point)FindPosition.Invoke(grid, new object[] { e.X, e.Y });
GridItem entry = null;
if (p != invalidPoint) {
var GetGridEntryFromRow = grid.GetType()
.GetMethod("GetGridEntryFromRow", flags);
entry = (GridItem)GetGridEntryFromRow.Invoke(grid, new object[] { p.Y });
}
if (entry != null && entry.Value != null) {
object parent;
if (entry.Parent != null && entry.Parent.Value != null)
parent = entry.Parent.Value;
else
parent = this.SelectedObject;
if (entry.Value != null && entry.Value is bool) {
entry.PropertyDescriptor.SetValue(parent,!(bool)entry.Value);
this.Refresh();
}
}
}
}
Drawing CheckBox in PropertyGrid
public class MyBoolEditor : UITypeEditor
{
public override bool GetPaintValueSupported
(System.ComponentModel.ITypeDescriptorContext context)
{ return true; }
public override void PaintValue(PaintValueEventArgs e)
{
var rect = e.Bounds;
rect.Inflate(1, 1);
ControlPaint.DrawCheckBox(e.Graphics, rect, ButtonState.Flat |
(((bool)e.Value) ? ButtonState.Checked : ButtonState.Normal));
}
}
Class which used in screenshot
public class Model
{
public int Property1 { get; set; }
[Editor(typeof(MyBoolEditor), typeof(UITypeEditor))]
public bool Property2 { get; set; }
[TypeConverter(typeof(ExpandableObjectConverter))]
public Model Property3 { get; set; }
}
I'd like to comment, but rep isn't high enough yet.
The accepted answer works great. However as mentioned the code doesn't trigger the PropertyValueChanged event.
Adding a call to OnPropertyValueChanged triggers the PropertyValueChanged event.
entry.PropertyDescriptor.SetValue(parent, !(bool)entry.Value);
this.Refresh();
base.OnPropertyValueChanged(null);
Then in the PropertyValueChanged event code you can access the custom object that has been changed.
To communicate the changed property back to the form create some properties in the custom object, with Browsable set to false so they do not appear in the PropertyGrid.
[Browsable(false)]
public string changedParent { get; set; }
[Browsable(false)]
public string changedLabel { get; set; }
[Browsable(false)]
public string changedValue { get; set; }
At the top of the Form class create this static property
public partial class Form1 : Form
{
private static Form1 form = null;
In the constructor of Form1 link form to this.
public Form1()
{
InitializeComponent();
..
..
form = this;
Back in grid_MouseClick before triggering OnPropertyValueChanged save off the changed property information.
entry.PropertyDescriptor.SetValue(parent, !(bool)entry.Value);
this.Refresh();
form.sh.changedParent = entry.Parent.Label;
form.sh.changedLabel = entry.Label;
form.sh.changedValue = entry.Value.ToString();
base.OnPropertyValueChanged(null);
Now in the PropertyValueChanged event code you can determine which property was changed.
form.customobject.changedParent
form.customobject.changedLabel
form.customobject.changedValue
The best answer used a reflection to get GridItem under the mouse. However, I don't see the point in doing this since it's enough to request a dedicated GridItem. Here is my implementation of MouseClick:
void grid_MouseClick(object sender, MouseEventArgs e)
{
GridItem entry = SelectedGridItem;
if (entry != null && entry.Value != null && entry.Value is bool b)
{
var obj = SelectedObjects.Length == 1 ? SelectedObject : SelectedObjects;
entry.PropertyDescriptor.SetValue(obj, !b);
}
}
The above code from Reza Aghaei work long time but now changed the list of controls.
void grid_MouseClick(object sender, MouseEventArgs e)
{
var grid = this.Controls[2]; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
var invalidPoint = new Point(-2147483648, -2147483648);
///// following line throws a Nullreference Exception
var FindPosition = grid.GetType().GetMethod("FindPosition", flags);
var p = (Point)FindPosition.Invoke(grid, new object[] { e.X, e.Y });
GridItem entry = null;
...
}
Now you need to select the right control(PropertyGridView).
Below my solution.
int idx = -1;
for (int i = 0; i < this.Controls.Count; i++)
{
Control control = this.Controls[i];
if (control.Text.Contains("PropertyGridView"))
{
idx = i;
break;
}
}
var grid = this.Controls[idx];

Manipulating collection from the inside

I have a collection of panels which are highlighted when user clicks on them. I want to force them to behave as a set of radio buttons so only the one that is clicked on is highlighted and others aren't.
I guess that there must be a way to manipulate whole collection (set property to false) from the inside, because the event is triggered by one item from the collection. Is there a way for the one item to manipulate whole collection? This is such a common feature in applications so I guess there must be a pattern how to do it properly. Thanks.
You may store collection of your panels and handle required functionality as in following code snippet:
List<Panel> Panels;
private void Initialization()
{
Panels = new List<Panel>();
Panels.Add(pnl1);
Panels.Add(pnl2);
//add all your panels into collection
foreach(Panel Item in this.Panels)
{
//add handle to panel on click event
Item.Click += OnPanelClick;
}
}
private void OnPanelClick(object sender, EventArgs e)
{
foreach(Panel Item in this.Panels)
{
//remove highlight from your panels, real property should have other name than Panel.HighlightEnabled
Item.HighlightEnabled = false;
}
((Panel)sender).HighlightEnabled = true; //add highlight to Panel which invoked Click event
Application.DoEvents(); //ensure that graphics redraw is completed immediately
}
private void AddNewPanelIntoLocalCollection(Panel panel)
{
//here you can add new items to collection during program lifecycle
panel.Click += OnPanelClick;
this.Panels.Add(panel);
}
This is how I do it
public class SelectOne : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private bool isSelected = false;
private HashSet<SelectOne> selecteOnes = null;
public bool IsSelected
{
get { return isSelected; }
set
{
if (isSelected == value) return;
if (isSelected && selecteOnes != null)
{
foreach (SelectOne so in selecteOnes)
{
if (so == this) continue;
so.IsSelected = false;
}
}
NotifyPropertyChanged("IsSelected");
}
}
public SelectOne() { }
public SelectOne(bool IsSelected) { isSelected = IsSelected; }
public SelectedOne(bool IsSelected, HashSet<SelectOne> SelecteOnes)
{
isSelected = IsSelected;
selecteOnes = SelecteOnes;
}
}
Eventually I did find a way to do this properly with only one delegate.
In class A I have a collection of objects B
List<B> b = new List<B>
class B, needs to have an unique ID and delegete for void metod with Id parameter
delegate void DeleteItemDelegate(int id);
class B
{
public int ID {get; set;}
public DeleteItemDeleate deleteThis {set; get;}
}
class A has a metod like this:
public void RemoveItem(int id)
{
for (int x = 0; x < b.Count; x++)
{
if (b[x].id == id)
{
b.RemoveAt(x);
}
}
}
when adding a new B object into List just add metod RemoveItem to B.deleteThis delegate
B bObject = new B();
bObject.deleteThis = RemoveItem;
b.Add(bObject);
Now all you need to do is add DeleteMe metod in B class
void DeleteMe()
{
// and call local delegate - pointing to metod which actually can manipulate the collection
deleteThis(id);
}

CheckedListBox with search function not checking correctly

I'm having strange issues with the check box control in C# .Net
My code below shows all logic that is required - _itemsChecked is a private dictionary containing all of the _fixtures and whether they are true or false (checked or un checked)
What I want is to be able to search my check list whilst retaining those which have been checked previously. If a checked item is included in the search results I want it to be checked.
The code nearly works! But for some reason boxes are randomly checked here and there, and it appears to work through debug but when the screen returns to the control it then hasn't worked.
Sure I'm missing something very simple.
My logic is:
DataSource includes those which match the typed search query,
Iterate through this list and check if the Guid is true in the dictionary.
If it is true then we set it as checked.
Hope I have provided adequate information.
Many thanks in advance.
private void searchTextBox_KeyUp(object sender, EventArgs e)
{
lst.DataSource = _fixtures
.OrderBy(f =>
f.Description)
.Where(f =>
f.Description.ToLower().Contains(searchFixturesTextBox.Text.ToLower()))
.ToList();
lst.DisplayMember = "Description";
for (var i = 0; i < lst.Items.Count; i++)
if(_itemsChecked.Contains(new KeyValuePair<Guid, bool>(((Fixture)lst.Items[i]).Guid, true)))
lst.SetItemChecked(i, true);
}
void lst_ItemCheck(object sender, ItemCheckEventArgs e)
{
var selectedItem = ((ListBox) sender).SelectedItem as Fixture;
if (selectedFixtureItem != null)
_itemsChecked[selectedItem.Guid] = e.CurrentValue == CheckState.Unchecked;
}
So I put this together from a few examples I found. The majority of the work came from How do I make a ListBox refresh its item text?
public class Employee
{
public string Name { get; set; }
public int Id { get; set; }
public bool IsChecked { get; set; }
public override string ToString()
{
return Name;
}
}
public partial class Form1 : Form
{
// Keep a bindable list of employees
private BindingList<Employee> _employees;
public Form1()
{
InitializeComponent();
// Load some fake employees on load
this.Load += new EventHandler(Form1_Load);
// Click once to trigger checkbox changes
checkedListBox1.CheckOnClick = true;
// Look for item check change events (to update there check property)
checkedListBox1.ItemCheck +=
new ItemCheckEventHandler(CheckedListBox_ItemCheck);
}
// Load some fake data
private void Form1_Load(object sender, EventArgs e)
{
_employees = new BindingList<Employee>();
for (int i = 0; i < 10; i++)
{
_employees.Add(new Employee()
{ Id = i, Name = "Employee " + i.ToString() });
}
// Display member doesnt seem to work, so using ToString override instead
//checkedListBox1.DisplayMember = "Name";
//checkedListBox1.ValueMember = "Name";
checkedListBox1.DataSource = _employees;
// Another example databind to show selection changes
txtId.DataBindings.Add("Text", _employees, "Id");
txtName.DataBindings.Add("Text", _employees, "Name");
}
// Item check changed, update the Employee IsChecked property
private void CheckedListBox_ItemCheck(object sender, ItemCheckEventArgs e)
{
CheckedListBox clb = sender as CheckedListBox;
if (clb != null)
{
Employee checked_employee = clb.Items[e.Index] as Employee;
if (checked_employee != null)
{
checked_employee.IsChecked = (e.NewValue == CheckState.Checked);
}
}
}
// Just a simple test that removes an item from the list, rebinds it
// and updates the selected values
private void btnChangeList_Click(object sender, EventArgs e)
{
_employees.RemoveAt(1);
checkedListBox1.DataSource = _employees;
for (var i = 0; i < checkedListBox1.Items.Count; i++)
{
Employee employee_to_check = checkedListBox1.Items[i] as Employee;
if (employee_to_check != null)
{
checkedListBox1.SetItemChecked(i, employee_to_check.IsChecked);
}
}
}
}

Categories