Add empty entry to combobox bound to entity list - c#

I use a ComboBox which is bound to a List<> of Entities. How can I add a "Not selected" entry to the combobox? Adding null to the list results in empty combobox.

If you're binding to IEnumerable list of entities you can certainly add your empty object manually.
For example
var qry = from c in Entities
select c;
var lst = qry.ToList();
var entity = new Entity();
entity.EntityId= -1;
entity.EntityDesc = "(All)";
lst.Insert(0, entity);
MyComboBox.DataSource = lst;
MyComboBox.DisplayMember = "EntityDesc"
MyComboBox.ValueMember = "EntityId"

You should use an empty string or other unique text pattern instead of null.
And then You can handle the Format event of the Combobox to intercept the <empty> and display an alternate text.
private void comboBox1_Format(object sender, ListControlConvertEventArgs e)
{
e.Value = FormatForCombobox(e.ListItem);
}
private string FormatForCombobox(object value)
{
string v = (string) value;
if (v == string.Empty)
v = "<no Selection>";
return v;
}

Related

unable select combobox value after form is loaded

cant select combo item by value after its passed to form. Comb is populated properly i get all the customers displayed by Text.
public OrderViewerForm(string orderId)
{
InitializeComponent();
PopulateCustomerCombobox();
PopulateForm(orderId);
PopulateProductsTable();
}
private void PopulateForm(string orderId)
{
OrderModel order = db.LoadOrder(orderId);
List<ProductsOrderedModel> productsOrdered = db.ProductsOrdered_Load(orderIdValue.Text);
orderIdValue.Text = order.Id.ToString();
customerIdValue.Text = order.CustomerId.ToString();
customerIdCombo.SelectedValue = order.CustomerId;
}
private void PopulateCustomerCombobox()
{
customerIdCombo.Items.Clear();
List<CustomerModel> customer = db.CustomerGet_All();
foreach (CustomerModel c in customer)
{
customerIdCombo.DisplayMember = "Text";
customerIdCombo.ValueMember = "Value";
customerIdCombo.Items.Add(new { Text = c.FullInfo, Value = c.Id });
}
}
At the end order.CustomerId is set properly but customerIdCombo.SelectedValue stays null. What am I doing wrong?
I would suggest switching to databinding here. Your foreach looks weird by the way.
private void PopulateCustomerCombobox()
{
customerIdCombo.DisplayMember = "Text";
customerIdCombo.ValueMember = "Value";
customerIdCombo.DataSource = db.CustomerGet_All().Select(c=> new { Text = c.FullInfo, Value = c.Id});
}
Thinking about it you may want to drop the hole anonymous type thing. why not simply bind the list you get from CustomerGet_All?

Extracting specific string from combobox select

