DatagridView Column Sorting - c#

I have bind datagrid with following values.
Id as Int,Price as int, IsActive as bit.
Now I want to sort data based on IsActive when i click on column header of IsActive.
I did the same thing for Id and it working properly but for IsActive it is not working.
Below is my code for IsActive field :
private void dataGridView1_SortCompare(object sender, DataGridViewSortCompareEventArgs e)
{
// Try to sort based on the cells in the current column.
e.SortResult = System.String.Compare(e.CellValue1.ToString(), e.CellValue2.ToString());
// If the cells are equal, sort based on the ID column.
if (e.SortResult == 0 && e.Column.Name != "IsActive ")
{
e.SortResult = System.String.Compare(
dataGridView1.Rows[e.RowIndex1].Cells["IsActive "].Value.ToString(),
dataGridView1.Rows[e.RowIndex2].Cells["IsActive "].Value.ToString());
}
e.Handled = true;
}
I want to know How can i sort Boolean data in datagridView.

According to MSDN -DataGridView.SortCompare Event, The SortCompare will be triggered only for columns that have their "SortMode" property set to "Automatic":
DataGridViewColumn col = this.dataGridView1.Columns["IsActive"];
col.SortMode = DataGridViewColumnSortMode.Automatic;
If I may, I would suggest letting .Net doing the dirty job.
Lets assume you have 3 rows in your grid view:
Id Price IsActive
1 1 1
2 2 1
3 11 1
In the way you have implemented the sorting, if you sort by price, row 3 will precede row 2 (the string "11" comes before "2"...).
Preferably you would have your data in a data table, bind the datatable to the gridview and let .Net do the rest:
/// <summary>
/// Binds the Grid view.
/// </summary>
private void BindGridView()
{
//Creating the columns.
//The gridview will know how to sort Items by the type specified in the second argment
DataTable dt = new DataTable();
dt.Columns.Add("Id",typeof(int));
dt.Columns.Add("Price",typeof(int));
dt.Columns.Add("IsActive",typeof(bool));
//Creating some random data
//Replace this with your actual data...
Random rnd = new Random(1);
for (int i = 0; i < 100; i++)
{
int Id = i+1;
int Price = Id%2 == 0? 500-Id*2:350+Id*3;
bool isActive = (Id%5) !=0;
DataRow row = dt.NewRow();
row["Id"] =Id ;
row["Price"] = rnd.Next(1000) ;
row["IsActive"] = isActive;
dt.Rows.Add(row);
}
this.dataGridView1.DataSource = dt;
//making sure all columns are sortable
foreach (DataGridViewColumn col in this.dataGridView1.Columns)
{
col.SortMode = DataGridViewColumnSortMode.Automatic;
}
}

Related

Use Last Value in Column for Summary Instead of SUM

