I'm trying to add very large number of columns into datagridview and I faced a challenging problem. When column count becomes too large the code below adds columns very slowly. This depends upon size value - when it less than 10000 I get more or less good result(2-4 seconds to add this number of columns), but when size grows up to 15000 or more the time of adding is not proportional at all, for 30000 of columns it can reach 2 minutes instead of 20-30 seconds as I expect. So my question is it possible to optimize this procedure somehow?
datagridview.SuspendLayout();
int size = 10000;
var columns = new DataGridViewColumn[size];
for (int i = 0; i < size; i++)
{
columns[i] = new DataGridViewTextBoxColumn();
columns[i].Name = "col" + i;
columns[i].HeaderText = "col" + i;
columns[i].FillWeight = 0.00001f;
}
datagridview.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
Array.ForEach(columns, item => datagridview.Columns.Add(item));
datagridview.ResumeLayout();
And this is what I've done:
int visibleColumns = 20;
string[] headers;
DataGridViewColumn[] columns;
HScrollBar hbar = new HScrollBar();
public Constructor(){
...
int sizeDezired = 15000;
int size = Math.Min(sizeDezired, visibleColumns);
columns = new DataGridViewColumn[size];
headers = new string[sizeDezired];
for (int i = 0; i < size; i++)
{
columns[i] = new DataGridViewTextBoxColumn();
columns[i].Name = "col" + i;
columns[i].HeaderText = "col" + i;
columns[i].FillWeight = 0.00001f;
}
for (int i = 0; i < sizeDezired;i++ )
{
headers[i] = "col" + i;
}
if (sizeDezired > size)
{
hbar.Maximum = sizeDezired - size;
hbar.Minimum = 0;
hbar.Value = 0;
}
hbar.Scroll += hbar_Scroll;
...
}
void hbar_Scroll(object sender, ScrollEventArgs e)
{
for (int i = 0; i < datagridview.ColumnCount; i++)
{
datagridview.Columns[i].HeaderText = headers[i + e.NewValue];
}
}
I added this code:
int visibleColumns = 20;// columns that are in data grid view
string[] headers;// headers for all desired columns
DataGridViewColumn[] columns;
HScrollBar hbar = new HScrollBar();
public Constructor(){
...
int sizeDesired = 15000;
int size = Math.Min(sizeDesired, visibleColumns);
columns = new DataGridViewColumn[size];
headers = new string[sizeDesired];
for (int i = 0; i < size; i++)
{
columns[i] = new DataGridViewTextBoxColumn();
columns[i].Name = "col" + i;
columns[i].HeaderText = "col" + i;
columns[i].FillWeight = 0.00001f;
}
for (int i = 0; i < sizeDesired;i++ )
{
headers[i] = "col" + i;
}
if (sizeDesired > size)
{
hbar.Maximum = sizeDesired - size;
hbar.Minimum = 0;
hbar.Value = 0;
}
hbar.Scroll += hbar_Scroll;
...
}
void hbar_Scroll(object sender, ScrollEventArgs e)
{
for (int i = 0; i < datagridview.ColumnCount; i++)
{
datagridview.Columns[i].HeaderText = headers[i + e.NewValue];
}
}
Here horizontal scroll bar added to cycle through all invisible columns and shift column headers to visually "scroll" through all columns(15000 in this example) but in reality only 20 columns are present. This code does not use any databinding, only headers are changing so you need to modify hbar_Scroll handler to show relevant data in cells.
The technique you are looking for is called pagination. Look at the following reference
"Paging is a great way to limit the amount of data displayed to the
user at one time, but is also a very good way of stopping lots of data
being transmitted across the network, being held in memory or big
queries on databases... It solves many problems.
The most common solution is (just like on a Google search) is you get
shown a list of pages and you can navigate around pages either going
up / down a page at a time or clicking on a page number.
Another way is to make it seem like there is actually one big list but
page behind the scenes so the user feels like they are actually
viewing one big list. An example where you see this done is in TFS.
You can do this with a windows forms data grid (DataGridView).
Related
I have NxM textboxes created dynamicly.
User fill textboxes with integer.
I need to create table NxM with data which was puted into Textboxes.
I need it to do matrix calculations.
How can I do that? Can I do this using for each loop?
I have this code which gives me NxM Textboxes:
for (int i = 0; i <= verticalCount; i++)
{
if (i == verticalCount)
{
for (int j = 0; j < horizontalValue; j++)
{
var xEnd = 100 + 80 * verticalCount; ;
var yEnd = 100 + 60 * j;
var textBoxNM = new TextBox();
textBoxNM.Name = string.Format("TextBox_{0}_{1}", i, j);
textBoxNM.Location = new Point(xEnd, yEnd);
textBoxNM.Size = new System.Drawing.Size(50, 25);
Step2.Controls.Add(textBoxNM);
string end = string.Format("result = ", i + 1);
newLabel(end, xEnd - 60, yEnd, Step2);
}
}
else
{
for (int j = 0; j < horizontalValue; j++) //
{
var x = 20 + 80 * i;
var y = 100 + 60 * j;
if (j < horizontalValue)
{
newTextbox(x, y, Step2);
string nbr = string.Format("x{0}", i + 1);
newLabel(nbr, x + 50, y, Step2);
}
}
}
}
I have code written in c++ and I'm trying to create windows application of it.
Thanks!
edit:
public void button2_Click(object sender, EventArgs e)
{
var verticalCount = Convert.ToInt32(comboBox1.Text);
var horizontalValue = Convert.ToInt32(comboBox2.Text);
int[,] tbArray;
tbArray = new int[,] { { horizontalValue , verticalCount } };
foreach (Control ShouldBeTextBox in this.Controls)
{
if (ShouldBeTextBox is TextBox)
{
if (ShouldBeTextBox != null)
{
int x = horizontalValue;
int y = verticalCount;
var tag = ShouldBeTextBox.Tag as int[];
string a = Convert.ToString(tag);
MessageBox.Show(a);
tbArray[tag[x], tag[y]] = Convert.ToInt32(ShouldBeTextBox.Text);
}
else
MessageBox.Show("Fill all parameters");
}
}
}
what you could do is when creating a textbox give them matrix co-ordinates as a tag. Send the i and j to your newTextbox method and do something like
theNewTextBox.Tag = new int[] {i, j};
Later when you need to get values into your matrix array you can do something like this:
foreach(Control c in Step2.Controls)
{
Textbox tb = c as TextBox;
if (tb != null)
{
var tag = tb.Tag as int[];
theMatrixArray[tag[0], tag[1]] = tb.Text; // Or parse it to int if you can't have it in text
}
}
Hope this helps. Good luck!
I really recommend you use WPF for anything new which is UI related, as it simplifies UI customization in contrast to WinForms. By using something called DataTemplates, you can tell WPF how to represent your data model as a UI element.. this means that you can make WPF create as much Textboxes as necessary. You can also receive value updates to each data model instance via a mechanism called Binding. Finally, a mechanism called ItemsPanelTemplate lets you control the layout of your items. You can use a Grid as a Panel Template for a ListView Control.
I am relatively new to posting on here but have been stuck on this loop for quite sometime. In the commented section of the code I have successfully written a for loop that does what it is intended to. I have even gone as far as to make sure that I was successfully getting the filtered row count and number of groups from the textbox that the users fills in. However, I need a loop that iterates through the filtered datatable and assigns each row to a group that is set by the user and only if supported based on row count divided by the min number that a team can hold and not exceeding the max number that can be on a team. Finally the results with the added column showing what group the row is in needs to populate another datagridview in this case datagridview2. All of this takes place on a button click. Any help or advice is greatly appreciated. Thanks....
private void btnAssign_Click(object sender, EventArgs e)
{
Random rnd = new Random();
const double maxTeam = 16.00;
const double minTeam = 6.00;
double peopleCnt = 0.00;
int numGroups = Convert.ToInt32(txtNumGroups.Text);
int j = 1;
double totPeopleCnt = Convert.ToDouble(fdt.Rows.Count);
//MessageBox.Show(totPeopleCnt.ToString()+"&"+numGroups.ToString());
//fdt.Columns.Add(new DataColumn("RandomNumber", Type.GetType("System.Int32")));
// for (int i = 0; i < fdt.Rows.Count; i++)
// {
// fdt.Rows[i]["RandomNumber"] = rnd.Next();
// }
// DataView fdv = new DataView(fdt);
// fdv.Sort = "RandomNumber";
// dataGridView2.DataSource = fdv.ToTable();
//fdt.Columns.Add(new DataColumn("RandomNumber", Type.GetType("System.Int32")));
if (totPeopleCnt / minTeam < Convert.ToDouble(numGroups) || totPeopleCnt / maxTeam > Convert.ToDouble(numGroups))
if (totPeopleCnt / minTeam < Convert.ToDouble(numGroups))
{
MessageBox.Show("Number of Players entered will not meet team minimums for " + txtNumGroups.Text + " Teams.");
}
else
{
for (int i = 0; i < totPeopleCnt; i++)
{
peopleCnt++;
fdt.Rows[i][j] = rnd.Next();
j = (j == numGroups ? 1 : j + 1);
}
DataView fdv = new DataView(fdt);
fdv.Sort = numGroups.ToString();
dataGridView2.DataSource = fdv.ToTable();
}
}
I am creating a calculator where the user enters a number into a textbox specifing how many inputs (textboxes) the user wants to have (Code not shown). I have used a textbox array to create these textboxes. The problem comes when I want to get the text from these textboxes to perform the calculations, the code I have written so far for this is shown below:
int n;
TextBox[] textBoxes;
Label[] labels;
double[] values;
public void GetValue()
{
n = Convert.ToInt16(txtInputFields.Text);
values = new double[n];
textBoxes = new TextBox[n];
for (int i = 0; i < n; i++)
{
}
}
I am unsure what to put in the for loop for this; I have tried the following:
values[n] = Convert.toDouble(textBoxes[n].Text);
but it gives me the error: Index was outside the bounds of the array.
I am new to C# and programming in general so any help would be much appreciated.
Thanks.
EDIT: Code to create textboxes is shown here:
public void InstantiateTextFields()
{
n = Convert.ToInt16(txtInputFields.Text);
int posLeft = 100;
textBoxes = new TextBox[n];
labels = new Label[n];
// Creates number of inputs and labels as specified in txtInputFields (n).
for (int i = 0; i < n; i++)
{
textBoxes[i] = new TextBox();
textBoxes[i].Top = 100 + (i * 30);
textBoxes[i].Left = posLeft;
textBoxes[i].Name = "txtInput" + (i + 1);
labels[i] = new Label();
labels[i].Top = 100 + (i * 30);
labels[i].Left = posLeft - 50;
labels[i].Text = "Input " + (i + 1);
labels[i].Name = "lblInput" + (i + 1);
}
for (int i = 0; i < n; i++)
{
this.Controls.Add(textBoxes[i]);
this.Controls.Add(labels[i]);
}
}
Your code in the GetValue method recreates the array of textboxes and doing so destroys the orginal content (the textboxes dynamically created InstantiateTextFields).
In this way your loop fails with Object Reference not set.
You just need to use the global variable without reinitiaizing it
public void GetValue()
{
n = Convert.ToInt16(txtInputFields.Text);
values = new double[n];
// textBoxes = new TextBox[n];
for (int i = 0; i < n; i++)
{
values[i] = Convert.ToDouble(textBoxes[i].Text);
}
}
There is something to be said about reading the input text and converting it to double without checks. If your user types something that cannot be converted to a double your code will crash on the Convert.ToDouble line. Use instead
double temp;
for (int i = 0; i < n; i++)
{
if(double.TryParse(textBoxes[i].Text, out temp)
values[i] = temp;
else
{
// Not a double value....
// A message to your user ?
// fill the array with 0 ?
// Your choice....
}
}
values[n] = Convert.toDouble(textBoxes[n].Text); gives you error because n is outside of the array. You allocate an array with the size of n which is zero indexed aka the last element is at position n-1.
for (int i = 0; i < n; i++)
{
values[i] = Convert.toDouble(textBoxes[i].Text);
}
I'd like to design an interface showing buttons in a data grid - for each day and its 24 hours. All in all we'll be showing: 24 * 7 = 168 buttons.
Any idea how to accomplish this?
I did an example code of how it ca ne done!
This code only creates the buttons, so check it out:
note: name buttons your own way, I only did an example!
dataGridView1.AllowUserToAddRows = false;
for (int i = 0; i < 24; i++)
{
DataGridViewButtonColumn btnColumn = new DataGridViewButtonColumn();
btnColumn.HeaderText = string.Format("{0}:00", i+1);
btnColumn.Name = "dayColumn";
btnColumn.Width = 40; //set yout width
dataGridView1.Columns.Add(btnColumn);
}
for (int i = 0; i < 7; i++)
{
dataGridView1.Rows.Add();
dataGridView1.Rows[i].HeaderCell.Value = (i + 1).ToString();
for (int j = 0; j < dataGridView1.Columns.Count; j++)
{
dataGridView1[j, i].Value = string.Format("{0}:00", (j + 1));
}
}
--
Maybe subscribing to an event handler, when some button is clicked is a good idea:
public Form1()
{
InitializeComponent();
dataGridView1.AllowUserToAddRows = false;
for (int i = 0; i < 24; i++)
{
DataGridViewButtonColumn btnColumn = new DataGridViewButtonColumn();
btnColumn.HeaderText = string.Format("{0}:00", i+1);
btnColumn.Name = "dayColumn";
btnColumn.Width = 40; //set yout width
dataGridView1.Columns.Add(btnColumn);
}
for (int i = 0; i < 7; i++)
{
dataGridView1.Rows.Add();
dataGridView1.Rows[i].HeaderCell.Value = (i + 1).ToString();
for (int j = 0; j < dataGridView1.Columns.Count; j++)
dataGridView1[j, i].Value = string.Format("{0}:00", (j + 1));
}
dataGridView1.CellClick += new DataGridViewCellEventHandler(dataGridView1_CellClick);
}
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
string day = dataGridView1.Rows[e.RowIndex].HeaderCell.Value.ToString();
string hour = dataGridView1.Columns[e.ColumnIndex].HeaderText.ToString();
MessageBox.Show("you have clciked on day: " + day + ", hour: " + hour);
}
There is a framework interface called ITypedList which allows you to directly specify properties to be displayed for each constituent member, in a way that databinding understands.
Realise that attempting to do binding in this way is absolutely hideous. The basic steps are as follows:
Implement a collection type that implements ITypedList;
For each item in the collection, return a PropertyDescriptor which encapsulates the value of the row/column pair;
Databind to the collection as normal
I've done this as an experiment and it works, but the hoops you'll have to jump through to get everything to work properly are a nightmare. Still, I'd usually consider it better than manipulating view logic directly. DataGridView is meant to be intelligent enough to infer everything it needs to do from binding, and if I can modify the binding target rather than the control, that's what I'd prefer.
I have a grid which has 4 bands and the columns in band[3] are not arranged in order as in its parent bands. My primary requirement is to arrange the Band[3] columns.
This is a part of "Export to Excel" code. The grids are already displayed in the form.
I have tried the below approach -
private UltraGrid rearrangeCol(UltraGrid grid)
{
int bandCount = grid.DisplayLayout.Bands.Count;
int band = 0;
List<String> colPos = new List<string>();
for (int i = grid.DisplayLayout.Bands[band].Columns["EndurEndInv"].Index; i < grid.DisplayLayout.Bands[band].Columns.Count; i++)
{
colPos.Add(grid.DisplayLayout.Bands[band].Columns[i].Key);
}
band++;
while (band < bandCount)
{
int initialColPos = grid.DisplayLayout.Bands[band].Columns["EndurEndInv"].Index;
for (int i = initialColPos, j = 0; i < grid.DisplayLayout.Bands[band].Columns.Count && j < colPos.Count; i++, j++)
{
if(!grid.DisplayLayout.Bands[band].Columns[i].Key.Equals(colPos[j], StringComparison.InvariantCulture))
{
grid.DisplayLayout.Bands[band].Override.AllowColSwapping = Infragistics.Win.UltraWinGrid.AllowColSwapping.WithinBand;
int xcngPos = grid.DisplayLayout.Bands[band].Columns[colPos[j]].Index;
grid.DisplayLayout.Bands[band].Columns.Insert(i, "Temp");
grid.DisplayLayout.Bands[band].Columns[xcngPos + 1].Swap(grid.DisplayLayout.Bands[band].Columns[i]);
grid.DisplayLayout.Bands[band].Columns.Remove("Temp");
grid.DisplayLayout.Bands[band].Override.AllowColSwapping = Infragistics.Win.UltraWinGrid.AllowColSwapping.Default;
}
else
continue;
}
band++;
};
return grid;
}
Actually, nothing happens when I use SWAP, the keys, index remains same.
Is there any better approach?
Yes, to rearrange to position of colums you need to work on
grid.DisplayLayout.Bands[index].Columns[colName].Header.VisiblePosition = newPos;
no need to swap the column positions, just rearrange the Header position.
This code below has not been tested and I write as an example
private UltraGrid rearrangeCol(UltraGrid grid)
{
int bandCount = grid.DisplayLayout.Bands.Count;
int band = 1;
UltraGridBand b0 = grid.DisplayLayout.Bands[0];
while (band < bandCount)
{
UltraGridBand b = grid.DisplayLayout.Bands[band]
for (int i = b0.Columns["EndurEndInv"].Index; i < b0.Columns.Count; i++)
{
string colKey = b0.Columns[i].Key;
if(b.Columns.Exists(colKey))
b.Columns[colKey].Header.VisiblePosition =
b0.Columns[colKey].Header.VisiblePosition;
}
band++;
}
return grid;
}