I have a combobox for an item list that I populate using the following code:
List<string> comboboxItems = new List<string>();
foreach (var p in products)
{
var x = p.Product;
foreach (var pn in x)
{
comboboxItems.Add(pn.name + " :Qty " + pn.quantity_available
+ " :Tax " + pn.default_tax_tier);
}
}
cmbItems.DataSource = comboboxItems;
What should I do in order to get the value, pn.name only when the combobox item is selected?
Using WinForms.
You have to handle the event DataGridView.EditingControlShowing event, in there you can access the actual combobox and register the SelectedIndexChanged event handler like this:
//EditingControlShowing event handler for your dataGridView1
private void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e){
if(dataGridView1.CurrentCell.OwningColumn == cmbItems){
var combo = e.Control as ComboBox;
combo.SelectedIndexChanged -= cmbItems_SelectedIndexChanged;
combo.SelectedIndexChanged += cmbItems_SelectedIndexChanged;
}
}
private void cmbItems_SelectedIndexChanged(object sender, EventArgs e){
var combo = sender as ComboBox;
//Note that SelectedItem may be null
var s = Convert.ToString(combo.SelectedItem);
int i = s.IndexOf(" :Qty");
var selectedName = i == -1 ? "" : s.Substring(0,i+1).TrimEnd();
//other code ...
}
you should create an item such as
public class CboItem
{
public string Text { get; set; }
public object Value { get; set; }
public override string ToString()
{
return Text;
}
}
then you can create easily using something like
CboItem item = new CboItem();
item.Text = "My Item";
item.Value = "Anything";
in the Value you can store your var pn whatever it is.
then you can retrieve like so :
((CboItem)comboBox1.SelectedItem).Value;
You will need to cast the result above to the proper type you stored inside as the Value is of type object.
We can also regular expressions to extract data from string.
Create string variable in the below format
string inputPattern = "{0} :Qty {1} :Tax {2}";
While inserting the data into combo box,
comboboxItems.Add(string.Format(inputPattern, p.Name, p.Quantity_Available, p.Tax));
After you added it, to extract the strings we can use Regex, code snippet below.
string extractPattern = "(?<Name>.*) :Qty (?<Qty>.*) :Tax (?<Tax>.*)";
foreach (var item in (comboBox1.DataSource as List<string>))
{
var matches = Regex.Match(item, extractPattern);
if (matches.Groups["Name"].Success)
{
MessageBox.Show(matches.Groups["Name"].Value);
}
if (matches.Groups["Qty"].Success)
{
MessageBox.Show(matches.Groups["Qty"].Value);
}
if (matches.Groups["Tax"].Success)
{
MessageBox.Show(matches.Groups["Tax"].Value);
}
}

Looping through a Listbox created on the fly

I am working on exporting data and right now some fields export the value, instead of the text. So I am saving the object that returns the text and value to a list box and matching it to the value in the listbox from the object like so:
MaterialDB materials = new MaterialDB();
DropDownList listBoxMaterials = new DropDownList();
listBoxMaterials.DataSource = materials.GetItems(ModuleId, TabId);
listBoxMaterials.DataBind();
string materialString = "";
foreach (ListItem i in listBoxMaterials.Items)
{
if (i.Value == row["MaterialTypeID"].ToString())
{
materialString = i.Text;
}
}
When I use this for the i.Value it always returns "System.Data.DataRowView" instead of the actual value. I'm doing this all in code behind. Anyway around this to get it to work?
Thanks!
You need to set the DataTextField and DataValueField properties of the DropDownList. Example:
MaterialDB materials = new MaterialDB();
DropDownList listBoxMaterials = new DropDownList();
listBoxMaterials.DataSource = materials.GetItems(ModuleId, TabId);
listBoxMaterials.DataTextField = "MaterialName";
listBoxMaterials.DataTextValue = "MaterialID";
listBoxMaterials.DataBind();
string materialString = "";
foreach (ListItem i in listBoxMaterials.Items)
{
if (i.Value == row["MaterialTypeID"].ToString())
{
materialString = i.Text;
}
}

How to delete object from combobox?

