I have a ListBox control populated with branches of a large retail chain. The staff using the system have to log in to the relevant branch, and I would like them to be able to search the ListBox to find their branch.
I have created an event handler for when text in the search box changes, and attempted to use code sound on StackOverflow already:
private int lastMatch = 0;
private void txtSearch_TextChanged(object sender, EventArgs e)
{
int x = 0;
string match = txtSearch.Text;
if (txtSearch.Text.Length != 0)
{
bool found = true;
while (found)
{
if (lbBranches.Items.Count == x)
{
lbBranches.SetSelected(lastMatch, true);
found = false;
}
else
{
lbBranches.SetSelected(x, true);
match = lbBranches.SelectedValue.ToString();
if (match.Contains(txtSearch.Text))
{
lastMatch = x;
found = false;
}
x++;
}
}
}
}
When I compile and start typing into the search box, I get this error:
Object reference not set to an instance of an object.
The line in question is:
match = lbBranches.SelectedValue.ToString();
I have no idea what could be wrong there, anyone got an idea?
Thanks!
SelectedValue of the listbox will only return a value if you have specified the ValueMember property of the listbox to indicate a property from which you would like to read the value for the selected item. The property you want to use in this case is SelectedItem:
match = lbBranches.SelectedItem.ToString();
when the user is entering text it's possible that no value has been selected (hence the error) -- keep in mind that what is being entered by the user has no mandatory or direct association with selections in the controls listbox sub-element
it's possible what you're doing might be simpler to implement with a full combo-box control and I think some of the examples at MSDN could be very helpful for you as well
Related
I have a ListBox with words and I need to click a button that opens an InputBox where I can search for a word and the program will run the ListBox and highlight the word I wrote in the InputBox if it's there. If the program reaches the end of the list and doesn't find the word then I'll get a MessageBox saying the word I'm looking for isn't there. I need to use some sort of cycle for this program.
I know how to make the button, InputBox and the error MessageBox, but I don't know how to do the searching and cycle.
I've read a lot of similar questions here but I don't think any of them return the result I'm looking for.
Can anyone help me? Or redirect me to a post with the answer?
This is for Winforms.
That should get you on track, it's pretty much self-explanative:
whenever text changes
find matching items in list
select them
Code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
var textBox = sender as TextBox ?? throw new InvalidOperationException();
var text = textBox.Text;
if (string.IsNullOrWhiteSpace(text))
return; // nothing to search for
const StringComparison comparison = StringComparison.InvariantCultureIgnoreCase; // maybe change this
// find items matching text
var indices = new List<int>();
for (var i = 0; i < listBox1.Items.Count; i++)
{
var item = listBox1.Items[i];
if (string.Equals(item?.ToString(), text, comparison))
indices.Add(i);
}
// select them in list
if (!indices.Any())
return;
listBox1.SelectedIndices.Clear();
foreach (var index in indices)
listBox1.SelectedIndices.Add(index);
}
Of course, list selection mode has to be multiple for it to work properly.
Also, you will need to clear selection if there are no matches so as to not leave the UI in an ambiguous state (not done).
I am trying to create a text input field with autocomplete functionality. The list of available options is huge (50,000+) and will need to be queried on TextChanged (after the first 3 characters have been entered).
I have a 99%-working solution with TextBox, setting AutoCompleteCustomSource to my new AutoCompleteStringCollection in the TextChanged event, but that results in occasional memory access violations due to a well-documented bug in the underlying AutoComplete implementation...
Microsoft Support say "Do not modify the AutoComplete candidate list dynamically during key events"...
Several SO threads: 1, 2, 3
These threads have some suggestions on how to prevent the exceptions but nothing seems to completely eliminate them, so I'm looking for an alternative. have tried switching to a ComboBox-based solution but can't get it to behave as I want.
After the user types the third character, I update the ComboBox's DataSource but the first item is automatically selected. The user is not able to continue typing the rest of the name.
The ComboBox items are not visible until the user clicks the triangle to expand the list
If the user selects the text they have entered and starts typing, I set DataSource to null to remove the list of suggestions. Doing this puts the cursor at the start of the text, so their characters appear in completely the wrong order!
My View:
public event EventHandler SearchTextChanged;
public event EventHandler InstrumentSelected;
public Instrument CurrentInstrument
{
get { return comboBoxQuickSearch.SelectedItem as Instrument; }
}
public IEnumerable<Instrument> Suggestions
{
get { return comboBoxQuickSearch.DataSource as IEnumerable<Instrument>; }
set
{
comboBoxQuickSearch.DataSource = value;
comboBoxQuickSearch.DisplayMember = "Name";
}
}
public string SearchText
{
get { return comboBoxQuickSearch.Text; }
}
private void comboBoxQuickSearch_TextChanged(object sender, EventArgs e)
{
if (SearchTextChanged != null)
{
SearchTextChanged(sender, e);
}
}
private void comboBoxQuickSearch_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter && InstrumentSelected != null)
{
InstrumentSelected(sender, e);
}
}
My Presenter:
private void SearchTextChanged(object sender, EventArgs e)
{
lock (searchLock)
{
// Do not update list of suggestions if:
// 1) an instrument has already been selected
// (the user may be scrolling through suggestion list)
// 2) a search has taken place within the last MIN_SEARCH_INTERVAL
if (DateTime.Now - quickSearchTimeStamp < minimumSearchInterval
|| (view.Suggestions != null && view.Suggestions.Any(i => i.Name == view.SearchText)))
{
return;
}
string searchText = view.SearchText.Trim();
if (searchText.Length < SEARCH_PREFIX_LENGTH)
{
// Do not show suggestions
view.Suggestions = null;
searchAgain = false;
}
// If the prefix has been entered or changed,
// or another search is needed to display the full sublist
else if (searchText.Length == SEARCH_PREFIX_LENGTH
|| searchText.Substring(0, SEARCH_PREFIX_LENGTH) != searchTextPrefix
|| searchAgain)
{
// Record the current time and prefix
quickSearchTimeStamp = DateTime.Now;
searchTextPrefix = searchText.Substring(0, SEARCH_PREFIX_LENGTH);
// Query matches from DB
IList<Instrument> matches = QueryMatches(searchText);
// Update suggestions
view.Suggestions = matches;
// If a large number of results was received, search again on the next chararacter
// This ensures the full match list is presented
searchAgain = matches.Count() > MAX_RESULTS;
}
}
}
(The searchAgain bit is left over from the TextBox implementation, where the AutoCompleteCustomSource wouldn't always show the complete list if it contained too many items.)
Can I get the ComboBox to work as I want it to, providing suggestions as the user types, given my requirement to query those suggestions on TextChanged?
Is there some other combination of controls I should use for a better user experience, e.g. ListBox?
I have 2 LookUpEdit controls from DevExpress on my form. Both use an ObservableCollection as it's datasource, one being of type string and the other of type double. The LookUpEdit control has an event called ProcessNewValue which fires when, you guessed it, a new value is entered in the control. I've added some code in this event to add the newly added value to the ObservableCollection and it automatically selects it once done. This works as expected for the string LooUpEdit but when I try it with the double LookUpEdit`, it adds it to the collection but then it clears out the control.
Here's the code to load the controls, which gets called in Form_Load():
void InitControls()
{
double[] issueNumbers = new double[5];
issueNumbers[0] = 155;
issueNumbers[1] = 156;
issueNumbers[2] = 157;
issueNumbers[3] = 158;
issueNumbers[4] = 159;
ObservableCollection<double> issues = new ObservableCollection<double>(issueNumbers);
lookupIssues.Properties.DataSource = issues;
DevExpress.XtraEditors.Controls.LookUpColumnInfoCollection colInfo = lookupIssues.Properties.Columns;
colInfo.Clear();
colInfo.Add(new DevExpress.XtraEditors.Controls.LookUpColumnInfo("Column"));
colInfo[0].Caption = "Issue ID's";
string[] stringNumbers = Array.ConvertAll<double, string>(issueNumbers, Convert.ToString);
ObservableCollection<string> issuesString = new ObservableCollection<string>(stringNumbers);
lookupStringValue.Properties.DataSource = issuesString;
colInfo.Clear();
colInfo.Add(new DevExpress.XtraEditors.Controls.LookUpColumnInfo("Column"));
colInfo[0].Caption = "String Issue ID's";
}
And here's the ProcessNewValue event for both (I've renamed them to try to make it easier to see which does what):
private void OnProcessNewValue_Double(object sender, DevExpress.XtraEditors.Controls.ProcessNewValueEventArgs e)
{
ObservableCollection<double> source = (ObservableCollection<double>)(sender as LookUpEdit).Properties.DataSource;
if (source != null)
{
if ((sender as LookUpEdit).Text.Length > 0)
{
source.Add(Convert.ToDouble((sender as LookUpEdit).Text));
(sender as LookUpEdit).Refresh();
}
}
e.Handled = true;
}
private void OnProcessNewValue_String(object sender, DevExpress.XtraEditors.Controls.ProcessNewValueEventArgs e)
{
ObservableCollection<string> source = (ObservableCollection<string>)(sender as LookUpEdit).Properties.DataSource;
if (source != null)
{
if ((sender as LookUpEdit).Text.Length > 0)
{
source.Add((sender as LookUpEdit).Text);
(sender as LookUpEdit).Refresh();
}
}
e.Handled = true;
}
As you can see, the code it identical with the exception of one converting text to a double before adding it to the collection.
Anyone know why the double value gets added to the collection but the control doesn't automatically select it like it does with a string collection? I've even tried to hard-code the newly added value right after e.Handled = true; but it still doesn't select it. What's weird is that if I run it through the debugger, I can step through and see that the lookupIssues control indeed gets the newly added value AND it's Text property is set to it, but as soon as the event terminates, the control clears it out.....really strange.
Any help is greatly appreciated!
BTW, I can add a link to a sample project that duplicates the problem but you would need to have DevExpress v12.2.6 controls installed in order to compile the project.
I posted this to the DevExpress team as well and they were gracious enough to provide the solution:
I agree that this discrepancy appears confusing as-is. The reason for the discrepancy is LookUpEdit.ProcessNewValueCore makes a call to RepositoryItemLookUpEdit.GetKeyValueByDisplayValue which returns a null value from the LookUpListDataAdapter because no implicit conversion exists from double to string. You may resolve the discrepancy with the following change to your ProcessNewValue handler:
private void OnProcessNewValue_Double(object sender, DevExpress.XtraEditors.Controls.ProcessNewValueEventArgs e)
{
ObservableCollection<double> source = (ObservableCollection<double>)(sender as LookUpEdit).Properties.DataSource;
if (source != null) {
if ((sender as LookUpEdit).Text.Length > 0) {
double val = Convert.ToDouble((sender as LookUpEdit).Text);
source.Add(val);
e.DisplayValue = val;
(sender as LookUpEdit).Refresh();
}
}
e.Handled = true;
}
The control now behaves as expected. I hope this can help someone else out :)
I have a ListView in a C# based win-form project. Is it possible to limit the maximum length of the title of all ListViewItem inside the ListView ?
UPDATE
I mean the input length , I set the item to editable , so users can rename the items
UPDATE2
Right , It's called "the text" of that item , not the title.
You can utilise the label after edit event of a listview. Here is a sample.
private void listview1_AfterLabelEdit(object sender, LabelEditEventArgs e)
{
try
{
const int maxPermittedLength = 1;
if (e.Label.Length > maxPermittedLength)
{
//trim text
listview1.Items[e.Item].SubItems[0].Text = listview1.Items[e.Item].SubItems[0].Text.Substring(0, maxPermittedLength); //or something similar
//or
//show a warning message
//or
e.CancelEdit = true; //cancel the edit
}
}
catch (Exception ex)
{
}
}
Remember, its tricky, not straightforward, you will have to take care of a few exceptions, but thats homework.. The above code is not a working code, but you have the idea now how to go about it. Read the documentation well, it has a nice example and a warning associated with this event.
What do you mean ListViewItem's title ? is it the item text you mean ? I believe whatever retrievable is fixable and controllable. If it is the item text, you can write a check method
public string SimplifyTxt(string input)
{
if(input.Length>LIMIT_NUMBER)
{
//please shorten the string before display
}
return retStr;
}
and it then can be assigned as
listview1.items.add(new Listviewitem{Text=retVal});
I am working on C#.net windows application. i am filling combobox on my winform by using follows.
cmbEMPType.DataSource = objEntityManager.EmployeeTypes();
cmbEMPType.DisplayMember = "EMPTypeName";
cmbEMPType.ValueMember = "EMPTypeId";
where objEntityManager.EmployeeTypes(); in the manager method that gets the List from Linq to sql server. this is working fine.
but as i select the item form combo box, and clicked the button then in the button click event i am getting cmbEMPType.SelectedValue as EmpType return type rather than its Id. why should this? I don't want to create one more EmpType object. need simple selected value. also can not keep faith with SelectedIndex. it may varies for item each time.
**Edited**
public List<EMPType> EmployeeTypes()
{
List<EMPType> EMPTypeList = null;
try
{
if (CommonDataObject.dataContext.EMPAllTypes.Any())
{
EMPTypeList = CommonDataObject.dataContext.EMPAllTypes.ToList();
}
return EMPTypeList;
}
catch
{
return EMPTypeList;
}
}
Edited
private void btnSave_Click(object sender, EventArgs e)
{
iEMPTypeId = cmbEMPType.SelectedValue;
}
here I must get inte. but asking of create the EMPType object.
This is the correct and expected behavior, you can't change it.
SelectedValue should return the type of the property, e.g. if EMPTypeId is integer it should return integer - please post more code so that we can try figuring out why you get different return value.
If by any chance you're using SelectedItem then have such code to get the ID:
int selectedID = (cmbEMPType.SelectedItem as EmpType).EMPTypeId;
To handle cases when there's nothing selected:
object oSelectedEmp = cmbEMPType.SelectedItem;
int selectedID = oSelectedEmp == null ? -1 : (oSelectedEmp as EmpType).EMPTypeId;
The problem is the sequence of your code. Please remove the first line code to the last line. You will get an int value (iEMPTypeId) from cmbEMPType.SelectedValue.
cmbEMPType.DisplayMember = "EMPTypeName";
cmbEMPType.ValueMember = "EMPTypeId";
cmbEMPType.DataSource = objEntityManager.EmployeeTypes();
iEMPTypeId = cmbEMPType.SelectedValue
Another option is to override the toString function in your EMPType class. As Edwin de Koning stated "If no ValueMember is specified it gives a ToString() representation."
Something like (I cant test it at the moment):
public override string ToString()
{
return this.ID;
}
You can check out this article: http://msdn.microsoft.com/en-us/library/ms173154(v=vs.80).aspx