i am trying to implement my own ComboBox class in C# because, untill 3.5 NET Framework (if i'm not mistaking) suggestion lookup is made with a "StartWith" function (i.e. if the list contains "Doe, John" and user types "John", that item is not displayed). Basically i'm adding or removing items on text change event, getting them from the initial content of the list. Everything works pretty fine for what i am looking for, the only issue is, when ComboBox is clicked out, an item is still being selected even though it is not equal to the inserted text. Following the example i did, i want that "Doe, John" is selected (and set as ComboBox.Text property) only if user clicked on it, if user just typed "John" and no item is strictly equal to it (not just contain it), then Text property must remain as the user inserted it. Here's the code of my derived class
public class customTB : ComboBox
{
private object[] startlist;
public customTB() : base()
{
this.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.None;
this.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.None;
this.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown;
this.Sorted = true;
this.KeyPress += customTB_KeyPress;
this.TextChanged += customTB_TextChanged;
this.Enter += customTB_Enter;
}
void customTB_Enter(object sender, EventArgs e)
{
this.DroppedDown = (this.Items.Count > 0);
}
void customTB_TextChanged(object sender, EventArgs e)
{
UpdateList();
}
void customTB_KeyPress(object sender, KeyPressEventArgs e)
{
this.DroppedDown = (this.Items.Count>0);
}
void UpdateList()
{
if (this.startlist == null)
{
//get starting lists elems
this.startlist = new Object[this.Items.Count];
this.Items.CopyTo(this.startlist, 0);
}
this.BeginUpdate();
foreach (object o in startlist)
{
if (o.ToString().Contains(this.Text))
{
if (!this.Items.Contains(o))
this.Items.Add(o);
}
else if (this.Items.Contains(o))
this.Items.Remove(o);
}
this.EndUpdate();
}
}
If tried, any time you try to exit the ComboBox, Text is highlighted and its value is set to an item.
As example of what i would like to have is:
items contains "Doe John", "Smith John", "Smith Marie".
if user types "John", then dropdown items are "Doe John" and "Smith John" but if he doesn't click any of the dropdown elements and exit the ComboBox (i.e. clicking outside), the Text remains "John"
Have one boolean variable itemClicked
Set itemClicked to false inside Enter event handler
Set itemClicked to true inside SelectionChangeCommitted event handler
Set Text property to string.Empty if not itemClicked inside DropDownClosed event handler
Related
I'm new to c# and I'm now learning how to trigger events based on some form actions.
This is part of view:
private void comboGoodsName_TextChanged(object sender, EventArgs e)
{
controller.selectName(comboGoodsName.Text);
}
public void nameChanged(object sender, MeasurementEventArgs e)
{
comboGoodsName.TextChanged -= comboGoodsName_TextChanged;
comboGoodsName.Text = e.value;
comboGoodsName.TextChanged += comboGoodsName_TextChanged;
}
And this is part of controller:
public void selectName(string name)
{
model.Name = name.Split('|')[0].Trim();
if (name.Contains(" | "))
{
string code = name.Split('|')[1].Trim();
model.NameCode = code;
}
}
The scenario is as follows:
I want to have a ComboBox with some items in it (doesn't matter what's the source). Items are combination of name and code in following format: NAME | CODE. When I enter some text in ComboBox (type it in), comboGoodsName_TextChanged is triggered, which in turn calls selectName which sets model's property, which in turn raises an event which is observed by nameChanged. This works fine, as expected (puts NAME in ComboBox and CODE to TextBox - not shown as not relevant). Problem shows up when I select item from ComboBox drop-down list. When I select item, instead of showing NAME in ComboBox, I see NAME | CODE.
Edit: In the model, property is set correctly, which I confirmed by printing its value. So, issue is related only to displaying proper value in ComboBox.
Try this:
private void comboGoodsName_SelectedIndexChanged(object sender, EventArgs e)
{
// if combobox has selected item then continue
if (comboGoodsName.SelectedIndex > -1)
{
// split the selecteditem text on the pipe into a string array then pull the first element in the array i.e. NAME
string nameOnly = comboGoodsName.GetItemText(this.comboGoodsName.SelectedItem).Split('|')[0];
// handing off the reset of the combobox selected value to a delegate method - using methodinvoker on the forms main thread is an efficient to do this
// see https://msdn.microsoft.com/en-us/library/system.windows.forms.methodinvoker(v=vs.110).aspx
this.BeginInvoke((MethodInvoker)delegate { this.comboGoodsName.Text = nameOnly; });
}
}
I need to keep track of the selected item on a ListBox to update/disable other controls according to the currently selected value.
This is the code to reproduce the issue:
public partial class Form1 : Form
{
private readonly BindingList<string> List = new BindingList<string>();
public Form1()
{
InitializeComponent();
listBox1.DataSource = List;
listBox1.SelectedValueChanged += (s, e) => System.Diagnostics.Debug.WriteLine("VALUE");
listBox1.SelectedIndexChanged += (s, e) => System.Diagnostics.Debug.WriteLine("INDEX");
addButton.Click += (s, e) => List.Add("Item " + (List.Count + 1));
removeButton.Click += (s, e) => List.RemoveAt(List.Count - 1);
logSelectionButton.Click += (s, e) =>
{
System.Diagnostics.Debug.WriteLine("Selected Index: " + listBox1.SelectedIndex);
System.Diagnostics.Debug.WriteLine("Selected Value: " + listBox1.SelectedValue);
};
}
}
My form has a list box listBox1 and three buttons: addButton, removeButton and logSelectionButton.
If you press addButton (starting with an empty list), then removeButton and finally addButton again, neither SelectedValueChanged nor SelectedIndexChanged will fire at the last addButton press, even though if you press logSelectionButton before and after the last addButton press, you'll see that the values of both SelectedIndex and SelectedValue have changed from -1 to 0 and from null to "Item 1" respectively, and that "Item 1" looks selected on the list box.
This would cause any other controls I need to update according to the selected item to stay disabled until the user manually selects an item on the list box, even though the first item is already selected.
I can't think of any workaround. Perhaps also subscribing to my BindingList's ListChanged event to see whether the list is empty or not, but then I don't know if the items in the list box will be updated before or after my event handler fires, which will cause other problems.
Seems like you found a bug in ListControl internal handling of the PositionChanged event when data bound (if you turn Exceptions on in VS, you'll see an exception when the first item is added to the empty list).
Since ListControl derived classes like ListBox, ComboBox etc. in data bound mode synchronize their selection with the Position property of the BindingManagerBase, the reliable workaround (and basically a more general abstract solution) is to handle CurrentChanged event of the underlying data source binding manager:
listBox1.BindingContext[List].CurrentChanged += (s, e) =>
System.Diagnostics.Debug.WriteLine("CURRENT");
I found a workaround that seems to work fine. Since ListBox updates the selected index by setting the SelectedIndex property and the property is virtual I can override it to keep track of it:
public class ListBoxThatWorks : ListBox
{
private int LatestIndex = -1;
private object LatestValue = null;
public EqualityComparer<object> ValueComparer { get; set; }
public override int SelectedIndex
{
get { return base.SelectedIndex; }
set { SetSelectedIndex(value); }
}
private void NotifyIndexChanged()
{
if (base.SelectedIndex != LatestIndex)
{
LatestIndex = base.SelectedIndex;
base.OnSelectedIndexChanged(EventArgs.Empty);
}
}
private void NotifyValueChanged()
{
if (!(ValueComparer ?? EqualityComparer<object>.Default).Equals(LatestValue, base.SelectedValue))
{
LatestValue = base.SelectedValue;
base.OnSelectedValueChanged(EventArgs.Empty);
}
}
private void SetSelectedIndex(int value)
{
base.SelectedIndex = value;
NotifyIndexChanged();
NotifyValueChanged();
}
}
I'm trying to retrieve the value of a DataGridViewComboBoxCell in the following way
When adding items to the ComboBox:
public void LoadLayouts()
{
ImmutableSet<string> layoutNames = _store.Current.Keys;
var dgvComboBox = (DataGridViewComboBoxColumn)this.schedulesDataGrid.Columns[1];
foreach (string name in layoutNames)
{
dgvComboBox.Items.Add(name);
}
}
When trying to read back the value:
var combo = (DataGridViewComboBoxCell) this.schedulesDataGrid[args.ColumnIndex, args.RowIndex];
string LayoutChosen = (string)combo.Value;
However, even if I can see that there is a value selected in the ComboBox, the Value comes back as null, and the FormattedValue comes back as "".
I've tried just setting the array of names as my DataSource, but then I'm not sure what to set for my Display and Value Members, considering I only have a single value (the name of the layout)
Thoughts?
At the time you attempt to read the cell's value, the row has not committed changes. If you have row headers visible, you will see the pencil icon on the row that has not been committed. This normally happens after the cell looses focus. You can force the issue by hooking into the in-place editing control combo-box and causing it to post EndEdit when it changes values:
void dgv_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
var comboBox = e.Control as ComboBox;
if (comboBox != null)
{
// remove any handler we may have added previously
comboBox.SelectionChangeCommitted -= new EventHandler(comboBox_SelectionChangeCommitted);
// add handler
comboBox.SelectionChangeCommitted += new EventHandler(comboBox_SelectionChangeCommitted);
}
}
void comboBox_SelectionChangeCommitted(object sender, EventArgs e)
{
// Allow current dispatch to complete (combo-box submitting its value)
// then EndEdit to commit the change to the row
dgv.BeginInvoke(new Action(() => dgv.EndEdit()));
}
I want a user to select a value from a ComboBox. Entries have to be suggested at user's text input.
Do I have to use events to enforce a System.Windows.Forms.ComboBox to contain a value from its own DataSource?
Example: Entries have to be suggested to the user... If I write "CO", the combo should suggest "CONGO" and "COLOMBIA", but only one of those values should be entered by the user. The user should not introduce "COfdfgdfg" or any random string.
Thanks!
Set the combo box's style to ComboBoxStyle.DropDownList
You have to use combobox.autocompletemode to get the names liek Congo,congress when the user enter the text like "CO"
you can your own datasource to comboBox1.AutoCompleteSource
this.comboBox1.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
this.comboBox1.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;
this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBox1.FormattingEnabled = true;
this.comboBox1.Location = new System.Drawing.Point(113, 192);
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size(121, 20);
Then populate the combox items source with objects like, that override the ToString method
public class POCLRO
{
public int ID { get; set; }
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
Edit: You have to do validation on text entered by the user in combobox , if the user selects the drop down item suggested by auto completemode the validation returns true else it returns false ...
Do something like below ...
Create a KeyDown event handler for the combobox and check for an Enter key. Note that after the user hits enter the text in the combobox is selected (as in, selected as if you were doing a cut or copy operation) and focus remains in the combobox.
If enter was pressed call a validation function that will do whatever you feel necessary if the value entered is equal with name that was stored in database...
You can call this same function in a Leave event handler to prevent the user from leaving the combobox until a valid selection is made.
private void ComboBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
ValidateSelection();
}
}
private bool validation()
{
// do validation here
}
private void ComboBox_Leave(object sender, EventArgs e)
{
if(!ValidateSelection())
{
ComboBox.Focus();
}
}
I've a very trivial requirement which makes me go nuts. I've a DataGridView in windows forms application. This contains one databound ComboBox Column. I'm using DisplayMember and ValueMember properties of that combobox.
Now my requirement is ComboBox should show the list of DisplayMembers in drop down list but when user selects one item from it, I should display the part of that DisplayMember in the combobox cell visible to the user. For example.
My display member list looks as below:
"Cust1 - Customer 1"
"Cust2 - Customer 2"
"Cust3 - Customer 3"
and when user selects any one of them from the above list (Say user selected 'Cust2 - Customer 2') then I need to display the value in the combobox column cell as only "Cust2" instead of complete DisplayMember text.
This DisplayMember list is a combination of two fields from the datasource bound to it i.e. First part points to CustomerCode field and second part points Customer name. I need to display only CustomerCode in the ComboBox cell after user selects one item from the drop down list.
How can I do this? Or should I come up with my own control which will have a different AutoCompleteCustomSource and display member value. Even I'm confused with that approach too.
Update: As no one has come up with any solution to my problem. Now I'm starting a bounty for that, also if anyone can suggest me other way to implement the same functionality, it would be great.
I've even tried to come up with my own control and tried to work on simple combobox to display a different value than the selected dropdown list, even that didn't work. Is there any other way to implement this? Any tips and tricks are greatly appreciable.
#Anurag: Here is the code which I've used.
Created a datagridview in the design mode. Created one column of type 'DataGridViewComboBoxColumn' that and named it as CustomerColumn.
In the designer file it looks like below:
private System.Windows.Forms.DataGridViewComboBoxColumn CustomerColumn;
This is the entity class which I've used for datasource
public class Customer
{
public int Id { get; set; }
public string CustCode { get; set; }
public string CustName { get; set; }
public string NameWithCode { get; set; }// CustCode - CustName format
}
In the form load event I'm doing the following:
CustomerColumn.DataSource = GetCustomers();
CustomerColumn.DisplayMember = "NameWithCode";
CustomerColumn.ValueMember = "Id";
I'm answering my own question because I've implemented my own solution to this by using custom control.
This custom control is created by keeping a textbox above combo box in such a way that only drop down button of combobox is visible.
Now I've created a custom column in datagridview deriving the DataGridViewEditingControl from my usercontrol.
I've added a property in Usercontrol which will take drop down list source from the control which is hosting datagridview.
Now in the EditingControlShowing event I'm setting this property as below
private void dataGridView2_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if(dataGridView2.CurrentCell.ColumnIndex.Equals(0) && e.Control is UserControl1)
{
var uscontrol = e.Control as UserControl1;
uscontrol.DropDownListSource = source;
}
}
This drop down list source is used in the usercontrol to set the autocompletesource to the textbox and datasource to the combobox as below:
Whenever I set the DropDownDataSource I'm firing an event in the usercontrol which will do the following. This is to ensure that every time EditingControlShowing event occurs for this column in DataGridView, this source is updated for textbox and combobox in usercontrol.
private void DropDownSourceChanged(object sender, EventArgs eventArgs)
{
textBox1.AutoCompleteCustomSource = DropDownListSource;
textBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
comboBox1.DataSource = DropDownListSource;
}
Now whenever user starts typing in the textbox autocomplete source will display dropdown list with 'NameWithCode' values and if user selects one of them then I'll set it to the Text propery overidden in my usercontrol which will be used for the cell value in the DataGridView. Now based on the Textbox text (which is NameWithCode) I can get the code part and set it to the text property.
If user uses combobox dropdown button to select the item then I'll get the combobox selected text and set it in the Textbox which is ultimately used by the cell for getting value.
This way I could achieve the solution I want.
#Homam, solution also works but when I change the ComboBox's DropDownStyle to allow the user to type the value in the combobox it behaves weirdly and not getting up to the mark solution for my requirement. Hence I used this solution.
I know that this is not perfect solution, but I looked for a better one and I didn't find, so I went to a workaround
I did the following:
when the user open the ComboBox, I change the DisplayMember to "NameWithCode"
when the user close it, I return it to "CustCode"
You can Access to the ComboBox control by DataGridView.EditingControlShowing event for the DataGridView.
The code:
private void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
var comboBox = e.Control as ComboBox;
comboBox.DropDown += (s1, e1) => comboBox.DisplayMember = "NameWithCode";
comboBox.DropDownClosed += (s2, e2) =>
{
// save the last selected item to return it after
// reassign the Display Member
var selectedItem = comboBox.SelectedItem;
comboBox.DisplayMember = "CustCode";
comboBox.SelectedItem = selectedItem;
};
}
Note: You have to start the DisplayMember with "CustCode"
Good luck!
Each time at the offensive of event dataGridView1_EditingControlShowing there is addition of new handlers for events comboBox.DropDown and comboBox.DropDownClosed. It results in the increase of number of these handlers and their repeated calls. This code decides this problem.
private void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
var comboBox = e.Control as ComboBox;
comboBox.DropDown += comboBox_DropDown;
comboBox.DropDownClosed += comboBox_DropDownClosed;
}
private void comboBox_DropDown(object sender, System.EventArgs e)
{
var comboBox = sender as ComboBox;
if(comboBox != null)
{
comboBox.DropDown -= comboBox_DropDown;
comboBox.DisplayMember = "NameWithCode";
}
}
private void comboBox_DropDownClosed(object sender, System.EventArgs e)
{
var comboBox = sender as ComboBox;
if(comboBox != null)
{
comboBox.DropDownClosed -= comboBox_DropDownClosed;
var selectedItem = comboBox.SelectedItem;
comboBox.DisplayMember = "CustCode";
comboBox.SelectedItem = selectedItem;
}
}