In a WinForms dataviewer project I've made a ComboBox to select a filter value. The list items come from of a database query. They are sorted descending. The ComboBox uses AutoCompleteMode.Append. Although the dropdown list is sorted descending, the AutoComplete always suggests the lowest matching value instead of the highest. This happens even if I explicitly populate the AutoCompleteCustomSource with descending data.
Does anyone know how to make the AutoComplete suggesting the highest matching value?
The ComboBox looks like this after typing "010":
This is a part of the dropdown list:
...
012-0020-00
010-0070-00
010-0069-00
010-0068-00
008-1018-00
...
Why this matters:
I will use this filter for various string data containing numbers, like parts codes, document codes, project codes etc. Newer entries have higher numbers. And the newest entries are queried most often. In the above example, 010-0070-00 ist the newest part code of the 010 group. Therefore I expect the AutoComplete to show 010-0070-00 after I have typed 010.
This project replaces an MS Access front end. An Access ComboBox suggests the highest value if the list is sorted descending resp. the lowest value if sorted ascending. But Access ComboBoxes are not WinForms controls.
Any suggestions are welcome.
Example using a ToolStripDropDown:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
String[] data = "012-0020-00,010-0070-00,010-0069-00,010-0068-00,008-1018-00".Split(',');
ComboBox combo = new ComboBox();
//ToolStripDropDownMenu menu = new ToolStripDropDownMenu(); // has image/icon border
ToolStripDropDown menu = new ToolStripDropDown(); // plain menu, no image/icon border
menu.AutoClose = false;
bool isAdjusting = false;
combo.LostFocus += delegate {
if (!isAdjusting)
menu.Close();
};
menu.ItemClicked += (o, e) => {
isAdjusting = true;
combo.Text = e.ClickedItem.Text;
menu.Close();
isAdjusting = false;
};
combo.TextChanged += delegate {
if (isAdjusting)
return;
isAdjusting = true;
String prefix = combo.Text;
menu.SuspendLayout();
for (int i = menu.Items.Count - 1; i >= 0; i--) {
var item = menu.Items[i];
menu.Items.RemoveAt(i);
item.Dispose();
}
foreach (String part in data) {
if (part.StartsWith(prefix))
menu.Items.Add(new ToolStripMenuItem(part));
}
menu.ResumeLayout();
menu.Show(combo, 0, combo.Height);
combo.Focus();
combo.SelectionStart = prefix.Length;
isAdjusting = false;
};
Form f7 = new Form();
f7.Controls.Add(combo);
Application.Run(f7);
Related
I am working in a simple C# form. I have a total of 20 ComboBoxes . 10 ComboBoxes will contain similar data type and will have very similar name (CB1, CB2, CB3, ... CB10). Each ComboBox was loaded with a list of 5 elements A,B,C,D,E (by this I meant that I added those values to each of the 10 "CB" ComboBoxes). My intend is that the user can have the ability to select items (one out of A,B,C,D,E) from either 1 combobox, or 2 comboboxes, . . . ., or all of them (10 comboboxes).
I wish to store the data from the ComboBoxes where an item was selected in a list or array. For that I would like to iterate through the 10 ComboBoxes (the comboboxes which names are CB1, CB2, CB3, ..., CB10), check if the an item was selected in the combobox, and store the value selected in the ComboBox into a list (in the code below the list is called symbols). At the end the length of my symbols list (number of items) will depend on how many ComboBoxes the user selected from. Here is the code I am using:
List<string> symbols = new List<string>();
for (int i = 1; i <= 10; i++)
{
var my_comboBox = this.Controls["CB" + i.ToString()];
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
when I run the code I get the following error.
Object reference not set to an instance of an object.
Could anyone please explain what I am doing wrong? I got the code from another question that was posted and answered, below is the link to that question. Thanks in advance.
How can I iterate all ComboBoxes controls with a loop in C#?
I also tried the other alternative proposed on the answers to the questions cited. But it did not work. It does not go through the foreach loop (no error is shown though). Here is the code for option 2.
List<string> symbols = new List<string>();
var comboBoxes = this.Controls.OfType<ComboBox>().Where(x => x.Name.StartsWith("CB"));
foreach (var cmbBox in comboBoxes)
{
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
Again if anyone can please provide me with ideas to what I am doing wrong that would be very nice. Thanks in advance once more.
The ComboBoxes belong to the form as shown in the picture below. In there the ComboBoxes are called component_CB1, component_CB2, component_CB3, ..., component_CB10 (I changed the name of the ComboBoxes in the question to CB to make it easier to understand).
screenshot of my solution explorer
Thanks to everyone who contributed to find the answer to this problem. Please read the comments to identify the contributors.
The answer is that you can iterate through selected ComboBoxes in C#. However for this to work you need to make sure you know to what container control your ComboBoxes are added.
To know to container control your ComboBoxes are added to, go to View → Other Windows → Document Outline. You can see if those controls are directly children of the form or they are children of another container control.
If the ComboBoxes are added to your form directly, then here there are two alternatives to iterate through the ComboBoxes:
ALTERNATIVE 1: (ComboBoxes added to the form directly)
List<string> symbols = new List<string>();
for (int i = 1; i <= 10; i++)
{ //CB is the begining of the name of the comboboxes CB1, CB2, ... CB10
var my_comboBox = this.Controls["CB" + i.ToString()];
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
ALTERNATIVE 2: (ComboBoxes added to the form directly)
List<string> symbols = new List<string>();
//CB is the begining of the name of the comboboxes CB1, CB2, ... CB10
var comboBoxes = this.Controls.OfType<ComboBox>().Where(x=>x.Name.StartsWith("CB"));
foreach (var cmbBox in comboBoxes)
{
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
If the ComboBoxes are added to a different control container (e.g. a tab in a TabControl as in my initial case), you must specify the control container name rather than "this". Here are two alternatives taking as example the ComboBoxes Cb1, CB2, CB3, ..., CB10 that are added to a tab called tab1.
ALTERNATIVE 1: (ComboBoxes added to a tab of a TabControl)
List<string> symbols = new List<string>();
for (int i = 1; i <= 10; i++)
{//CB is the begining of the name of the comboboxes CB1, CB2, ... CB10
var my_comboBox = tab1.Controls["CB" + i.ToString()];
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
ALTERNATIVE 2: (ComboBoxes added to a tab of a TabControl)
List<string> symbols = new List<string>();
//CB is the begining of the name of the comboboxes CB1, CB2, ... CB10
var comboBoxes = tab1.Controls.OfType<ComboBox>().Where(x
=>x.Name.StartsWith("CB"));
foreach (var cmbBox in comboBoxes)
{
if (null != my_comboBox.SelectedItem)
{ symbols.add(my_comboBox.Text); }
}
Thanks for the help again. I hope this can be useful for others.
I have a Listview that contains all the guests from the database
public FormGuestManagement()
{
InitializeComponent();
listViewGuests.View = View.Details;
listViewGuests.GridLines = true;
listViewGuests.Scrollable = true;
listViewGuests.FullRowSelect = true;
listViewGuests.HideSelection = false;
var guests = Repository.GetAllGuests();
foreach (var guest in guests)
{
ListViewItem lvData = new ListViewItem(guest.AccountID.ToString());
lvData.SubItems.Add(guest.Username);
lvData.SubItems.Add(guest.Email);
lvData.SubItems.Add(guest.FirstName);
lvData.SubItems.Add(guest.LastName);
lvData.SubItems.Add(guest.TelephoneNumber);
lvData.SubItems.Add(guest.AddressLine1);
lvData.SubItems.Add(guest.AddressLine2);
lvData.SubItems.Add(guest.City);
lvData.SubItems.Add(guest.State);
lvData.SubItems.Add(guest.Postcode);
lvData.SubItems.Add(guest.Country);
listViewGuests.Items.Add(lvData);
}
}
How would it be possible to search one single column (in this case guest.Firstname) with a string and only show the values that match the string (hide the others) in the listview?
Thank you for your time!
The ListView control does not provide the functionality you want. In order to simulate what you describe, you would have to rebuild the desired list every time a filter condition changes.
Or you could do as one commenter suggested and use something more robust, such as a DataGridView. This control provides true Row/Column behaviors and takes a DataSource that can be bound.
It's possible with LINQ and should be very easy:
using System.Linq;
var result = guests.Where(guest => guest.FirstName.Equals("Test"));
I am new in Winform application, here i am using Entity framework, when i binding values to a combobox from sql table, i need to set first combobox item as "Please select", how can i set this..?
var qry = context.Tbl_EmployeeDetails.Where(x => x.IsDeleted == false).ToList();
if(qry!=null)
{
drpname.ValueMember = "RecordId";
drpname.DisplayMember = "Name";
drpname.DataSource = qry;
}
how can i set this first item as "please select" and value '0'
another suggestion in winform that..
how can i set value in a datagridview linkbutton column when i set 'Edit', 'Delete' columns are as linkbutton
You can insert that item in the beginning of the list.
Code:
var items = db.Tbl_EmployeeDetails.Where(x => x.IsDeleted == false).ToList();
items.Insert(0,new Tbl_EmployeeDetail() { RecordId= 0, Name = "[Please Select an Item]" });
drpname.DropDownStyle = ComboBoxStyle.DropDownList; //optional
drpname.ValueMember = "RecordId";
drpname.DisplayMember = "Name";
drpname.DataSource = items;
drpname.SelectedIndex = 0;
Screenshot:
Note
To have a hint in ComboBox without adding an item, take a look at following post:
Set hint or watermark or default text for ComboBox without adding it as item:
After you load your var qry which will be a List<string>
you want to do the following
var qry = context.Tbl_EmployeeDetails.Where(x => x.IsDeleted == false).ToList();
if(qry!=null)
{
drpname.ValueMember = "RecordId";
drpname.DisplayMember = "Name";
drpname.DataSource = qry;
drpname.Items.Insert(0, "--Please Select--");
drpname.SelectedIndex = 0;
}
Or you could have easily added it to qry since you are returning the data ToList()
for example
var qry = context.Tbl_EmployeeDetails.Where(x => x.IsDeleted == false).ToList();
qry.Insert(0, "--Please Select--");
if(qry!=null)
{
drpname.ValueMember = "RecordId";
drpname.DisplayMember = "Name";
drpname.DataSource = qry;
drpname.SelectedIndex = 0;
}
Your combobox cannot contain additional items if you are binding it to a data source. You have a few options here:
Don't bind it to a data source, i.e. populate it manually from your data set by looping through the items in your query and adding them individually to the combobox; this will allow you the opportunity to first add your "Please Select" item. This can be complicated because you will actually have to add the string names as the items in the combobox, and then you can set the item's Tag property to the actual query row to reference it later.
Include a fake item returned from your query that has a display member of "please select". Note, I don't recommend this approach because you're allowing your presentation logic to leak into your data/business layer.
Use a third-party control instead of the win forms combobox. I've used Infragistics and they have the ability to display a "suggestion" that's not actually in the list when nothing is selected.
In Winforms I wanted to show a list box having item index number and item exactly as shown in the below . I have gone through many SO links and also many articles also, most of them are using ListView instead of ListBox and also instead of showing List Item index they showing Image+ Item in it. I am ok with the listview but my objective is to show the Items with their corresponding index and on select it should give me the item only. Can anybody provide me any link or idea to achieve this in Winform using C#.
Thanks in advance.
I think best option for you is using DataGridView with two columns:
DataGridViewButtonColumn, AutoSizeMode = AllCells
DataGridViewTextBoxColumn, ReadOnly = true, AutoSizeMode = Fill
Also RowHeadersVisible and ColumnHeadersVisible to false. Result:
Code:
string[] members = { "Beverages", "Condiments", "Confections" };
dataGridView1.DataSource = members.Select((x, i) => new { Value = x, Index = i })
.ToList();
Also you need to set columns DataPropertyName to Index and Value.
UPDATE (for .NET 2.0):
string[] members = { "Beverages", "Condiments", "Confections" };
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = members;
Subscribe to CellFormatting event:
void dataGridView1_CellFormatting(object sender,
DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex == 0)
{
e.Value = e.RowIndex;
return;
}
e.Value = dataGridView1.Rows[e.RowIndex].DataBoundItem;
}
You can customize your Listbox
Following link will guide you right direction.
http://www.codeproject.com/Articles/15464/Extending-the-ListBox-to-show-more-complex-items
I think you should consider setting the DrawMode property on the listbox to DrawMode.Manual and handle the drawing of the items in your code.
Have a look at http://msdn.microsoft.com/en-us/library/system.windows.forms.drawmode.aspx
i have a drop down list on my page with Countries, here is my code behind where i grab the ddl id, and bind my datasource:
DropDownList ddlSalary = (DropDownList)this.FindControl(MyControls.CountryDDL);
if (ddlSalary != null)
{
ddlSalary.DataSource = MyMethods.LoadCountries();
ddlSalary.DataValueField = "Value";
ddlSalary.DataTextField = "Text";
ddlSalary.DataBind();
}
My Countries ive written in are Alphabetical. But id like the option to move a specific one to the top of the List, or perhaps an Auto-Select. Example 'United Kingdom' is first in the list
What would be the most efficient way to go about doing this ?
I think this logic is better suited to be placed in your service/model provider (MyMethods.LoadCountries(); ).
Something like:
public static List<Country> LoadOrderedCountries(){
var orderedCounteries = MyMethods.LoadCountries();
orderedCounteries .Sort(); // Just to make sure alphabetical order, assuming that Country implements IComparable
var defaultCountry = Country.GetDefault();
orderedCounteries .Remove(defaultCountry);
orderedCounteries .Insert(0, defaultCountry);
return orderedCounteries ;
}
Here is a quick & dirty way to do it with SQL. I prefer to do it at database level
What I am doing is selecting the items you want first in the list
Union ALL with items you want in the list in ascending order by name excluding the ones in my first query
Results are with first Item at top irrespective of 2nd query
select * from ( (select Empcode,Empname from
mySchema.Employees where EmpCode not in (90) ) UNION ALL
select EmpCode,EmpName from mySchema.Employeeswhere
EmpCode=90 order by EmpName) as z
You ca also set the value selected on pageLoad as
ddlYourDropDownList.SelectedValue="90";
After databinding your datasource, you can just sort the ListItems
A better way to do so is to do the sorting in your datasource (you haven't supplied enough information regarding where are you getting the data from)
private void SortDDL(ref DropDownList objDDL)
{
ArrayList textList = new ArrayList();
ArrayList valueList = new ArrayList();
foreach (ListItem li in objDDL.Items)
{
textList.Add(li.Text);
}
textList.Sort();
foreach (object item in textList)
{
string value = objDDL.Items.FindByText(item.ToString()).Value;
valueList.Add(value);
}
objDDL.Items.Clear();
for(int i = 0; i < textList.Count; i++)
{
ListItem objItem = new ListItem(textList[i].ToString(), valueList[i].ToString());
objDDL.Items.Add(objItem);
}
}
I normally do this in the databound event
DropDownList ddlSalary = (DropDownList)this.FindControl(MyControls.CountryDDL);
if (ddlSalary != null)
{
ddlSalary.DataSource = MyMethods.LoadCountries();
ddlSalary.DataValueField = "Value";
ddlSalary.DataTextField = "Text";
ddlSalary.DataBind();
ddlSalary.DataBound += ddlSalary_DataBound;
}
protected void ddlSalary_DataBound(object sender, EventArgs e)
{
ListItem MovingItem = ddlSalary.Items.FindByValue("yourvalue");
ddlSalary.Items.Remove(MovingItem);
ddlSalary.Items.Insert(0, MovingItem);
}
Edit: this method is more suited to adding an a "Other" type option, which is not supplied by your datasource. Amby's solution is better than this.