I need to use a the DataGridView control to display a large number of columns. I have a DataGridViewCell class that specifies a custom Paint method for each cell. I have added the columns like so...
int ColumnCount = 5000;
DataGridViewTextBoxCell cell = new DataGridViewTextBoxCell();
for (int i = 0; i < ColumnCount; i++)
{
dataGridView1.Columns.Add(new DataGridViewColumn() { CellTemplate = cell, FillWeight = 1 });
}
The problem is, this takes ages to add all the columns, much longer than it should really take. When I add the columns I can see the size of the scroll bar at the bottom of the DataGridView getting smaller like the grid is drawing each column each time I add one.
Does anyone know of a quicker way to add a large number of columns, or how to prevent the DataGridView updating until all the columns have been added?
I've tried disabling resizing, SuspendLayout(), and setting dataGridView1.Visible = false.
If you use the VirtualMode = TRUE for the DataGridView, you can refresh ONLY the screen portion.
Related
I have a dgv C# DataGridView built without DataSource.
I have a column with index c that is set to invisible when the value of a cell of another column changes to a certain value (within the CellValueChanged event handler):
dgv.Columns[c].Visible = false;
The CellValueChanged event handler is called after modifying the cell and then clicking on another cell. The problem is that when I click on the column which is about to be set to invisible I get the exception "Current cell cannot be set to an invisible cell", whereas everything is fine if I click on one of the other columns.
I read some other answers (e.g. this or this) where using CurrencyManager.SuspendBinding and CurrencyManager.ResumeBindingis suggested. However that doesn't work for me because the DataSource is null.
Any hint?
Thank you.
The problem with setting the column to “not visible” in the grids CellValueChanged event is that while the scope of execution is in the CellValueChanged event, the grid is NOT going to give you ANY information about “where” the user clicked/moved “to” after the cells value changed.
This alone is ALWAYS going to leave hiding the column (in that event) prone to errors since we do not know “where” the user clicked/moved. Because of this it would appear obvious that if we want to avoid the error we are going to have to “hide” the column in some other event.
Another issue you may run into is that some events don’t like it when you try to “change” the grids “selected” cell, which is what we are looking to do when the user clicks on the column we want to make invisible. We need to “change” the selected cell to some other cell to avoid the error.
Given this, below is a hacky approach that may help. Be aware that there was NOT a lot of testing on the code and one issue/error occurred when the user clicked on the column header (to sort) for the first column. This caused a crash and I ended up turning the “sorting” off for that column.
The small example below uses this logic... A DataGridView with four (4) text columns is set on a form and filled with some data. ConditionColumn (column 0) in the grid is the “condition” column and it has string values from 0-19. It is this column that will trigger “when” to “hide” the HideColumn (column 2).
IF a value greater than twenty (20) is entered into any cell in the ConditionColumn, THEN the HideColumn will be set as invisible. There is a small function AllCellsLessThan20 that loops through all the rows in the grid and checks the ConditionColumn cells for any values that are greater than 20. If at least one cell value is greater than 20, then the function will return false. This is used to turn the hidden column on or off such that if ALL cells in the ConditionColumn are less than 20, then the column will be shown. Otherwise, if one or more cells is greater than 20, then the column will be hidden.
To avoid the error described, the code will set the column to invisible in the grids SelectionChanged event. This event will fire “after” the CellValueChanged event if the user selects another cell by any means… click, tab or arrow keys. We will check several things to determine if we need to hide or show the column.
Obviously since this event is called AFTER the CellValueChange event fired, the grid is not going to give us ANY information about “where” the previously selected cell was. Hence, the global variables. Setting these global values in the CellValueChanged event will make them available in the SelectionChanged event.
Maybe some variables like:
ColumnShown : bool to indicated if the column is currently hidden or shown.
ValueGreaterThan20: bool to indicate if any value in the ConditionColumn is greater than 20
and PreviousRow : int to indicate the row index of the last “Changed” cell.
bool ColumnShown = true;
bool ValueGreaterThan20 = false;
int PreviousRow = 0;
Set the grids columns with some useful names to avoid any indexing problems, then fill the grid with some test data.
public Form1() {
InitializeComponent();
dataGridView1.Columns[0].Name = "ConditionColumn";
dataGridView1.Columns[0].HeaderText = "Condition";
dataGridView1.Columns[2].Name = "HideColumn";
dataGridView1.Columns[2].HeaderText = "Hide Column";
}
private void Form1_Load(object sender, EventArgs e) {
FillGrid();
}
private void FillGrid() {
for (int i = 0; i < 20; i++) {
dataGridView1.Rows.Add(i, "C1R" + i, "C2R" + i, "C3R" + i);
}
}
Next is a function to check if any value in the ConditionColumn is greater than 20.
private bool AllCellsLessThan20() {
foreach (DataGridViewRow row in dataGridView1.Rows) {
if (row.Cells[0].Value != null) {
string sValue = row.Cells["ConditionColumn"].Value.ToString();
if (int.TryParse(sValue, out int value)) {
if (value > 20) {
return false;
}
}
}
}
return true;
}
Next, is the grids CellValueChanged event. Here we only need to check two things: 1) was the value changed in column 0 ConditionColumn and since a value did change in that column we need to check ALL the values. If any value is over 20, then set the variable ValueGreaterThan20 to true otherwise false. Also, we will save the row index (PreviousRow) of the changed cell since we will want to use it in the SelectionChanged event.
private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if (e.ColumnIndex == 0) {
if (AllCellsLessThan20())
ValueGreaterThan20 = false;
else
ValueGreaterThan20 = true;
}
PreviousRow = e.RowIndex;
}
The SelectionChanged event is below. I am aware this could be more compact. First a check is made to see if the column is shown or hidden.
If the column is already “hidden”, then we just need to check to make sure at least one value in the ConditionColumn is greater than 20 and simply leave it hidden. If all values are less than 20 then we want to un-hide the column.
If the column is NOT “hidden,” then we check to see if any values are greater than 20. If no values are greater than 20, then simply leave the column visible.
If the column is NOT “hidden,” and one or more values are greater than 20, then we need to check to see if the current selection is the column we want to hide. If the current selection is NOT the column we want to hide, then simply hide it since we know this will not throw an error.
Lastly, if the column is NOT “hidden” AND there is a value greater than 20 AND the current selection IS the column we want to hide. In this case the code will set/change the CurrentCell to the first cell in the PreviousRow. Then set the column to invisible and set the global variable ColumnShown.
In my test, if the user changes a cell in column 0 to a value grater than 20 then “Clicks” on a cell in column 2 HideColumn, then the error does not pop up and the selection changes to the first cell in the previous row.
private void dataGridView1_SelectionChanged(object sender, EventArgs e) {
if (ColumnShown) {
if (ValueGreaterThan20) {
int curColIndex = dataGridView1.CurrentCell.ColumnIndex;
if (dataGridView1.Columns[curColIndex].Name == "HideColumn") {
dataGridView1.CurrentCell = dataGridView1.Rows[PreviousRow].Cells["ConditionColumn"];
dataGridView1.CurrentCell.Selected = true;
}
dataGridView1.Columns["HideColumn"].Visible = false;
ColumnShown = false;
}
}
else {
if (!ValueGreaterThan20) {
dataGridView1.Columns["HideColumn"].Visible = true;
ColumnShown = true;
}
}
}
As I said its hacky but it works with some caveats. Hope this helps!
I have the following code:
for (int i = 0; i < COLUMNS.Count; i++)
{
DataGridViewColumn column = new DataGridViewColumn()
{
Name = COLUMNS.ElementAt(i).Key,
HeaderText = COLUMNS.ElementAt(i).Value,
Width = 60
};
operationsDataGridView.Columns.Add(column);
}
I hope it's clear enough, I'm looping through a Dictionary of Column Name and Column Headers, and adding them as DataGridViewColumn instances to the DataGridView.
However, the program is looping only once, when it should loop 9 times (I've already debugged it, and COLUMNS.Count is, in fact, 9)
Therefore, only the first column is added, as it can be seen in the image:
As for the debugging output, these strange messages appear:
I'm sure it has something to do with this problem.
Interesting Fact!
If I change my code to:
for (int i = 0; i < COLUMNS.Count; i++)
{
operationsDataGridView.Columns.Add(COLUMNS.ElementAt(i).Key,
COLUMNS.ElementAt(i).Value);
}
(Which I think, is esentially the same), the program works fine!!
I was able to reproduce this error on my end, but I was getting an exception as well about "At least one of the DataGridView control's columns has no cell template."
If you change your loop to DataGridViewColumn column = new DataGridViewTextBoxColumn() it will probably work (fixed it on my end at least). Looks like the .add method with the two parameters defaults to this type of column.
I am looking for a Windows Forms Control that looks like a grid of a check boxes in C#. Something like this I guess, hope it makes sense.
So how would I go about making this happen, is it even possible?
You can use TableLayoutPanel, this link may help you to know some of its properties, and this link is example for using it
Strongly avoid a TableLayoutPanel it is much too expensive with this many check boxes.
DataGridView is the appropriate choice, change the column types to DataGridViewCheckBoxColumn. Editing only requires a single real checkbox control, taken care of automatically. No problems with focus either.
Making your own is a very reasonable approach as well, you can make it look any way you want. Derive a class from Control, Panel if you need it to be scrollable. ControlPaint.DrawCheckBox() can be useful.
Use a TableLayoutPanel to render the check boxes.
This sample creates a TableLayoutPanel by code:
TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
int rowCount = 2;
int columnCount = 2;
for (int row = 0; row < rowCount; row++)
{
tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
for (int column = 0; column < columnCount; column++)
{
tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
CheckBox checkBox = new CheckBox();
tableLayoutPanel.Controls.Add(checkBox, column, row);
}
}
this.Controls.Add(tableLayoutPanel);
I'm trying to display the table of m*n cells with some text in each cell, and the background colors of each cell could be different.
Am I right the dataGridView component could be used exactly for this purpose?
If yes, then how to make the dataGridView to contain more then just one empty row? Let's say I want it 5*5 cells and the cells could be empty.
You can add values to a DataGridView control in many ways: from a database, from a Collection (Array, DataTable, etc.), directly row by row, etc. In each cell you can put the (string) values you want, including ""/empty. Here you have a sample code to get some inspiration:
int count = 0;
int maxCount = 5;
do
{
count = count + 1;
//dataGridView1.Rows.Add("col1", "col2", "col3", "col4", "col5");
dataGridView1.Rows.Add(); //For adding empty rows, you can use this one
} while(count < maxCount);
dataGridView1[1, 2].Style.BackColor = Color.Yellow;
dataGridView1[3, 1].Style.BackColor = Color.Yellow;
dataGridView1[4, 4].Style.BackColor = Color.Yellow;
It takes dataGridView1 (a DataGridView with 5 columns added via "Design View"), adds 5 rows to it and colors the background of various cells.
Or is there any better suited 3rd party control for this purpose?
I know that DevExpress XtraGrid supports, in theory, Int32.MaxValue rows or columns in the grid. In this case, the limit is the system memory not the grid.
But do you really need to display so much data?
Short answer: Dont do it!
Long answer: Change the FillWeight to 10 or less (default is 100). The limit you are reaching is due to the total FillWeight exceeding 64K x 100 (who knows why that is a limit).
Use a virtual list (loads only the rows that are visible). I'm not sure that WinForms ListView has a virtual mode but the WPF one does.
So create a WPF user control and set it up for VirtualMode = True and host that user control on your WinForms client with an ElementHost container.
Sorry I can't be more specific, I don't have the code to hand.
Ryan
You're missing that the FillWeight variable takes a floating point number not an integer, so 0.5f or 0.01f would do (the latter would allow up to 6553500 columns in theory). Unfortunately, creation is very slow (at least for me, increasingly past around 1000 columns; 10,000 cols takes about 20 seconds). Perhaps the VirtualMode others have suggested is worth a shot.
For what it's worth, here is the code I use to create an x by y size table of empty cells. Perhaps someone can optimize the speed further:
private void createDGVcells(DataGridView dgv, int columns, int rows) {
// Optimization:
dgv.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; // Massive speed up if high column count
dgv.ScrollBars = ScrollBars.None; // Apx. 75% speedup for high row count
dgv.AllowUserToAddRows = false; // Further 50% apx speedup when creating rows
dgv.ReadOnly = true; // Small apx. 50ms latency speedup?
// First clear any existing cells, should they exist:
if (dgv.DataSource != null) dgv.DataSource = null;
else {
dgv.Rows.Clear();
dgv.Columns.Clear();
}
// Create the first row (the columns):
DataGridViewColumn[] dgvc = new DataGridViewColumn[columns];
for (int i = 0; i < dgvc.Length; ++i) {
DataGridViewColumn dg = new DataGridViewTextBoxColumn();
dg.FillWeight = 0.1f; // Allows up to 655350 columns in theory
dgvc[i] = dg;
}
dgv.Columns.AddRange(dgvc);
// Add all the rows (very quick)
for (int j = 0; j < rows - 1; j++) dgv.Rows.Add();
// Optional to turn these back on
dgv.ReadOnly = false;
dgv.AllowUserToAddRows = true;
dgv.ScrollBars = ScrollBars.Both;
dgv.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing;
}
Xceed's DataGrid for WPF can do this easily and uses both column and UI virtualization. Check out their live demo. You can populate the demo with as many columns and rows needed to test perf. http://xceed.com/Grid_WPF_Demo.html