I have the following class which fills a list as shown bellow from an event when a button bound to each column is clicked on a DataGridView called MenuGrid:
public class orderedfood
{
public string price;
public string item;
}
List<orderedfood> order = new List<orderedfood>();
private void MenuGrid_CellClick(object sender, DataGridViewCellEventArgs e)
{
order.Add(new orderedfood { item = MenuGrid.Rows[e.RowIndex].Cells[1].Value.ToString(), price = subtotal.ToString() });
}
This MenuGrid has the following format:
What I want to do is to reload the DataGridView bound to the order List, hence I tried the following code:
MenuGrid.DataSource = null;
MenuGrid.Rows.Clear();
for (int i = 0; i < order.Count; i++)
{
MenuGrid.Rows.Add(order[i].item, order[i].price);
}
MenuGrid.Refresh();
This gives the following output, which is not what I want:
The final screenshot is correct on the number of rows but it doesn't include the name and the price of the the item.
Any suggestions?
Dont set DataSource to null. And also you can try this inside your for loop,
DataGridViewRow row = new DataGridViewRow();
dataGridView1.Rows.Add(row);
dataGridView1.Rows.Insert(0, order[i].item, order[i].price);
I have an assignment about ListView sort by Column using C# Windows Form and the codes that I got from MSDN didn't work. Can anybody find out what's wrong with the codes? Everytime I click the ListView Column nothing happens.
Here's the code, I also added the items that will show in my ListView
private int sortColumn = -1;
private void listView1_ColumnClick(object sender, ColumnClickEventArgs e)
{
// Determine whether the column is the same as the last column clicked.
if (e.Column != sortColumn)
{
// Set the sort column to the new column.
sortColumn = e.Column;
// Set the sort order to ascending by default.
listView1.Sorting = SortOrder.Ascending;
}
else
{
// Determine what the last sort order was and change it.
if (listView1.Sorting == SortOrder.Ascending)
listView1.Sorting = SortOrder.Descending;
else
listView1.Sorting = SortOrder.Ascending;
}
// Call the sort method to manually sort.
listView1.Sort();
// Set the ListViewItemSorter property to a new ListViewItemComparer
// object.
this.listView1.ListViewItemSorter = new ListViewItemComparer(e.Column,
listView1.Sorting);
}
private void FillItems()
{
// Add items
ListViewItem item1 = new ListViewItem("Nipun Tomar");
item1.SubItems.Add("1");
item1.SubItems.Add("10/11/2000");
ListViewItem item2 = new ListViewItem("First Last");
item2.SubItems.Add("2");
item2.SubItems.Add("12/12/2010");
ListViewItem item3 = new ListViewItem("User User");
item3.SubItems.Add("3");
item3.SubItems.Add("12/01/1800");
ListViewItem item4 = new ListViewItem("Sample");
item4.SubItems.Add("4");
item4.SubItems.Add("05/30/1900");
// Add the items to the ListView.
listView1.Items.AddRange(
new ListViewItem[] {item1, item2, item3, item4});
}
private void Form1_Load(object sender, EventArgs e)
{
FillItems();
}
public class ListViewItemComparer : IComparer
{
private int col;
private SortOrder order;
public ListViewItemComparer()
{
col = 0;
order = SortOrder.Ascending;
}
public ListViewItemComparer(int column, SortOrder order)
{
col = column;
this.order = order;
}
public int Compare(object x, object y)
{
int returnVal= -1;
returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
((ListViewItem)y).SubItems[col].Text);
// Determine whether the sort order is descending.
if (order == SortOrder.Descending)
// Invert the value returned by String.Compare.
returnVal *= -1;
return returnVal;
}
}
Note: I added the columns in the design form.
Here's what my assignment looks like:
You dont have any columns in your list view. They are just items. thats why the event listView1_ColumnClick never fires. (also make sure you have added this event to your list view.)
Add this at first of your Form1_Load event to initialize columns.
// set view mode to see columns
listView1.View = View.Details;
// 100 is just a length of column. HorizontalAlignment.Left starts from left side
listView1.Columns.Add("Name", 100, HorizontalAlignment.Left);
listView1.Columns.Add("Number", 100, HorizontalAlignment.Left);
listView1.Columns.Add("Date", 100, HorizontalAlignment.Left);
Now you see the columns which you can select them to sort items by that column.
Note that i just added 3 columns. so list view will show each item with 2 of their SubItems under columns by order.
As you requested to post the gif. Here is it :)
You call listView1.Sort()before setting the comparer: this.listView1.ListViewItemSorter = ...
Just invert the two lines.
Also, note that you are using string.Compare for all columns, which, I think, it's not what you want for column 3 (date)
[Edit]:
Just realized now the setting the value for ListviewItemSorter cause the LV to sort: your code seems to work even without calling listView1.Sort()
Problem must be somewhere else. Try with debugger setting breakpoints...
private void lvw_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
{
ListViewColumnSorter sorter = new ListViewColumnSorter();
sorter.SortColumn = e.Column;
sorter.Order = System.Windows.Forms.SortOrder.Ascending;
lvw.ListViewItemSorter =sorter;
lvw.Sort();
}
how can i have a ultragrid with 3-way sorting on every columns? I mean :
a. Ascendiing -Indicated by default ascending SortIndicator.
b. Descending- Indicated by default descending SortIndicator.
c. No Sort- UnSort the column.
Note: I have tried BeforeSortChanged Event but i had 2 problems:
I could not get the previous column sort indicator to find out when
should i disable sorting.
I have got an Exception where it is saying that we can't change
SortIndicator in BeforeSortChange Event
It's not that trivial to implement the "unsorted" state. Once the data has been sorted, it is simply reordered within the band. You can see for yourself by applying the below code:
Infragistics.Win.UltraWinGrid.UltraGridColumn[] oldSort;
private void Sort() {
ultraGrid1.BeforeSortChange += new Infragistics.Win.UltraWinGrid.BeforeSortChangeEventHandler(ultraGrid1_BeforeSortChange);
ultraGrid1.AfterSortChange += new Infragistics.Win.UltraWinGrid.BandEventHandler(ultraGrid1_AfterSortChange);
}
void ultraGrid1_BeforeSortChange(object sender, Infragistics.Win.UltraWinGrid.BeforeSortChangeEventArgs e) {
oldSort = new Infragistics.Win.UltraWinGrid.UltraGridColumn[e.Band.SortedColumns.Count];
e.Band.SortedColumns.CopyTo(oldSort, 0);
}
void ultraGrid1_AfterSortChange(object sender, Infragistics.Win.UltraWinGrid.BandEventArgs e) {
for (int i = 0; i < oldSort.Length; i++) {
for (int j = 0; j < e.Band.SortedColumns.Count; j++) {
Infragistics.Win.UltraWinGrid.UltraGridColumn column = e.Band.SortedColumns[j];
if (column.Key == oldSort[i].Key) {
if (column.SortIndicator == Infragistics.Win.UltraWinGrid.SortIndicator.Ascending) {
//column.SortIndicator = Infragistics.Win.UltraWinGrid.SortIndicator.None;
e.Band.SortedColumns.Remove(column.Key);
j--;
break;
}
}
}
}
}
My guess is that you would need to keep a separate array with the row indices in it, and reorder rows within a band once the sorting is removed, according to the array. But that could be memory consuming in my opinion.
Another approach would be to have an additional hidden column, which could be filled with integers incremented in a simple for loop. Once the column's sort is "removed", you simply apply the Ascending sort to that hidden column. There are other aspects that needs to be maintained with such method though.
I solved my problem by using UIElement.GetContext() to find clicked column in Mouse Up event handler of my grid and then i checked and changed the sortIndicator property of that column to what i want.
Edit :
private void Grid_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
UIElement element = Grid.DisplayLayout.UIElement.ElementFromPoint(new Point(e.X, e.Y));
if (element == null)
return;
ColumnHeader clickedHeader = (ColumnHeader)element.GetContext(typeof(ColumnHeader), true);
UltraGridColumn clickedColumn;
if (clickedHeader != null)
clickedColumn = clickedHeader.Column;
if (clickedColumn == null)
return;
Switch ( clickedColumn.SortIndicator)
{
case SortIndicator.Ascending :
clickedColumn.SortIndicator= SortIndicator.Descending;
break;
case SortIndicator.Descending :
clickedColumn.SortIndicator= SortIndicator.Disabled;
break;
case SortIndicator.Disabled :
default :
clickedColumn.SortIndicator= SortIndicator.Ascending;
break;
}
}
}
I would like to sort my grid at form load but Visual Studio is not liking my code. Any help would be appreciated!
dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Descending);
You could sort the data initially before binding it. For example, if you have a DataTable, you could do:
dataTable.DefaultView.Sort("Cola DESC");
And then you bind your data already sorted.
private void Form_Load(object sender, EventArgs e)
{
//bind u r collection to datagridview
DataGridView1.Sort( new RowComparer( SortOrder.Ascending ) );
}
private class RowComparer : System.Collections.IComparer
{
private static int sortOrderModifier = 1;
public RowComparer(SortOrder sortOrder)
{
if (sortOrder == SortOrder.Descending)
{
sortOrderModifier = -1;
}
else if (sortOrder == SortOrder.Ascending)
{
sortOrderModifier = 1;
}
}
public int Compare(object x, object y)
{
DataGridViewRow DataGridViewRow1 = (DataGridViewRow)x;
DataGridViewRow DataGridViewRow2 = (DataGridViewRow)y;
// Try to sort based on the Last Name column.
int CompareResult = System.String.Compare(
DataGridViewRow1.Cells[1].Value.ToString(),
DataGridViewRow2.Cells[1].Value.ToString());
// If the Last Names are equal, sort based on the First Name.
if ( CompareResult == 0 )
{
CompareResult = System.String.Compare(
DataGridViewRow1.Cells[0].Value.ToString(),
DataGridViewRow2.Cells[0].Value.ToString());
}
return CompareResult * sortOrderModifier;
}
}
I got it to work by adding this line to the BindingSource
this.carCheckoutBindingSource.Sort = "startMiles DESC";
Is the datagridview bound to a collection? If Yes then perform the sort on the collection and rebind the sorted collection to the datagridview
Ex:
collection.OrderBy(i=> i.<PropertyName>);
datagridview.DataSource = collection;
NOTE:
My collection is of type List.
How do I sort a DataGridView by two columns (ascending)? I have two columns: day and status.
If I need to sort by one column, I do:
this.dataGridView1.Sort (this.dataGridView1.Columns["day"], ListSortDirection.Ascending);
But for two?
If your DataGridView is databound, you can sort your Datatable view and rebind to datatable as below:
private DataGridView dataGridView1 = new DataGridView();
private BindingSource bindingSource1 = new BindingSource();
private void Form1_Load(object sender, System.EventArgs e)
{
// Bind the DataGridView to the BindingSource
dataGridView1.DataSource = bindingSource1;
SortDataByMultiColumns(); //Sort the Data
}
private void SortDataByMultiColumns()
{
DataView view = dataTable1.DefaultView;
view.Sort = "day ASC, status DESC";
bindingSource1.DataSource = view; //rebind the data source
}
OR, without using bindingsource and binding directly to DataView:
private void SortDataByMultiColumns()
{
DataView view = ds.Tables[0].DefaultView;
view.Sort = "day ASC, status DESC";
dataGridView1.DataSource = view; //rebind the data source
}
Add a hidden column that combines the two and sort by that.
You can use the DataGridView's Sort method, but specify an argument that is an instance of a class that implements IComparer.
Here is an example of such a class:
public class MyTwoColumnComparer : System.Collections.IComparer
{
private string _SortColumnName1;
private int _SortOrderMultiplier1;
private string _SortColumnName2;
private int _SortOrderMultiplier2;
public MyTwoColumnComparer(string pSortColumnName1, SortOrder pSortOrder1, string pSortColumnName2, SortOrder pSortOrder2)
{
_SortColumnName1 = pSortColumnName1;
_SortOrderMultiplier1 = (pSortOrder1 == SortOrder.Ascending) ? 1 : -1;
_SortColumnName2 = pSortColumnName2;
_SortOrderMultiplier2 = (pSortOrder2 == SortOrder.Ascending) ? 1 : -1;
}
public int Compare(object x, object y)
{
DataGridViewRow r1 = (DataGridViewRow)x;
DataGridViewRow r2 = (DataGridViewRow)y;
int iCompareResult = _SortOrderMultiplier1 * String.Compare(r1.Cells[_SortColumnName1].Value.ToString(), r2.Cells[_SortColumnName1].Value.ToString());
if (iCompareResult == 0) iCompareResult = _SortOrderMultiplier2 * String.Compare(r1.Cells[_SortColumnName2].Value.ToString(), r2.Cells[_SortColumnName2].Value.ToString());
return iCompareResult;
}
}
Now, we might call this from a column whose SortMode is 'Programmatic' on a mouse click:
private void dgvAllMyEmployees_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
DataGridViewColumn dgvcClicked = dgvAllEmployees.Columns[e.ColumnIndex];
if (dgvcClicked.SortMode == DataGridViewColumnSortMode.Programmatic)
{
_SortOrder = (_SortOrder == SortOrder.Ascending) ? SortOrder.Descending : SortOrder.Ascending;
MyTwoColumnComparer Sort2C = new MyTwoColumnComparer(dgvcClicked.Name, _SortOrder, "LastName", SortOrder.Ascending);
dgvAllEmployees.Sort(Sort2C);
}
}
The class level variable _SortOrder helps keep track of which order to go in. One can enhance this more to remember the last two columns clicked and sort on them in the desired order.
TLDR; I have a two-line solution.
I had to do the same thing, but after researching all these complicated ways to do this by either including a separate .dll or writing my own class/methods, I knew there had to be an easier way. It turns out I was right because I figured out how to accomplish this with using only two lines of code. This worked for me.
Luckily, it turns out for us that the .NET Framework Sort() method does help us with this. The idea is that you want to sort the columns individually, but the order in which you sort them in is what will produce the desired output.
So, as an example, I have a column for file type and a column for a file name. Whenever I want to sort the data by the types, I want to make sure that the names are also sorted within each type shown.
GOAL: Sorting by type will also sort the file names alphabetically.
Data:
zxcv.css
testimg3.jpg
asdf.html
testimg2.jpg
testimg1.jpg
Sorting data by name:
mConflictsDataGridView.Sort(mConflictsDataGridView.Columns[mNameLabel.Index], ListSortDirection.Ascending);
asdf.html
testimg1.jpg
testimg2.jpg
testimg3.jpg
zxcv.css
As you can see, this will name sure that the names will be sorted accordingly, such that when I now sort by the file types, both requirements will satisfy.
Sorting data by file type:
mConflictsDataGridView.Sort(mConflictsDataGridView.Columns[mFileExtensionLabel.Index], ListSortDirection.Ascending);
zxcv.css
asdf.html
testimg1.jpg
testimg2.jpg
testimg3.jpg
Voila! It's sorted!
SOLUTION: In your case, you may want to try something like the following, and you may need to tweak it some more to have it cater to your own code.
DataGridView1.Sort(DataGridView1.Columns["status"], ListSortDirection.Ascending);
DataGridView1.Sort(DataGridView1.Columns["day"], ListSortDirection.Asscending);
This should be able to display your results by the day with its status field sorted as well.
The answer that John Kurtz provided got me close. But what I found was the when I clicked on a column once, it did indeed sort by the two columns ... In his example: dgvcClicked.Name, "LastName". So, good!!
But, if I clicked on the column again, then it would NOT sort by the opposite direction. So the column became stuck in Ascending.
To overcome this, I had to track the sort order manually. Started with this class:
public class ColumnSorting
{
public int ColumnIndex { get; set; }
public ListSortDirection Direction { get; set; }
}
Then, I added this globally scoped List:
List<ColumnSorting> _columnSortingList = new List<ColumnSorting>();
Then, in the method that does the Sort, I would
Check to see if the column index being sorted already exists in _columnSortingList. If not, add it.
If it already exists, then swap the sort order
And Bob's your uncle.
You can try this, or use custom sorting:
private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (dataGridView1.Columns[e.ColumnIndex].HeaderText =="day")
{
myBindingSource.Sort = "day, hour";
}
}
I use this solution when working with bound data. This works for our users, and displays the current sort criteria. All sorts are only in ascending order.
Add a CheckBox, a TextBox, a ColumnHeaderMouseClick event, and code as shown. The CheckBox will toggle the TextBox's visibility, and clicking on any column header will add the sort criteria to the TextBox. To clear the TextBox, just double click in it.
private void FooDataGridView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if(chkMultiSort.Checked == true)
{
string columnHeader = FooDataGridView.Columns[e.ColumnIndex].DataPropertyName;
txtMultiSort.Text += (columnHeader + ", ");
try
{
FooBindingSource.Sort = txtMultiSort.Text.Remove(txtMultiSort.Text.Length - 2);
}
catch
{
MessageBox.Show("Invalid Sort Data", "Information", MessageBoxButtons.OK, MessageBoxIcon.None);
txtMultiSort.Text = String.Empty;
}
}
}
private void ChkMultiSort_CheckedChanged(object sender, EventArgs e)
{
if(chkMultiSort.Checked == true)
{
txtMultiSort.Visible = true;
}
else
{
txtMultiSort.Visible = false;
txtMultiSort.Text = String.Empty;
}
}
private void TxtMultiSort_DoubleClick(object sender, EventArgs e)
{
txtMultiSort.Text = String.Empty;
}
Here is a refinement on the IComparer class from #John Kurtz, which supports mutliple columns and sorting by numbers or dates.
public static void SortOnMultipleColumns(DataGridView dgv, Dictionary<string /*Column Name*/, ColumnSortInfo> sortingColumns)
{
// Show the glyphs
foreach (DataGridViewColumn col in dgv.Columns)
{
System.Windows.Forms.SortOrder sortOrder = System.Windows.Forms.SortOrder.None;
foreach (var kvp in sortingColumns)
{
if (kvp.Key == col.Name)
{
sortOrder = kvp.Value.SortOrder;
break;
}
}
col.HeaderCell.SortGlyphDirection = sortOrder;
}
// Sort the grid
MultiColumnCompararor multiColumnCompararor = new MultiColumnCompararor(sortingColumns);
dgv.Sort(multiColumnCompararor);
}
public class ColumnSortInfo
{
public enum ValueConversion { ToString, ToNumber, ToDate}
public ColumnSortInfo(System.Windows.Forms.SortOrder sortOrder, ValueConversion valueConversion = ValueConversion.ToString)
{
SortOrder = sortOrder;
MyValueConversion = valueConversion;
SortOrderMultiplier = (SortOrder == SortOrder.Ascending) ? 1 : -1;
}
public System.Windows.Forms.SortOrder SortOrder { get; set; }
public int SortOrderMultiplier { get; }
public ValueConversion MyValueConversion { get; set; }
public static double StringToDouble(string sVal)
{
if (Double.TryParse(sVal, out double dVal))
{
return dVal;
}
return 0;
}
public static DateTime StringToDateTime(string sVal)
{
if (DateTime.TryParse(sVal, out DateTime dt))
{
return dt;
}
return DateTime.MinValue;
}
}
private class MultiColumnCompararor : System.Collections.IComparer
{
IDictionary<string /*Column Name*/, ColumnSortInfo> _sortingColumns;
public MultiColumnCompararor(IDictionary<string /*Column Name*/, ColumnSortInfo> sortingColumns)
{
_sortingColumns = sortingColumns;
}
public int Compare(object x, object y)
{
try
{
DataGridViewRow r1 = (DataGridViewRow)x;
DataGridViewRow r2 = (DataGridViewRow)y;
foreach (var kvp in _sortingColumns)
{
string colName = kvp.Key;
ColumnSortInfo csi = kvp.Value;
string sVal1 = r1.Cells[colName].Value?.ToString().Trim()??"";
string sVal2 = r2.Cells[colName].Value?.ToString().Trim()??"";
int iCompareResult = 0;
switch (csi.MyValueConversion)
{
case ColumnSortInfo.ValueConversion.ToString:
iCompareResult = String.Compare(sVal1, sVal2);
break;
case ColumnSortInfo.ValueConversion.ToNumber:
double d1 = ColumnSortInfo.StringToDouble(sVal1);
double d2 = ColumnSortInfo.StringToDouble(sVal2);
iCompareResult = ((d1 == d2) ? 0 : ((d1 > d2) ? 1 : -1));
break;
case ColumnSortInfo.ValueConversion.ToDate:
DateTime dt1 = ColumnSortInfo.StringToDateTime(sVal1);
DateTime dt2 = ColumnSortInfo.StringToDateTime(sVal2);
iCompareResult = ((dt1 == dt2) ? 0 : ((dt1 > dt2) ? 1 : -1));
break;
default:
break;
}
iCompareResult = csi.SortOrderMultiplier * iCompareResult;
if (iCompareResult != 0) { return iCompareResult; }
}
return 0;
}
catch (Exception ex)
{
return 0;
}
}
}
Usage:
Dictionary<String, ColumnSortInfo> sortingColumns = new Dictionary<String, ColumnSortInfo>
{ {"policyNumber", new ColumnSortInfo(System.Windows.Forms.SortOrder.Ascending)},
{"MessageId", new ColumnSortInfo(System.Windows.Forms.SortOrder.Descending, ColumnSortInfo.ValueConversion.ToNumber)},
{"CreationDate", new ColumnSortInfo(System.Windows.Forms.SortOrder.Ascending, ColumnSortInfo.ValueConversion.ToDate)}};
CsUtils.SortOnMultipleColumns(dgv, sortingColumns);