I have a combobox with objects of Foo type, here is the Foo class:
public class Foo
{
public string name { get; set; }
public string path { get; set; }
}
The Foo.name is the displayed text in the combobox and Foo.path is the value of the selected option.
I want to delete an option from the combobox after some operation I made.
I've tried these ways:
1
comboBox2.Items.Remove(#comboBox2.Text);
2
comboBox2.Items.Remove(#comboBox2.SelectedValue.ToString());
3
Foo ToDelete = new Foo();
ToDelete.name = #comboBox2.Text;
ToDelete.path = #comboBox2.SelectedValue.ToString();
comboBox2.Items.Remove(ToDelete);
Nothing works for me. : / How to do this?
UPDATE
This is how I'm initializing my combobox:
string[] filePaths = Directory.GetFiles(sites.paths[comboBox1.SelectedIndex]);
List<Foo> combo2data = new List<Foo>();
foreach (string s in filePaths)
{
Foo fileInsert = new Foo();
fileInsert.path = s;
fileInsert.name = Path.GetFileName(s);
combo2data.Add(fileInsert);
}
comboBox2.DataSource = combo2data;
comboBox2.ValueMember = "path";
comboBox2.DisplayMember = "name";
comboBox2.Items.Remove(comboBox2.SelectedValue); will only remove from the combobox, not from the datasource bound to the combobox. You may remove it from the datasource and re-bind the datasource.
Use ComboBox.SelectedIndex property.
For example: let me have comboBox1 added to the form. In the delete button:
if (comboBox1.SelectedIndex >= 0)
comboBox1.Items.RemoveAt(comboBox1.SelectedIndex);
combox1.Remove(takes an object)
Object selectedItem = comboBox1.SelectedItem;
So you cna do it this way combox1.Remove(selectedItem);
Suppose you want to Remove Items by Index:
combo2data.RemoveAt(0); //Removing by Index from the dataSource which is a List
//Rebind
comboBox2.DataSource = null;
comboBox2.DataSource = combo2data;
comboBox2.ValueMember = "path";
comboBox2.DisplayMember = "name";
Suppose you want to Remove by seraching for a member value
Foo item = combo2data.Where(f => f.name.Equals("Tom")).FirstOrDefault();
if (item != null)
{
combo2data.Remove(item);
comboBox2.DataSource = null;
comboBox2.DataSource = combo2data;
comboBox2.ValueMember = "path";
comboBox2.DisplayMember = "name";
}
These 2 commands will remove an item from your data source.
list.Remove((Foo)comboBox1.SelectedItem);
or
list.Remove(list.Find(P=>P.name == comboBox1.SelectedText));
I think the secret is to first attribute null to the datasource and after rebind to a modified collection:
int idToRemove = 1;
var items = (cbx.DataSource as List<MyEntity>);
items.RemoveAll(v => v.Id == idToRemove);
rebindCombobox(cbx, items, "Name", "Id");
private void rebindCombobox(ComboBox cbx, IEnumerable<Object> items, String displayMember, String valueMember)
{
cbx.DataSource = null;
cbx.DisplayMember = displayMember;
cbx.ValueMember = valueMember;
cbx.DataSource = items;
}
Perhaps delete all items in the Combobox with
comboBox.Items.Clear();

How to enable DataGridView sorting when user clicks on the column header?

I have a datagridview on my form and I populate it with this:
dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth })
.OrderBy(s => s.Apellidos)
.ToList();
Now, I use the s.Apellidos as the default sort, but I'd also like to allow users to sort when clicking on the column header.
This sort will not modify the data in any way, it's just a client side bonus to allow for easier searching for information when scanning the screen with their eyes.
Thanks for the suggestions.
Set all the column's (which can be sortable by users) SortMode property to Automatic
dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth })
.OrderBy(s => s.Apellidos)
.ToList();
foreach(DataGridViewColumn column in dataGridView1.Columns)
{
column.SortMode = DataGridViewColumnSortMode.Automatic;
}
Edit: As your datagridview is bound with a linq query, it will not be sorted. So please go through this [404 dead link, see next section] which explains how to create a sortable binding list and to then feed it as datasource to datagridview.
Code as recovered from dead link
Link from above is 404-dead. I recovered the code from the Internet Wayback Machine archive of the page.
public Form1()
{
InitializeComponent();
SortableBindingList<person> persons = new SortableBindingList<person>();
persons.Add(new Person(1, "timvw", new DateTime(1980, 04, 30)));
persons.Add(new Person(2, "John Doe", DateTime.Now));
this.dataGridView1.AutoGenerateColumns = false;
this.ColumnId.DataPropertyName = "Id";
this.ColumnName.DataPropertyName = "Name";
this.ColumnBirthday.DataPropertyName = "Birthday";
this.dataGridView1.DataSource = persons;
}
As Niraj suggested, use a SortableBindingList. I've used this very successfully with the DataGridView.
Here's a link to the updated code I used - Presenting the SortableBindingList - Take Two - archive
Just add the two source files to your project, and you'll be in business.
Source is in SortableBindingList.zip - 404 dead link
One more way to do this is using "System.Linq.Dynamic" library. You can get this library from Nuget. No need of any custom implementations or sortable List :)
using System.Linq.Dynamic;
private bool sortAscending = false;
private void dataGridView_ColumnHeaderMouseClick ( object sender, DataGridViewCellMouseEventArgs e )
{
if ( sortAscending )
dataGridView.DataSource = list.OrderBy ( dataGridView.Columns [ e.ColumnIndex ].DataPropertyName ).ToList ( );
else
dataGridView.DataSource = list.OrderBy ( dataGridView.Columns [ e.ColumnIndex ].DataPropertyName ).Reverse ( ).ToList ( );
sortAscending = !sortAscending;
}
You don't need to create a binding datasource. If you want to apply sorting for all of your columns, here is a more generic solution of mine;
private int _previousIndex;
private bool _sortDirection;
private void gridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.ColumnIndex == _previousIndex)
_sortDirection ^= true; // toggle direction
gridView.DataSource = SortData(
(List<MainGridViewModel>)gridReview.DataSource, gridReview.Columns[e.ColumnIndex].Name, _sortDirection);
_previousIndex = e.ColumnIndex;
}
public List<MainGridViewModel> SortData(List<MainGridViewModel> list, string column, bool ascending)
{
return ascending ?
list.OrderBy(_ => _.GetType().GetProperty(column).GetValue(_)).ToList() :
list.OrderByDescending(_ => _.GetType().GetProperty(column).GetValue(_)).ToList();
}
Make sure you subscribe your data grid to the event ColumnHeaderMouseClick. When the user clicks on the column it will sort by descending. If the same column header is clicked again, sorting will be applied by ascending.
your data grid needs to be bound to a sortable list in the first place.
Create this event handler:
void MakeColumnsSortable_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
//Add this as an event on DataBindingComplete
DataGridView dataGridView = sender as DataGridView;
if (dataGridView == null)
{
var ex = new InvalidOperationException("This event is for a DataGridView type senders only.");
ex.Data.Add("Sender type", sender.GetType().Name);
throw ex;
}
foreach (DataGridViewColumn column in dataGridView.Columns)
column.SortMode = DataGridViewColumnSortMode.Automatic;
}
And initialize the event of each of your datragrids like this:
dataGridView1.DataBindingComplete += MakeColumnsSortable_DataBindingComplete;
You can use DataGridViewColoumnHeaderMouseClick event like this :
Private string order = String.Empty;
private void dgvDepartment_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (order == "d")
{
order = "a";
dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth }) .OrderBy(s => s.Apellidos).ToList();
}
else
{
order = "d";
dataGridView1.DataSource = students.Select(s => new { ID = s.StudentId, RUDE = s.RUDE, Nombre = s.Name, Apellidos = s.LastNameFather + " " + s.LastNameMother, Nacido = s.DateOfBirth }.OrderByDescending(s => s.Apellidos) .ToList()
}
}
there is quite simply solution when using Entity Framework (version 6 in this case). I'm not sure but it seems to ObservableCollectionExtensions.ToBindingList<T> method returns implementation of sortable binding list. I haven't found source code to confirm this supposition but object returning from this method works with DataGridView very well especially when sorting columns by clicking on its headers.
The code is very simply and relies only on .net and entity framework classes:
using System.Data.Entity;
IEnumerable<Item> items = MethodCreatingItems();
var observableItems = new System.Collections.ObjectModel.ObservableCollection<Item>(items);
System.ComponentModel.BindingList<Item> source = observableItems.ToBindingList();
MyDataGridView.DataSource = source;
put this line in your windows form (on load or better in a public method like "binddata" ):
//
// bind the data and make the grid sortable
//
this.datagridview1.MakeSortable( myenumerablecollection );
Put this code in a file called DataGridViewExtensions.cs (or similar)
// MakeSortable extension.
// this will make any enumerable collection sortable on a datagrid view.
//
// BEGIN MAKESORTABLE - Mark A. Lloyd
//
// Enables sort on all cols of a DatagridView
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
public static class DataGridViewExtensions
{
public static void MakeSortable<T>(
this DataGridView dataGridView,
IEnumerable<T> dataSource,
SortOrder defaultSort = SortOrder.Ascending,
SortOrder initialSort = SortOrder.None)
{
var sortProviderDictionary = new Dictionary<int, Func<SortOrder, IEnumerable<T>>>();
var previousSortOrderDictionary = new Dictionary<int, SortOrder>();
var itemType = typeof(T);
dataGridView.DataSource = dataSource;
foreach (DataGridViewColumn c in dataGridView.Columns)
{
object Provider(T info) => itemType.GetProperty(c.Name)?.GetValue(info);
sortProviderDictionary[c.Index] = so => so != defaultSort ?
dataSource.OrderByDescending<T, object>(Provider) :
dataSource.OrderBy<T,object>(Provider);
previousSortOrderDictionary[c.Index] = initialSort;
}
async Task DoSort(int index)
{
switch (previousSortOrderDictionary[index])
{
case SortOrder.Ascending:
previousSortOrderDictionary[index] = SortOrder.Descending;
break;
case SortOrder.None:
case SortOrder.Descending:
previousSortOrderDictionary[index] = SortOrder.Ascending;
break;
default:
throw new ArgumentOutOfRangeException();
}
IEnumerable<T> sorted = null;
dataGridView.Cursor = Cursors.WaitCursor;
dataGridView.Enabled = false;
await Task.Run(() => sorted = sortProviderDictionary[index](previousSortOrderDictionary[index]).ToList());
dataGridView.DataSource = sorted;
dataGridView.Enabled = true;
dataGridView.Cursor = Cursors.Default;
}
dataGridView.ColumnHeaderMouseClick+= (object sender, DataGridViewCellMouseEventArgs e) => DoSort(index: e.ColumnIndex);
}
}
KISS : Keep it simple, stupid
Way A:
Implement an own SortableBindingList class when like to use DataBinding and sorting.
Way B:
Use a List<string> sorting works also but does not work with DataBinding.
I suggest using a DataTable.DefaultView as a DataSource. Then the line below.
foreach (DataGridViewColumn column in gridview.Columns)
{
column.SortMode = DataGridViewColumnSortMode.Automatic;
}
After that the gridview itself will manage sorting(Ascending or Descending is supported.)
If you get an error message like
An unhandled exception of type 'System.NullReferenceException'
occurred in System.Windows.Forms.dll
if you work with SortableBindingList, your code probably uses some loops over DataGridView rows and also try to access the empty last row! (BindingSource = null)
If you don't need to allow the user to add new rows directly in the DataGridView this line of code easily solve the issue:
InitializeComponent();
m_dataGridView.AllowUserToAddRows = false; // after components initialized
...
Create a class which contains all properties you need, and populate them in the constructor
class Student
{
int _StudentId;
public int StudentId {get;}
string _Name;
public string Name {get;}
...
public Student(int studentId, string name ...)
{ _StudentId = studentId; _Name = name; ... }
}
Create an IComparer < Student > class, to be able to sort
class StudentSorter : IComparer<Student>
{
public enum SField {StudentId, Name ... }
SField _sField; SortOrder _sortOrder;
public StudentSorder(SField field, SortOrder order)
{ _sField = field; _sortOrder = order;}
public int Compare(Student x, Student y)
{
if (_SortOrder == SortOrder.Descending)
{
Student tmp = x;
x = y;
y = tmp;
}
if (x == null || y == null)
return 0;
int result = 0;
switch (_sField)
{
case SField.StudentId:
result = x.StudentId.CompareTo(y.StudentId);
break;
case SField.Name:
result = x.Name.CompareTo(y.Name);
break;
...
}
return result;
}
}
Within the form containing the datagrid add
ListDictionary sortOrderLD = new ListDictionary(); //if less than 10 columns
private SortOrder SetOrderDirection(string column)
{
if (sortOrderLD.Contains(column))
{
sortOrderLD[column] = (SortOrder)sortOrderLD[column] == SortOrder.Ascending ? SortOrder.Descending : SortOrder.Ascending;
}
else
{
sortOrderLD.Add(column, SortOrder.Ascending);
}
return (SortOrder)sortOrderLD[column];
}
Within datagridview_ColumnHeaderMouseClick event handler do something like this
private void dgv_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
StudentSorter sorter = null;
string column = dGV.Columns[e.ColumnIndex].DataPropertyName; //Use column name if you set it
if (column == "StudentId")
{
sorter = new StudentSorter(StudentSorter.SField.StudentId, SetOrderDirection(column));
}
else if (column == "Name")
{
sorter = new StudentSorter(StudentSorter.SField.Name, SetOrderDirection(column));
}
...
List<Student> lstFD = datagridview.DataSource as List<Student>;
lstFD.Sort(sorter);
datagridview.DataSource = lstFD;
datagridview.Refresh();
}
Hope this helps
Just in case somebody still looks for it, I did it on VS 2008 C#.
On the Event ColumnHeaderMouseClick, add a databinding for the gridview, and send the order by field like a parameter. You can get the clicked field as follows:
dgView.Columns[e.ColumnIndex].Name
In my case the header's names are similar to view field names.
I have a BindingList<> object bind as a data source to dataGridView.
BindingList x1;
x1 = new BindingList<sourceObject>();
BindingSource bsx1 = new BindingSource();
bsx1.DataSource = x1;
dataGridView1.DataSource = bsx1;
When I clicked the column header, no sorting takes place.
I used the SortableBindingList answer provided by Tom Bushell.
Having included two source files into my project
SortableBindingList.cs
PropertyComparer.cs
Then this change is made to my code:
Be.Timvw.Framework.ComponentModel.SortableBindingList x1; // 1
x1 = new Be.Timvw.Framework.ComponentModel.SortableBindingList<sourceObject>(); // 2
BindingSource bsx1 = new BindingSource();
bsx1.DataSource = x1;
dataGridView1.DataSource = bsx1;
After these changes I performed a build on my program. I am now able to sort by clicking the column headers. Only two lines need changing, they are highlighted in the code snippet above by trailing comments.
In my case, the problem was that I had set my DataSource as an object, which is why it didn't get sorted. After changing from object to a DataTable it workd well without any code complement.
If using a DataTable: dgv.DataSource = (DataTable)table
You can automatically enable Sorting for objects that contain the IComparable Interface. After creating the DataTable, when adding the columns be sure to set the type also to at least object:
table.Columns.Add("ColumnName", typeof(object))
Otherwise, if you do Not specifically give it a type, it converts the object to a string.
I spent a fair amount of time creating a dgv_ColumnHeaderMouseClick() event because it was Not sorting the DataGridView correctly, then to find that all you need to do is specify the type for the column name, and it sorts properly. And the reason it was not sorting correctly previously was because without specifying the type for DataTable columns, it will convert objects to strings.
Just instead of passing a list to the datagrid, you store the search result as a datatable.
dataGridView1.DataSource = students
.Select(s => new {
ID = s.StudentId,
RUDE = s.RUDE,
Nombre = s.Name,
Apellidos = s.LastNameFather + " " + s.LastNameMother,
Nacido = s.DateOfBirth })
.OrderBy(s => s.Apellidos)
.ToDataTable();

Categories

Resources