i want to find an index of a selected RadioButton in RadioGroup. I attached next single method to each RadioButton in the group:
private void radio_button_CheckedChanged(object sender, EventArgs e){
if (sender.GetType() != typeof(RadioButton)) return;
if (((RadioButton)sender).Checked){
int ndx = my_radio_group.Controls.IndexOf((Control)sender);
// change something based on the ndx
}
}
It is important to me that lower radioButton must have lower index, starting from zero. And seems it is working, but i am not sure if this is a good solution. Maybe there is more betufilul way to do the same.
This will give you the Checked RadioButton:
private void radioButtons_CheckedChanged(object sender, EventArgs e)
{
RadioButton rb = sender as RadioButton;
if (rb.Checked)
{
Console.WriteLine(rb.Text);
}
}
Any indices in the Controls collection of its Parent are highly volatile.
You could access it like this: rb.Parent.Controls.IndexOf(rb)
If you want a relatively stable ID besides the Name and the Text you can put it in the Tag.
Obviously you need to hook up this event to all the RadionButtons in the group.
No type checks are really necessary (or imo recommended,) as only a RadioButton can (or rather: must ever) trigger this event.
To obtain index ideally you want to have controls arranged as collection. If you can add controls from code behind than that's as easy as
List<RadionButton> _buttons = new List<RadioButton>();
_buttons.Add(new RadioButton() { ... });
_buttons.Add(new RadioButton() { ... });
...
If you want to use form designed, then perhaps creating this list in form constructor is an alternative:
List<RadioButtons> _list = new List<RadioButton>();
public Form1()
{
InitializeComponent();
_list.Add(radioButton1);
_list.Add(radioButton2);
...
}
Then the actual task to obtain index is as simple as:
void radioButton_CheckedChanged(object sender, EventArgs e)
{
var index = _list.IndexOf(sender);
...
}
//----checked change----
private void radioButtons_CheckedChanged(object sender, EventArgs e)
{
int ndx = 0;
var buttons = RdoGroup.Controls.OfType<RadioButton>()
.FirstOrDefault(n => n.Checked);
//-----in initialize set radioButton tags : this.radioButton1.Tag = "1";------
if (buttons.Tag != null) ndx=Convert.ToInt32( buttons.Tag.ToString());
//--------do some thing by index----------
}
Related
I wrote some code to create an additional textbox during runtime. I'm using the metro framework, but this shouldn't matter for my question.
When you click a button, a textbox is being created by a private on_click event:
private void BtnAddButton_Click(object sender, EventArgs e)
{
MetroFramework.Controls.MetroTextBox Textbox2 = new MetroFramework.Controls.MetroTextBox
{
Location = new System.Drawing.Point(98, lblHandy.Location.Y - 30),
Name = "Textbox2",
Size = new System.Drawing.Size(75, 23),
TabIndex = 1
};
this.Controls.Add(Textbox2);
}
What I want to do now is to use the click event of another button, to remove the Textbox again. What I am not sure about is, if I have to remove just the controll or also the object itself. Furthermore I can neither access the Textbox2 Control nor the object from another place.
private void BtnRemoveTextbox2_Click(object sender, EventArgs e)
{
this.Controls.Remove(Textbox2);
}
This does not work, since the other form does not know about Textbox2. What would be the best way to achieve my goal? Do I have to make anything public and if so, how do I do that?
You have to find it first before you choose to remove it.
private void BtnRemoveTextbox2_Click(object sender, EventArgs e)
{
MetroFramework.Controls.MetroTextBox tbx = this.Controls.Find("Textbox2", true).FirstOrDefault() as MetroFramework.Controls.MetroTextBox;
if (tbx != null)
{
this.Controls.Remove(tbx);
}
}
Here, Textbox2 is the ID of your textbox. Please make sure you're setting the ID of your textbox control before adding it.
You need to find those controls using Controls.Find method and then remove and dispose them:
this.Controls.Find("Textbox2", false).Cast<Control>().ToList()
.ForEach(c =>
{
this.Controls.Remove(c);
c.Dispose();
});
Since the control was created in another form, the current form has no way of knowing it by its instance name.
To remove it, loop through all controls and look for its Name:
private void BtnRemoveTextbox2_Click(object sender, EventArgs e)
{
foreach (Control ctrl in this.Controls)
{
if (ctrl.Name == "Textbox2")
this.Controls.Remove(ctrl);
}
}
Yesterday I try to implement a new listview that support sub-item edit, my solution is to show a textbox when double click the sub-item. The key code as following:
protected override void OnDoubleClick(EventArgs e)
{
Point pt = this.PointToClient(Cursor.Position);
ListViewItem curItem;
int subItemIndex = GetSubItemAt(pt.X, pt.Y, out curItem);
DoubleClickEventArgs args = new DoubleClickEventArgs(subItemIndex);
base.OnDoubleClick(args);
if (subItemIndex>=0 && !args.Cancel)
{
//StartEdit(...);
}
}
public void EndEdit(bool acceptChanges)
{
//validation
.................
.................
AfterSubItemEventArgs e = new AfterSubItemEventArgs(this.SelectedItems[0], m_editSubItemIndex, this.SelectedItems[0].SubItems[m_editSubItemIndex].Text, m_textbox.Text, false);
OnAfterSubItemEdit(e);
if (e.Cancel)
{
//....
}
else
{
//set new value
}
m_textbox.Visible = false;
m_editSubItemIndex = -1;
}
OnAfterSubItemEdit is a event that user can do some validations or other operations. I add a check in this method, if the new value exist, I will show a messagebox to user firstly, then hide the textbox. But now, the problem comes, when i move the mouse, the listview items can be selected, I don't how to solve this issue, I tried my best to find out the way, but failed. So, please help me!
Listview has a LabelEdit property; when you set it "true", then in an event handler you can call Listview.Items[x].BeginEdit(), and edit an item. As an example, you can handle ListView.DoubleClick event and call BeginEdit right there:
private void Form1_Load(object sender, System.EventArgs e)
{
listView1.LabelEdit = true;
}
private void listView1_DoubleClick(object sender, System.EventArgs e)
{
if(this.listView1.SelectedItems.Count==1)
{
this.listView1.SelectedItems[0].BeginEdit();
}
}
The problem is that your form still calls the DoubleClick event whether the value exists or not. Add appropriate condition before calling base DoubleClick in your code, i.e.:
if(!new value exists)
base.OnDoubleClick(args);
I have 15 comboBox'es, and I do not want to create an event handler for each. How do I make just one procedure and tie all Combobox'es to it?
private void cbSlots0_SelectedIndexChanged(object sender, EventArgs e)
{
var item = ConfigClass.Slots["0"][cbSlots0.SelectedIndex];
ConfigClass.Slots["0"].Insert(0, item);
ConfigClass.Slots["0"].RemoveAt(cbSlots0.SelectedIndex + 1);
}
private void cbSlots1_SelectedIndexChanged(object sender, EventArgs e)
{
var item = ConfigClass.Slots["1"][cbSlots1.SelectedIndex];
ConfigClass.Slots["1"].Insert(1, item);
ConfigClass.Slots["1"].RemoveAt(cbSlots1.SelectedIndex + 1);
}
Correct answer:
var cb = ((ComboBox)sender);
var tag = int.Parse(cb.Tag.ToString());
var item = ConfigClass.Slots[tag.ToString()][cb.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cb.SelectedIndex + 1);
You can give each ComboBox a distinct Tag, which contains the number of the entry in the ConfigClass, and then use that like so:
private void cbSlots0_SelectedIndexChanged(object sender, EventArgs e)
{
int tag = (int)((ComboBox)sender).Tag;
var item = ConfigClass.Slots[tag.ToString()][cbSlots0.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cbSlots0.SelectedIndex + 1);
}
The tag can contain any data you want, so if you need something more complex stored in there, that's also a possibility.
I would recommend one event handler for all ComboBoxes. Afterwards, within your event handler, use the sender reference to decide which slot to use:
private void allComboBoxesSelectedIndesChanged(object sender, EventArgs e)
{
int index = 0; // Or string as you have shown in your example.
if (sender == cbSlots0)
index = 0;
else if (sender == cbSlots1)
index = 1;
/// And so on for any other comboBox
var item = ConfigClass.Slots[index][((ComboBox) sender).SelectedIndex];
ConfigClass.Slots[index].Insert(index, item);
ConfigClass.Slots[index].RemoveAt(((ComboBox) sender).SelectedIndex +1);
}
This is relatively simple. You create a single SelectedIndexChanged event handler method, and then wire that up to all of the combo box controls.
The way you distinguish between the controls inside of the method at run-time is by checking the value of the sender parameter. You'll have to cast it to a ComboBox control, but that's safe because you know that you didn't wire up any non-combobox controls to that event handler. Then you'll be able to access all the properties of the combobox that raised the event you're handling.
Tie each item in your markup to the same SelectedIndexChangedEvent and cast the sender as your item. So, in your code, look for all of the unique event names (ie. cbSlots0_SelectedIndexChanged, cbSlots1_SelectedIndexChanged, etc) and rename them to the single event name (eg. cbSlotsSelectedIndexChanged).
I think this is right. Verify.
CODE:
private void cbSlotsSelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cBox = (ComboBox) sender;
int tag = (int)cBox.Tag;
var item = ConfigClass.Slots[tag.ToString()][cBox.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(item.SelectedIndex + 1);
}
UPDATE:
I revised my post as requested
private void cbSlotsSelectedIndexChanged(object sender, EventArgs e)
{
var cb = ((ComboBox)sender);
var tag = int.Parse(cb.Tag.ToString());
var item = ConfigClass.Slots[tag.ToString()][cb.SelectedIndex];
ConfigClass.Slots[tag.ToString()].Insert(tag, item);
ConfigClass.Slots[tag.ToString()].RemoveAt(cb.SelectedIndex + 1);
}
The working code sample here synchronizes (single) selection in a TreeView, ListView, and ComboBox via the use of lambda expressions in a dictionary where the Key in the dictionary is a Control, and the Value of each Key is an Action<int>.
Where I am stuck is that I am getting multiple repetitions of execution of the code that sets the selection in the various controls in a way that's unexpected : it's not recursing : there's no StackOverFlow error happening; but, I would like to figure out why the current strategy for preventing multiple selection of the same controls is not working.
Perhaps the real problem here is distinguishing between a selection update triggered by the end-user and a selection update triggered by the code that synchronizes the other controls ?
Note: I've been experimenting with using Delegates, and forms of Delegates like Action<T>, to insert executable code in Dictionaries : I "learn best" by posing programming "challenges" to myself, and implementing them, as well as studying, at the same time, the "golden words" of luminaries like Skeet, McDonald, Liberty, Troelsen, Sells, Richter.
Note: Appended to this question/code, for "deep background," is a statement of how I used to do things in pre C#3.0 days where it seemed like I did need to use explicit measures to prevent recursion when synchronizing selection.
Code : Assume a WinForms standard TreeView, ListView, ComboBox, all with the same identical set of entries (i.e., the TreeView has only root nodes; the ListView, in Details View, has one Column).
private Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();
private void Form1_Load(object sender, EventArgs e)
{
// add the Controls to be synchronized to the Dictionary
// with appropriate Action<int> lambda expressions
ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));
// optionally install event handlers at run-time like so :
// treeView1.AfterSelect += (object obj, TreeViewEventArgs evt)
// => { synchronizeSelection(evt.Node.Index, treeView1); };
// listView1.SelectedIndexChanged += (object obj, EventArgs evt)
// => { if (listView1.SelectedIndices.Count > 0)
// { synchronizeSelection(listView1.SelectedIndices[0], listView1);} };
// comboBox1.SelectedValueChanged += (object obj, EventArgs evt)
// => { synchronizeSelection(comboBox1.SelectedIndex, comboBox1); };
}
private void synchronizeSelection(int i, Control currentControl)
{
foreach(Control theControl in ControlToAction.Keys)
{
// skip the 'current control'
if (theControl == currentControl) continue;
// for debugging only
Console.WriteLine(theControl.Name + " synchronized");
// execute the Action<int> associated with the Control
ControlToAction[theControl](i);
}
}
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
synchronizeSelection(e.Node.Index, treeView1);
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
// weed out ListView SelectedIndexChanged firing
// with SelectedIndices having a Count of #0
if (listView1.SelectedIndices.Count > 0)
{
synchronizeSelection(listView1.SelectedIndices[0], listView1);
}
}
private void comboBox1_SelectedValueChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex > -1)
{
synchronizeSelection(comboBox1.SelectedIndex, comboBox1);
}
}
background : pre C# 3.0
Seems like, back in pre C# 3.0 days, I was always using a boolean flag to prevent recursion when multiple controls were updated. For example, I'd typically have code like this for synchronizing a TreeView and ListView : assuming each Item in the ListView was synchronized with a root-level node of the TreeView via a common index :
// assume ListView is in 'Details View,' has a single column,
// MultiSelect = false
// FullRowSelect = true
// HideSelection = false;
// assume TreeView
// HideSelection = false
// FullRowSelect = true
// form scoped variable
private bool dontRecurse = false;
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
if(dontRecurse) return;
dontRecurse = true;
listView1.Items[e.Node.Index].Selected = true;
dontRecurse = false;
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if(dontRecurse) return
// weed out ListView SelectedIndexChanged firing
// with SelectedIndices having a Count of #0
if (listView1.SelectedIndices.Count > 0)
{
dontRecurse = true;
treeView1.SelectedNode = treeView1.Nodes[listView1.SelectedIndices[0]];
dontRecurse = false;
}
}
Then it seems, somewhere around FrameWork 3~3.5, I could get rid of the code to suppress recursion, and there was was no recursion (at least not when synchronizing a TreeView and a ListView). By that time it had become a "habit" to use a boolean flag to prevent recursion, and that may have had to do with using a certain third party control.
I believe your approach is totally fine. If you want something a little more advanced, see Rein in runaway events with the "Latch", which allows for
void TabControl_TabSelected(object sender, TabEventArgs args)
{
_latch.RunLatchedOperation(
delegate
{
ContentTab tab = (ContentTab)TabControl.SelectedTab;
activatePresenter(tab.Presenter, tab);
});
}
Note: I always assumed an SO user should never answer their own question. But, after reading-up on SO-Meta on this issue, I find it's actually encouraged. Personally, I would never vote on my own answer as "accepted."
This "new solution" uses a strategy based on distinguishing between a control being updated as a result of end-user action, and a control being updated by synchronizing code: this issue was mentioned, as a kind of "rhetorical question," in the original question.
I consider this an improvement: it works; it prevents multiple update calls; but, I also "suspect" it's still "not optimal": appended to this code example is a list of "suspicions."
// VS Studio 2010 RC 1, tested under Framework 4.0, 3.5
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace SynchronizationTest_3
{
public partial class Form1 : Form
{
private readonly Dictionary<Control, Action<int>> ControlToAction = new Dictionary<Control, Action<int>>();
// new code : keep a reference to the control the end-user clicked
private Control ClickedControl;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ControlToAction.Add(treeView1, (i => { treeView1.SelectedNode = treeView1.Nodes[i]; }));
ControlToAction.Add(listView1, (i => { listView1.Items[i].Selected = true; }));
ControlToAction.Add(comboBox1, (i => { comboBox1.SelectedIndex = i; }));
// new code : screen out redundant calls generated by other controls
// being updated
treeView1.AfterSelect += (obj, evt)
=>
{
if (treeView1 == ClickedControl) SynchronizeSelection(evt.Node.Index);
};
listView1.SelectedIndexChanged += (obj, evt)
=>
{
if (listView1.SelectedIndices.Count > 0 && listView1 == ClickedControl)
{
SynchronizeSelection(listView1.SelectedIndices[0]);
}
};
comboBox1.SelectedValueChanged += (obj, evt)
=>
{
if (comboBox1 == ClickedControl) SynchronizeSelection(comboBox1.SelectedIndex);
};
// new code here : all three controls share a common MouseDownHandler
treeView1.MouseDown += SynchronizationMouseDown;
listView1.MouseDown += SynchronizationMouseDown;
comboBox1.MouseDown += SynchronizationMouseDown;
// trigger the first synchronization
ClickedControl = treeView1;
SynchronizeSelection(0);
}
// get a reference to the control the end-user moused down on
private void SynchronizationMouseDown(object sender, MouseEventArgs e)
{
ClickedControl = sender as Control;
}
// revised code using state of ClickedControl as a filter
private void SynchronizeSelection(int i)
{
// we're done if the reference to the clicked control is null
if (ClickedControl == null) return;
foreach (Control theControl in ControlToAction.Keys)
{
if (theControl == ClickedControl) continue;
// for debugging only
Console.WriteLine(theControl.Name + " synchronized");
ControlToAction[theControl](i);
}
// set the clicked control to null
ClickedControl = null;
}
}
}
Why I "suspect" this is not optimal:
the idiosyncratic behavior of WinForms controls has to be taken into account: for example, the ListView Control fires its Selected### Events before it fires a Click Event: ComboBox and TreeView fire their Click Events before their SelectedValueChanged and AfterSelect Events respectively: so had to experiment to find that using 'MouseDown would work the same across all three controls.
a "gut level" feeling that I've gone "too far" out on "some kind of limb" here: a sense a much simpler solution might be possible.
Is this a simple process?
I'm only writing a quick hacky UI for an internal tool.
I don't want to spend an age on it.
Here's a quick down and dirty app. Basically I created a Form with a button and a ListBox. On button click, the ListBox gets populated with the date of the next 20 days (had to use something just for testing). Then, it allows drag and drop within the ListBox for reordering:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.listBox1.AllowDrop = true;
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 20; i++)
{
this.listBox1.Items.Add(DateTime.Now.AddDays(i));
}
}
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (this.listBox1.SelectedItem == null) return;
this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
}
private void listBox1_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void listBox1_DragDrop(object sender, DragEventArgs e)
{
Point point = listBox1.PointToClient(new Point(e.X, e.Y));
int index = this.listBox1.IndexFromPoint(point);
if (index < 0) index = this.listBox1.Items.Count-1;
object data = e.Data.GetData(typeof(DateTime));
this.listBox1.Items.Remove(data);
this.listBox1.Items.Insert(index, data);
}
7 Years Late. But for anybody new, here is the code.
private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
if (this.listBox1.SelectedItem == null) return;
this.listBox1.DoDragDrop(this.listBox1.SelectedItem, DragDropEffects.Move);
}
private void listBox1_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void listBox1_DragDrop(object sender, DragEventArgs e)
{
Point point = listBox1.PointToClient(new Point(e.X, e.Y));
int index = this.listBox1.IndexFromPoint(point);
if (index < 0) index = this.listBox1.Items.Count - 1;
object data = listBox1.SelectedItem;
this.listBox1.Items.Remove(data);
this.listBox1.Items.Insert(index, data);
}
private void itemcreator_Load(object sender, EventArgs e)
{
this.listBox1.AllowDrop = true;
}
The first time it takes a few hours if you never implemented drag and drop, want to get it done right and have to read through the docs. Especially the immediate feedback and restoring the list if the user cancels the operation require some thoughts. Encapsulating the behavior into a reusable user control will take some time, too.
If you have never done drag and drop at all, have a look at this drag and drop example from the MSDN. This would be a good starting point and it should take you maybe half a day to get the thing working.
This relies on #BFree's answer above - thanks it helped a lot.
I ran into an error when trying to use the solution because I was using a DataSource for my listbox. Just for completeness, you get this error if you try to remove or add an item to the listbox directly:
// Causes error
this.listBox1.Items.Remove(data);
Error:
System.ArgumentException: 'Items collection cannot be modified when the DataSource property is set.'
Solution: Update the datasource itself, and then rebind to your listbox. Program.SelectedReports is a BindingList.
Code:
private void listboxSelectedReports_DragDrop(object sender, DragEventArgs e)
{
// Get the point where item was dropped.
Point point = listboxSelectedReports.PointToClient(new Point(e.X, e.Y));
// Get the index of the item where the point was dropped
int index = this.listboxSelectedReports.IndexFromPoint(point);
// if index is invalid, put item at the end of the list.
if (index < 0) index = this.listboxSelectedReports.Items.Count - 1;
// Get the item's data.
ReportModel data = (ReportModel)e.Data.GetData(typeof(ReportModel));
// Update the property we use to control sorting within the original datasource
int newSortOrder = 0;
foreach (ReportModel report in Program.SelectedReports) {
// match sorted item on unique property
if (data.Id == report.Id)
{
report.SortOrder = index;
if (index == 0) {
// only increment our new sort order if index is 0
newSortOrder += 1;
}
} else {
// skip our dropped item's index
if (newSortOrder == index) {
newSortOrder += 1;
}
report.SortOrder = newSortOrder;
newSortOrder += 1;
}
}
// Sort original list and reset the list box datasource.
// Note: Tried other things, Reset(), Invalidate(). Updating DataSource was only way I found that worked??
Program.SelectedReports = new BindingList<ReportModel>(Program.SelectedReports.OrderBy(x => x.SortOrder).ToList());
listboxSelectedReports.DataSource = Program.SelectedReports;
listboxSelectedReports.DisplayMember = "Name";
listboxSelectedReports.ValueMember = "ID";
}
Other notes:
BindingList is under this namespace:
using System.ComponentModel;
When dynamically adding items to the list, make sure you populate your sorting property. I used an integer field 'SortOrder'.
When you remove an item, I don't have to worry about updating the Sorting property, as it will just create a number gap which is ok in my situation, YMMV.
To be honest, there could be a better sorting algorithm other than a foreach loop, but in my situation, I am dealing with a very limited number of items.
An alternative is using the list-view control, which is the control Explorer uses to display the contents of folders. It is more complicated, but implements item dragging for you.