I have following code to remove the summary row/band value on a retrieve UI event. I'm certain this is the wrong way to do it but it works.
public UiEventResult AfterRetrieveData_111(object sender, RetrieveDataEventArgs e)
{
UltraGridBase baseGrid = _view.ViewGrids["ultraGrid1"];
UltraGridLayout gridLayout = baseGrid.DisplayLayout;
for (int i = 0; i < 2; i++)
{
gridLayout.Bands[i].Columns["columnA"].Formula = "''";
}
for (int i = 0; i < 3; i++)
{
gridLayout.Bands[i].Columns["columnB"].Formula = "''";
gridLayout.Bands[i].Columns["columnC"].Formula = "''";
}
Is there a way to program the retrieve so that it populates the summary row for column A/band[2] so that is uses the last value in each column? Without the above code it will sum rows under but would like for a way for it to use the last row value instead. Data will always be sorted DESC by date so last row will always be the value needed...
One way to achieve this is in InitializeRowEvent by setting the value of the columnA to the value of the last row in the child band like this:
// Update the rows only in the first band. You can also use e.Row.Band.Key == {YOU_BAND_KEY}
if (e.Row.Band.Index == 0)
{
// set the value of the columnA to the value of the last row in the child band
e.Row.Cells["columnA"].Value = e.Row.ChildBands.LastRow.Cells["columnA"].Value;
}
Note, this will not work if you edit the cells values. If you need to update the parent row value after cell update, again in InitializeRowEvent you can add this:
// look for row in the secon band
if (e.Row.Band.Index == 1)
{
// get the last row in the second band
if (e.Row == e.Row.ParentRow.ChildBands.LastRow)
{
// get the value of the last row in the second band and set it to the parent row
e.Row.ParentRow.Cells["columnA"].Value = e.Row.Cells["columnA"].Value;
}
}
This will loop through ChildBands and set parent row value with the last value in each ChildBand.
int rowCount = gridLayout.Rows.Count;
for (int i = 0; i < rowCount; i++)
{
foreach (UltraGridChildBand childBand in baseGrid.Rows[i].ChildBands)
{
foreach (UltraGridRow row in childBand.Rows)
{
row.Cells["columnA"].Value =row.ChildBands.LastRow.Cells["columnA"].Value;
}
}
}

Merge values on multi-line columns on a Datagridview

I have Visual Studio 2019. The project is a .Net Windows Form on C# on .Net Framework 4.8.
I have a Datagridview which shows some tables data from different databases (MS SQL and Postgresql).
I merge that databases, and the result is too long, so we can't fit it on a screen; but we have to see all the data available on that screen, which are more than 40 columns. Reduce the font size is not plausible.
So, the solution proposed was to merge some values on the same column in this way (See this example):
The actual data view:
The way we need to view it:
If you have any ideas or you know an alternative to Datagridview which allows that, please share them.
Thanks in advance.
You can customize your datagridview's row and column to get the Multi-line columns on a datagridview.
I assume that the datatable is the table from database.
using System;
using System.Data;
using System.Linq;
using System.Windows.Forms;
private void Form1_Load(object sender, EventArgs e)
{
DataTable table = new DataTable();
table.Columns.Add("Name");
table.Columns.Add("Field1");
table.Columns.Add("Field2");
table.Columns.Add("Field3");
table.Columns.Add("Field4");
table.Columns.Add("Field5");
table.Columns.Add("Field6");
table.Columns.Add("Field7");
table.Rows.Add("test1", 1, 2, 3, 4, 5, 6, 7);
table.Rows.Add("test2", 1, 2, 3, 4, 5, 6, 7);
table.Rows.Add("test3", 1, 2, 3, 4, 5, 6, 7);
dataGridView1.ColumnHeadersVisible = false;
for (int i = 0; i < table.Columns.Count/2; i++)
{
dataGridView1.Columns.Add("","");
}
string[] columnNames = table.Columns.Cast<DataColumn>()
.Select(x => x.ColumnName)
.ToArray();
int count = table.Columns.Count/2;
var col1 = columnNames.Take(count).ToArray();
var col2= columnNames.Skip(count).Take(count).ToArray();
dataGridView1.Rows.Add(col1);
dataGridView1.Rows.Add(col2);
object[] arr;
for (int i = 0; i < table.Rows.Count; i++)
{
arr = table.Rows[i].ItemArray;
var row1=arr.Take(count).ToArray();
var row2 = arr.Skip(count).Take(count).ToArray();
dataGridView1.Rows.Add(row1);
dataGridView1.Rows.Add(row2);
}
}
Result:
After reviewing your question, I have to say that IMHO your solution is not giving much thought to the end user or if the code has to grab one of the values. Stacking fields into a single column “creates” two issues IMHO…One, as mentioned, is that the user is going to have to do extra work and check the order of the headers to distinguish which field value is which… a subtle yet (annoying) non intuitive extra step. Two, if the user is allowed to change fields or the code needs to grad a field, then, there is going to be extra work needed to differentiated which field goes with which value. Extra work for the user and extra work for the coder doesn’t sound like a good start.
Sorry about my rant. Fortunately, if you wanted to take a table as shown in the question, and turn it into a table as you describe, then the code below should do this. It basically creates “two” (2) field columns. Such that each column contains two fields. The code is hacky yet it is not too complicated I hope. I made numerous comments in the code. Some notes would be that, since we are adding two fields for each column and (as far as I know) a DataGridView won’t allow double column headers, the code does NOT use the column headers row and instead uses the first two rows of the grid for the two column headers. This will allow you to format the two rows to look like headers and/or color code if needed.
Lastly, a better solution IMHO. As previously mentioned a pivot will work, however, there are a couple of issues given how the data is stored in the original table. In a basic pivot where we switch rows and columns, the posted example would have three (3) columns… “Jim”, “Hugh” and “Terrance”. Then the number of rows would be (one (1) + however many fields/field columns). The extra “one” is the field “LastName.” Given this, it may look something like…
Jim Hugh Terrance
LastName Carey Jackman Hill
Field1 1 a N/A
Field2 2 b N/A
……..
It would appear obvious that the “LastName” should go with the column header. Therefore, the transpose/pivot may look like…
Jim Carey Hugh Jackman Terrance Hill
Field1 1 a N/A
Field2 2 b N/A
……..
IMHO, this will be more intuitive for the user to identify fields and there should not be any extra coding if we need to reference a specific value. The picture below shows a complete example from the code below. Drop three (3) DataGridViews onto a form and paste my code. The top-left grid is the original data. The bottom-left grid is the transpose as per your requirements and finally, the grid on the right is the what I feel would work best considering your dilemma.
A note on the last grid on the right… Initially, like the column header rows in your solution, I had the fields as a column in the grid. It will not be difficult to change the code if you want this. However, the code currently adds the field names as “row headers” in the grid. Since the DataTable does not really have row headers, this addition had to be made “after” the data source was set and can be seen in the forms Load event. Again, it will not be difficult to move the field to an added column in the DataTable.
To make this example complete, below is code to create some test data. The incoming parameter totalCols will make totalCols columns in the DataTable for the "Field" values.
private DataTable GetDataFromDB(int totalCols) {
DataTable dt = new DataTable();
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("LastName", typeof(string));
for (int i = 1; i <= totalCols; i++) {
dt.Columns.Add("Field" + i, typeof(string));
}
DataRow curRow;
string name;
string lName;
for (int i = 1; i < 4; i++) {
switch (i) {
case 1:
name = "Jim";
lName = "Carrey";
break;
case 2:
name = "Hugh";
lName = "Jackman";
break;
default:
name = "Terence";
lName = "Hill";
break;
}
curRow = dt.NewRow();
curRow["Name"] = name;
curRow["LastName"] = lName;
if (i < 3) {
for (int j = 2; j < dt.Columns.Count; j++) {
curRow[j] = "N" + i + "F" + (j - 1);
}
}
dt.Rows.Add(curRow);
}
return dt;
}
We will use three (3) global DataTables, one for each grid.
DataTable originalDT;
DataTable pivotDT1;
DataTable pivotDT2;
The load event that sets each grid to the proper DataTable and some specific formatting for each grid.
private void Form1_Load(object sender, EventArgs e) {
originalDT = GetDataFromDB(45);
dataGridView1.DataSource = originalDT;
// pivot 1 - bottom left grid
pivotDT1 = PivotTable(originalDT);
dataGridView2.DataSource = pivotDT1;
dataGridView2.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
dataGridView2.Rows[0].DefaultCellStyle.BackColor = Color.Blue;
dataGridView2.Rows[1].DefaultCellStyle.BackColor = Color.Blue;
dataGridView2.Rows[0].DefaultCellStyle.ForeColor = Color.White;
dataGridView2.Rows[1].DefaultCellStyle.ForeColor = Color.White;
dataGridView2.Columns[0].Frozen = true;
// pivot 2 - right grid
pivotDT2 = PivotTable2(originalDT);
dataGridView3.DataSource = pivotDT2;
int dgvRow = 0;
// add column headers as row headers in the grid
for (int i = 2; i < originalDT.Columns.Count; i++) {
dataGridView3.Rows[dgvRow++].HeaderCell.Value = originalDT.Columns[i].ColumnName;
}
dataGridView3.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders;
}
Finally, the two pivot/transform methods…
Using your solution and shown in the bottom-left grid…
private DataTable PivotTable(DataTable originalDT) {
DataTable pivotDT = new DataTable();
// the number of columns will be half the original number of columns
int halfCols = Math.DivRem(originalDT.Columns.Count, 2, out int rem);
// if there is a remainder then there is an odd number of columns and we need to add 1 col
if (rem > 0) {
halfCols++;
}
// add the columns to the pivot table
for (int i = 0; i < halfCols; i++) {
pivotDT.Columns.Add();
}
// the number of rows will be the number of original rows times 2
// PLUS 2 additional rows for the headers
for (int i = 0; i < (originalDT.Rows.Count * 2) + 2; i++) {
pivotDT.Rows.Add();
}
// Add the two header rows from the column names
int originalCol = 0;
for (int i = 0; i < halfCols; i++) {
pivotDT.Rows[0][i] = originalDT.Columns[originalCol++].ColumnName;
// if the original table had an odd number of columns
// then the last column only had one field
// - there would never be a column without at least one field
if (originalCol < originalDT.Columns.Count) {
pivotDT.Rows[1][i] = originalDT.Columns[originalCol++].ColumnName;
}
}
// finally add the rows from the original table.
int pivotRow = 2;
int pivotCol = 0;
int curPivotRow;
int curPivotCol;
string value;
for (int originalRow = 0; originalRow < originalDT.Rows.Count; originalRow++) {
curPivotRow = pivotRow;
curPivotCol = pivotCol;
for (originalCol = 0; originalCol < originalDT.Columns.Count; originalCol++) {
value = originalDT.Rows[originalRow][originalCol].ToString();
if (string.IsNullOrEmpty(value)) {
value = "N/A";
}
pivotDT.Rows[curPivotRow][curPivotCol] = value;
// if this is the first item then simply bump the pivot row
if (curPivotRow < pivotRow + 1) {
curPivotRow++;
}
else { // this is the second item -
// we want the curpivot row to start back at the starting pivotRow
// then move over a column for the next two columns in the original table
curPivotRow = pivotRow;
curPivotCol++;
}
}
// new row in the original data start back at column 0 in the pivot table
// and bump the row index by two since we added two rows
pivotRow += 2;
pivotCol = 0;
}
return pivotDT;
}
And my solution shown in the grid on the right.
private DataTable PivotTable2(DataTable originalDT) {
DataTable pivotDT = new DataTable();
for (int i = 0; i < originalDT.Rows.Count; i++) {
pivotDT.Columns.Add();
}
for (int i = 0; i < originalDT.Columns.Count - 2; i++) {
pivotDT.Rows.Add();
}
int pivotCol = 0;
foreach (DataRow row in originalDT.Rows) {
pivotDT.Columns[pivotCol++].ColumnName = row[0].ToString() + " " + row[1].ToString();
}
int pivotRow = 0;
pivotCol = 0;
string value;
for (int i = 0; i < originalDT.Rows.Count; i++) {
for (int j = 2; j < originalDT.Columns.Count; j++) {
value = originalDT.Rows[i][j].ToString();
if (string.IsNullOrEmpty(value)) {
value = "N/A";
}
pivotDT.Rows[pivotRow++][pivotCol] = value;
}
pivotCol++;
pivotRow = 0;
}
return pivotDT;
}
Finally, I am not that proficient using SQL, however, I am betting it is possible to create an SQL procedure that will produce my solution directly from the data base.

Remove a DataRow in a DataTable that contains no numeric values

I want to be able remove DataRows in a DataTable that contain null values for arrays containing no numeric data.
Trying to modify the code below which transposes the DataTable for use by Google Visualisation API:
private DataTable TransposeOpiate(DataTable inputTable)
{
DataTable outputTable = new DataTable();
// Add columns by looping rows
// Header row's first column is same as in inputTable
outputTable.Columns.Add(inputTable.Columns[0].ColumnName.ToString());
// Header row's second column onwards, 'inputTable's first column taken
foreach (DataRow inRow in inputTable.Rows)
{
string newColName = inRow[0].ToString();
outputTable.Columns.Add(newColName);
}
// Add rows by looping columns
for (int rCount = 1; rCount <= inputTable.Columns.Count - 1; rCount++)
{
DataRow newRow = outputTable.NewRow();
// First column is inputTable's Header row's second column
newRow[0] = inputTable.Columns[rCount].ColumnName.ToString();
for (int cCount = 0; cCount <= inputTable.Rows.Count - 1; cCount++)
{
string colValue = inputTable.Rows[cCount][rCount].ToString();
newRow[cCount + 1] = colValue;
}
outputTable.Rows.Add(newRow);
}
return outputTable;
}
For example in the intellisense output shown below the row for improved, where the values after contain no data (this would normally be numeric values) instead contains empty indexes:
I need to be able to delete these rows if they are in the above format, how can I modify my code to perform this?
Why not set a condition which prevents adding the row if all the row cells have a null value?
if (newRow.ItemArray.Any(x => x != null))
{
outputTable.Rows.Add(newRow);
}
If you need to check for columns as well, do this right before returning outputTable
for (int col = outputTable.Columns.Count - 1; col >= 0; col--)
{
bool toDelete = true;
for (int row = 0; row < outputTable.Rows.Count; row++)
{
if (outputTable.Rows[row][col] != null)
{
toDelete = false;
}
}
if (toDelete)
{
outputTable.Columns.RemoveAt(col);
}
}
You can try with something like this (comments are in code):
private void CheckColumns()
{
//table which we want to check
DataTable table = new DataTable();
//add column definition - first column will be string, other two are int columns
table.Columns.Add("string column", typeof(string));
table.Columns.Add("int column 1", typeof(int));
table.Columns.Add("int column 2", typeof(int));
//add data - in this example rows "abc" and "ghi" are valid because they have at least one numeric column
table.Rows.Add(new object[] { "abc", 1, 2 });
table.Rows.Add(new object[] { "def", null, null });
table.Rows.Add(new object[] { "ghi", null, 2 });
table.Rows.Add(new object[] { "jkl", null, null });
//filter rows in a way, using Linq, that rows are filtered where at least one column has numeric value
var validRows = table.AsEnumerable().Where(r => r.ItemArray.Any(c => IsNumeric(c))).ToList();
}
//this is helper method that code will call for each value in each row
private bool IsNumeric(object value)
{
int outputValue;
return int.TryParse(value.ToString(), out outputValue);
}
If you'll have decimal values, then you should une decimal.TyrParse in IsNumeric method.
If you need to be sure that all columns except first one have numeric values, you can do it like this (skipping first value from each row and checking if all other values are numeric)...
var validRows = table.AsEnumerable().Where(r => r.ItemArray.Skip(1).All(c => IsNumeric(c))).ToList();
once you have those 'valid' rows, you can write that data into another table or do whatever you need to do with those data...
Instead of .ToList() you can use .CopyToDataTable() method so you'll get new DataTable with those 'valid' rows. For example:
DataTable newDataTable = table.AsEnumerable().Where(r => r.ItemArray.Skip(1).All(c => IsNumeric(c))).CopyToDataTable();

How to duplicate Row in Datagridview with Button

I have a DataGridView with a column which is filled with buttons. Now I want that with every click on the button the selected row in the DataGridView is copied and inserted directly behind the selected row. But: I only want to duplicate the value of 2 specific columns. And between this two specific columns one should be counted with numbers from 1 starting manually typed in to 2, 3, 4 and so on with every click on the duplicate button. The other columns should be empty. How can I realize this ?
Many thanks in advance
I hope it does the job
// Enable AllowUserToAddRows
dgv.AllowUserToAddRows = true;
private void dgv_CellClick(object sender, DataGridViewCellEventArgs e)
{
DataGridView dgv = sender as DataGridView;
// If button clicked
if (e.ColumnIndex == 0)
{
// Get values what you want to use, the Cell 2 is already increased
var value1 = dgv.Rows[e.RowIndex].Cells[1].Value;
var value2 = dgv.Rows[e.RowIndex].Cells[2].Value;
// Define the maximum id and the row index of it
int maxId = Convert.ToInt32(value2);
int latestRowIndex = e.RowIndex;
for (int i = 0; i < dgv.Rows.Count; i++)
{
// Select the highest id of selected item and get its row index
if (dgv.Rows[i].Cells[1].Value == value1)
{
maxId = Convert.ToInt32(dgv.Rows[i].Cells[2].Value);
latestRowIndex = i;
}
}
// Insert a new row behind max id one
dgv.Rows.Insert(latestRowIndex + 1);
// Set the previously stored values and increase the stored Cell 2 value
dgv.Rows[latestRowIndex + 1].Cells[1].Value = value1;
dgv.Rows[latestRowIndex + 1].Cells[2].Value = maxId + 1;
}
}

Set value of each DataTable row in specific column

I'm having some problem while trying to set column value.
I'v had a dataTable which get some values from SQL and then im adding two new columns by :
dataTable.Columns.Add("dest", typeof(int));
dataTable.Columns.Add("amount", typeof(int));
Which works great but now i want to put 0 in every row in column name dest - and later user will edit this, and then i want to set amount value as
amount = all(this column is in dataTable before I add these 2 columns) + dest;
int columnNumber = 5; //Put your column X number here
for (int i = 0; i < yourDataTable.Rows.Count; i++)
{
yourDataTable.Rows[i][columnNumber] = "0";
}
You can use foreach too.
foreach (DataRow row in myDataTable.Rows)
//if (row["X"] has condition) // or if any condition
row["colName"] = row[colIndex] = "abc";

Categories

Resources