In the demo of the ObjectListView control there is this code (in the "Complex Example" tab page) to allow for a custom editor (a ComboBox) (Adapted to my case and edited for clarity):
EventHandler CurrentEH;
private void ObjectListView_CellEditStarting(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
{
ISomeInterface M = (e.RowObject as ObjectListView1Row).SomeObject; //(1)
ComboBox cb = new ComboBox();
cb.Bounds = e.CellBounds;
cb.DropDownStyle = ComboBoxStyle.DropDownList;
cb.DataSource = ISomeOtherObjectCollection;
cb.DisplayMember = "propertyName";
cb.DataBindings.Add("SelectedItem",
M, "ISomeOtherObject", false,
DataSourceUpdateMode.Never);
e.Control = cb;
cb.SelectedIndexChanged +=
CurrentEH = (object sender2, EventArgs e2) =>
M.ISomeOtherObject =
(ISomeOtherObject)((ComboBox)sender2).SelectedValue; //(2)
}
}
private void ObjectListView_CellEditFinishing(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
{
// Stop listening for change events
((ComboBox)e.Control).SelectedIndexChanged -= CurrentEH;
// Any updating will have been down in the SelectedIndexChanged
// event handler.
// Here we simply make the list redraw the involved ListViewItem
((ObjectListView)sender).RefreshItem(e.ListViewItem);
// We have updated the model object, so we cancel the auto update
e.Cancel = true;
}
}
I have too many other columns with combo editors inside objectlistviews to use a copy& paste strategy (besides, copy&paste is a serious source of bugs), so I tried to parameterize the code to keep the code duplication to a minimum. ObjectListView_CellEditFinishing is a piece of cake:
HashSet<OLVColumn> cbColumns = new HashSet<OLVColumn> (new OLVColumn[] { SomeCol, SomeCol2, ...};
private void ObjectListView_CellEditFinishing(object sender,
CellEditEventArgs e)
{
if (cbColumns.Contains(e.Column)) ...
but ObjectListView_CellEditStarting is the problematic.
I guess in CellEditStarting I will have to discriminate each case separately:
private void ObjectListView_CellEditStarting(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
// code to create the combo, put the correct list as the datasource, etc.
else if (e.Column == SomeOtherCol)
// code to create the combo, put the correct list as the datasource, etc.
And so on.
But how can I parameterize the "code to create the combo, put the correct list as the datasource, etc."? Problem lines are
(1) Get SomeObject. the property NAME varies.
(2) Set ISomeOtherObject, the property name varies too.
The types vary too, but I can cover those cases with a generic method combined with a not so "typesafe" API (for instance, the cb.DataBindings.Add and cb.DataSource both use an object)
Reflection? more lambdas? Any ideas? Any other way to do the same?
PS: I want to be able to do something like this:
private void ObjectListView_CellEditStarting(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
SetUpCombo<ISomeInterface>(ISomeOtherObjectCollection,
"propertyName",
SomeObject,
ISomeOtherObject);
else if (e.Column == SomeOtherCol)
SetUpCombo<ISomeInterface2>(ISomeOtherObject2Collection,
"propertyName2",
SomeObject2
ISomeOtherObject2);
and so on. Or something like that.
I know, parameters SomeObject and ISomeOtherObject are not real parameters per see, but you get the idea of what I want. I want not to repeat the same code skeleton again and again and again.
One solution would be "preprocessor generics" like C's DEFINE, but I don't thing c# has something like that.
So, does anyone have some alternate ideas to solve this?
Found it. Kudos to Tejs!
private void SetUpCombo(CellEditEventArgs e,
object ComboItems, string DisplayMember,
object DataSource, string PropertyName,
EventHandler evt)
{
ComboBox cb = new ComboBox();
cb.Bounds = e.CellBounds;
cb.DropDownStyle = ComboBoxStyle.DropDownList;
cb.DataSource = ComboItems;
cb.DisplayMember = DisplayMember;
cb.DataBindings.Add("SelectedItem", DataSource, PropertyName,
false, DataSourceUpdateMode.Never);
e.Control = cb;
cb.SelectedIndexChanged += CurrentEH = evt;
}
and the rewritten CellEditStarting:
private void ObjectListView_CellEditStarting(object sender,
CellEditEventArgs e)
{
if (e.Column == SomeCol)
{
ISomeInterface M = (e.RowObject as ObjectListView1Row).SomeObject;
SetUpCombo(e,
ISomeOtherObjectCollection,"propertyName",
M, "ISomeOtherObject",
(sender2, e2) =>
M.ISomeOtherObject =
(ISomeOtherObject)((ComboBox)sender2).SelectedValue);
}
else if (e.Column == SomeOtherCol)
{
ISomeInterface2 M = (e.RowObject as ObjectListView1Row).SomeObject2;
SetUpCombo(e,
ISomeOtherObjectCollection2,"propertyName2",
M, "ISomeOtherObject2",
(sender2, e2) =>
M.ISomeOtherObject2 =
(ISomeOtherObject)((ComboBox)sender2).SelectedValue);
}
and so on... There are some things I don't like yet. For instance: the disconnection between M.ISomeOtherObject (outside the method call), "ISomeOtherObject" (the param) and the setting of M.ISomeOtherObject (inside the lambda).
But, all things considered, it is much much better than copying & pasting the same code over and over again).
Related
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----------
}
i have 2 questions about this code.
1st:
What is the difference between 1st way and 2nd way in the code? i mean there syntax not what they do. which one is prefered?
2nd:
in Controls.OfType how can we get multiple result for example for TextBox and Button,here i have written only for TextBox.(Windows form)
private void Form2_Load(object sender, EventArgs e)
{
foreach (Control item in Controls.OfType<TextBox>())
{
item.MouseEnter += item_MouseEnter; // 1st way
item.MouseLeave += new System.EventHandler(item_MouseLeave); //2nd way
}
}
void item_MouseLeave(object sender, EventArgs e)
{
var senderButton = (Control)sender;
senderButton.Height -= 5;
senderButton.Width -= 5;
}
void item_MouseEnter(object sender, EventArgs e)
{
var senderButton = (Control)sender;
senderButton.Height += 5;
senderButton.Width += 5;
}
1st: There's no difference. The first line will implicitly use EventHandler. This is prefered.
2nd: OfType takes only 1 type argument, so it will return only objects of that Type. But you can do the filtering yourself. Controls.Where(ctrl => ctrl is TextBox || ctrl is Button)
(This is what OfType does internally anyway.)
Edit: It appears that ControlCollection implements the non-generic IEnumerable, so it does not have an extension method called Where.
So you have to use:
Controls.OfType<Control>().Where(ctrl => ctrl is TextBox || ctrl is Button)
(OfType will convert IEnumerable to IEnumerable<Control>)
Or you could use a simple if statement inside the loop.
I want to be able to right click on an image and for there to be a menu show up. When I then click on one of the items I want it to point to a class:
private void link1add_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("Add", HandleContextMenuAdd);
cm.MenuItems.Add("Remove", HandleContextMenuRemove);
link1add.ContextMenu = cm;
}
}
private void HandleContextMenuAdd(object sender, EventArgs e)
{
MessageBox.Show("Adding");
}
private void HandleContextMenuRemove(object sender, EventArgs e)
{
MessageBox.Show("Removing");
}
Code edited since first posted.
Thanks all for your help.
What about a lambda expression?
cm.MenuItems.Add("Item 2", (_, __) => {
if (...) ReadDocument();
});
or
cm.MenuItems.Add("Item 2", (_, __) => { this.myClassInstance.DoSomething(); });
Alternatively, you can create a method with the signature of the expected event handler:
cm.MenuItems.Add("Item 2", HandleContextMenuClick);
private void HandleContextMenuClick(object sender, EventArgs e)
{
if (...) ReadDocument();
}
That method doesn't need to be in the same class (you can write this.myClassInstance.HandleContextMenuclick for example). But I would hide the implementation detail from other classes to avoid unnecessary coupling.
Pattern for your own code after this:
public class ReadDocumentEventArgs : EventArgs
{
public string ImageInfo { get; set; }
}
public void ReadDocument(object sender, ReadDocumentEventArgs ea)
{
// do whatever you need to do
MessageBox.Show(ea.ImageInfo); // example
}
private void link1add_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
PictureBox imageCtrl = sender as PictureBox;
// get the information you need to get from your control to identify it
string imgInfo = "Hello, World!"; // example
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("Item 1");
cm.MenuItems.Add("Item 2",
(EventHandler)((s, rde) =>
{ ReadDocument(s, new ReadDocumentEventArgs()
{ ImageInfo = imgInfo });
}));
link1add.ContextMenu = cm;
}
}
In your MouseDown over your image you can create a menu item using the code I supplied above that will call an event handler called ReadDocument. Notice that there is a ReadDocumentEventArgs class that you can customize to contain the necessary properties that will help you identify which image you have clicked on.
So, in the example I have above one of the first things that happens in MouseDown is that you get the instance of image control (I assume it's a Picture Box, but you can cast it to whatever it really is).
At that point, you can then get a file name or whatever from your image that identifies it from the other controls on your form.
Next, when creating a context menu item, it tells the menu item to call ReadDocument but passes in the special data just taken from the image control.
In the ReadDocument method, you can then do whatever you need to do. In my example, I simply throw up a MessageBox to show you what data you passed in.
What you will want is to put another event handler and call your readdocument method.
I have simple textbox example as below:
private void Form1_Load(object sender, EventArgs e)
{
textBox1.Text = "Apple";
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.Text.Length == 1)
{
if (textBox1.Text == "B" || textBox1.Text == "b")
{
textBox1.Text = "Ball";
}
}
}
By default textbox1 should return "Apple" on Form load but when I press "b" or "B" then it should return "Ball" on textbox1. I have a confusion on utilize it into datagridview. how can i do it in datagridview?.
Suppose I have One column on datagridview like below:
private void Form1_Load(object sender, EventArgs e)
{
DataGridViewColumn Particulars = new DataGridViewTextBoxColumn();
dataGridView1.Columns.Insert(0, Particulars );
}
If I have above column In datagridview1 than How to do I with datagridview1 which I have did with textbox?.
You might find it more straightforward to use the auto-complete functionality built-in to the textbox control, rather than trying to code for all possible scenarios yourself.
There are two important properties of the TextBox control that you must configure to enable its auto-completion functionality: AutoCompleteMode and AutoCompleteSource.
The AutoCompleteMode property allows you to choose how the textbox autocomplete function will look in action. You can choose between any of the AutoCompleteMode values
None Disables the automatic completion feature for the ComboBox and TextBox controls.
Suggest Displays the auxiliary drop-down list associated with the edit control. This drop-down is populated with one or more suggested completion strings.
Append Appends the remainder of the most likely candidate string to the existing characters, highlighting the appended characters.
SuggestAppend Applies both Suggest and Append options.
The AutoCompleteSource property allows you to specify the strings that you want the textbox to propose auto-completion with. In your case, you will probably want to specify a CustomSource, which requires you to set the AutoCompleteCustomSource property to a user-defined collection of strings—something like "Apple, Ball, ..." etc.
The DataGridViewTextBoxColumn simply hosts a standard TextBox control, so all of the auto-complete functionality it provides is already available to you for free. You can set the appropriate properties of this textbox by handling the EditingControlShowing event of your DataGridView, like so:
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
//Create and fill a list to use as the custom data source
var source = new AutoCompleteStringCollection();
source.AddRange(new string[] {"Apple", "Ball"});
//Set the appropriate properties on the textbox control
TextBox dgvEditBox = e.Control as TextBox;
if (dgvEditBox != null)
{
dgvEditBox.AutoCompleteMode = AutoCompleteMode.Suggest;
dgvEditBox.AutoCompleteCustomSource = source;
dgvEditBox.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
}
EDIT: If you'd prefer to keep the same behavior as you have in the original textbox example, you can just handle the TextChanged event for the DataGridViewTextBoxColumn. As I already explained above, the DataGridViewTextBoxColumn simply hosts a standard TextBox control, so it's fairly straightforward to add a handler for its TextChanged event and use the same code you had before:
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
TextBox dgvEditBox = e.Control as TextBox;
if (dgvEditBox != null)
{
//Add a handler for the TextChanged event of the underlying TextBox control
dgvEditBox.TextChanged += new EventHandler(dgvEditBox_TextChanged);
}
}
private void dgvEditBox_TextChanged(object sender, EventArgs e)
{
//Extract the textbox control
TextBox dgvEditBox = (TextBox)sender;
//Insert the appropriate string
if (dgvEditBox.Text.Length == 1)
{
if (dgvEditBox.Text == "B" || dgvEditBox.Text == "b")
{
dgvEditBox.Text = "Ball";
}
}
}
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.