C# listview optimum column width - c#

I have a C# ListView and I'd like to autosize the column widths based on the contents of the column.
I know that setting the column width to -1 will size the column at the length of the widest member.
I know that setting the column width to -2 will size the column at the length of the column header.
How do I size the column to be the greater of the two?
I could do something like this:
for (int i = 0; i < listView.Columns.Count; ++i)
{
listView.Columns[i].Width = -1;
int width1 = listView.Columns[i].Width;
listView.Columns[i].Width = -2;
if (width1 > listView.Columns[i].Width)
listView.Columns[i].Width = -1;
}
but it does seem fabulously inefficient.
Does anyone have an answer?

You should try
ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
even though it says "ColumnHeaderAutoResizeStyle.HeaderSize" it should auto resize to the largest item in the column, whether it's the header or a column member

Related

c# epplus not filling background color of cell

Ok I am stumped the set font works and sets the correct cell. The Fill.BackgroundColor.SetColor does not work. It literally does nothing. Am I missing something? Every search I do that syntax should work.
Yes this is reading a data table dt that is not the issue the loop works properly.
int startupRow = 2; // row 1 is header
for (int row = startupRow; row <= dt.Rows.Count; row++)
{
//if allocation check is populated it will have a value > 0
if (Convert.ToInt32(workSheet.Cells[row, 8].Value) > 0)
{
//if Balance Remaining Barrels < allocation check
if (Convert.ToInt32(workSheet.Cells[row, 7].Value) < Convert.ToInt32(workSheet.Cells[row, 8].Value))
{
//set the font to red
var cell = workSheet.Cells[row, 7];
cell.Style.Font.Color.SetColor(System.Drawing.Color.Red);
cell.Style.Font.Bold = true;
//Setting the background color to red
cell.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
cell.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.Red);
}
}
}
I resolved my own question so hopefully this helps someone else.
The border has to be last in the formatting. Certain elements take precedent over other elements and they will disappear.

Changing row color in datagridview C#

So I am trying to change the color of a DataGridView row depending on the value of a cell. Basically, I am dropping real files into the DataGridView and it will display the information of that file.
this is what I currently have:
double size;
size = Math.Round(MeuFicheiro.Length / 1024d, 4);
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (size >= 0 && size <= 4999)
{
row.DefaultCellStyle.BackColor = Color.Red;
}
if (size >= 5000)
{
row.DefaultCellStyle.BackColor = Color.Green;
}
else if (size >= 15000)
{
row.DefaultCellStyle.BackColor = Color.Orange;
}
}
But this is not working because it does not work because when I insert a new fill it will change the color of all the rows instead of the row I've added.
After making some changes I've got this on the method I've created to display information into the DataGridView:
private void MostrarDataGridView()
{
try
{
con = new SqlConnection(cs.DBConn);
con.Open();
cmd = new SqlCommand("SELECT * FROM InfoFile", con);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds, "InfoFiel");
dataGridView1.DataSource = ds.Tables["InfoFile"].DefaultView;
DataTable dt = ds.Tables["InfoFile"];
System.Data.DataRowView dgrv = (System.Data.DataRowView)
string fnome = dgrv.Row.ItemArray[0].ToString();
con.Close();
}
}
Do you have any help to give? I just what to change the added row color depending on the value of size.
To start… Your first sentence and last sentence are contradictory. First you say…
So I am trying to change the color of a DataGridView row depending on the value of a cell.
THEN the last sentence:
I just what to change the added row color depending on the value of size.
Those are two different things, so it is unclear what exactly you want to do.
If you are simply comparing size, then ALL the rows will be same color depending on the value of size. In your code attempt, if size is greater than 15000 then all rows will be orange. But following your code, each row will first get set to red if tamanho is less than 5000, then set again to orange. This logic does not look right.
Example: Setting size = 5001 and tamanho to 4999.
if (size >= 0 && tamanho <= 4999)
In the above if statement it will change the color to red. Then, the next if statement:
if (size >= 5000)
is executed because there is no “else” clause in the previous if statement. This if statement will obligingly change the color to green because size = 5001. So your last setting of the row to red is unnecessary and confusing.
As long as size is less than 5000, all rows will be red if tamanho is less than 5000 and white if tamanho is greater than 4999.
If size is greater than 4999 but less than 15000 then all rows will be green no matter what tamanho is.
If size is greater than 14999 all rows will be orange no matter what tamanho is.
Since the value of size (and tamanho) never changes while looping thru the rows, your logic will simply change ALL the rows to the color that matches with the unchanging variable size. This will set ALL the rows to the same color depending on the value of size.
So in reference to you first question: change the color of a DataGridView row depending on the value of a cell. In your code you are not grabbing any value from any cells in the DataGridView to compare to anything. If you want to change a rows color when a particular cells value is greater than a set value, then you need to grab that cells value. Below is one example of how to grab the value from a cell in the DataGridView and check its value.
Using your foreach (DataGridViewRow row in dataGridView1.Rows) loop, you can access the FIRST cell/(column) of the current row with the following code.
String stringValue = row.Cells[0].Value.ToString();
If your DataGridView is bound to a DataSet Table then you may/will have to use something different depending on your table. An example is below to get the string from dgrv.
System.Data.DataRowView dgrv = (System.Data.DataRowView)DataTable.CurrentRow.DataBoundItem;
The code below will change the row color for that row if the current row’s ‘Cell[colIndex]’ is greater than ‘valueToCompare’. In this case let’s say the cell value is a string but contains an integer value. So to get this integer value and compare it to valueToCompare we need to parse the string to an integer then compare the values, then change the row color depending on if the cell value is greater than valueToCompare. GetIntValue(string) returns 0 if the string (cell value) is not a valid integer.
private void SetRowColorBasedOnColValue(int valueToCompare, int colIndex, Color color)
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.Cells[colIndex] != null && row.Cells[colIndex].Value != null)
{
int cellValue = GetIntValue(row.Cells[colIndex].Value.ToString());
if (cellValue >= valueToCompare)
{
row.DefaultCellStyle.BackColor = color;
}
else
{
row.DefaultCellStyle.BackColor = Color.White;
}
}
else
{
// null cell
row.DefaultCellStyle.BackColor = Color.White;
}
}
}
private int GetIntValue(string inString)
{
int returnValue = 0;
if (int.TryParse(inString, out returnValue))
return returnValue;
return returnValue;
}
Below I used the above method... I set the row color depending on the value of the cell at colIndex. If the cell’s value is greater than valueToCompare then the row color gets changed to color.
// SetRowColorBasedOnColValue(valueToCompare, colIndex, color);
SetRowColorBasedOnColValue(15000, 0, Color.Orange);
SetRowColorBasedOnColValue(5000, 0, Color.Green);
SetRowColorBasedOnColValue(0, 2, Color.Red);
This is not the best approach though as an anonymous function/delegate would be one of many better approaches to this problem as this method only compares if the cell value is “greater than” the supplied value, this severely limits the functionality of the method.
Hope this helps.

Insert Excel row with normal height

This is my C# code to insert rows into an Excel worksheet:
for (var j = 0; j < dummies.Count; j++)
{
mySheet.Range[myRange].Insert(Excel.XlInsertShiftDirection.xlShiftDown);
}
Nothing too Earth shattering there.
However, if one of the rows below the insert has a different height to the other rows, what seems to happen is that the new row at that position keeps the same height, and so does the row which is shifted down.
So for example, before the code is run, all rows are normal height except 11 which is double the height of the others.
If I insert 10 rows at row 7, the result is that rows 11 and 21 are double height.
Inserting 1 row above makes 11 and 12 double height.
Inserting 2 rows gives the same result for rows 11 and 13, etc.
So the original double-height row always seems to keep its height, even though the height also follows the shifted-down row.
Is there a way I can specify that when I insert a row, the row height should not get left behind?
[edit]
If I insert a row manually it all works fine.
[/edit]
I found out why this is.
In the code above I'm not shifting the entire row down.
This means that the cells that aren't shifting down are keeping their original height, which is affecting the height of all the other cells in the row.
The following works:
for (var j = 0; j < dummies.Count; j++)
{
mySheet.Range[myRange].EntireRow.Insert(Excel.XlInsertShiftDirection.xlShiftDown);
}

ListView AutoResizeColumns based on both Column content and header

we use this two methods to adjust column length based on Column content and header resp.
ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
But how to adjust based on both? i.e. adjust to the longest length for header and column content.
lvw.Columns[0].Width = -2
See remarks in MSDN for details:
http://msdn.microsoft.com/en-us/library/system.windows.forms.columnheader.width.aspx
Also note that MSDN says that 'To autosize to the width of the column heading, set the Width property to -2.', but actually it works for column heading AND column contents.
Here is a code to prove that:
lvw.Columns.Add(new String('x', 25)); // short header
lvw.Items.Add(new String('x', 100)); // long content
lvw.Columns[0].Width = -2;
// in result column width will be set to fit content
As answered here, calling both resizing options do the job :
myListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
myListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
This is what I use to adjust column width to both content and header:
public static void autoResizeColumns(ListView lv)
{
lv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
ListView.ColumnHeaderCollection cc = lv.Columns;
for (int i = 0; i < cc.Count; i++)
{
int colWidth = TextRenderer.MeasureText(cc[i].Text, lv.Font).Width + 10;
if (colWidth > cc[i].Width)
{
cc[i].Width = colWidth;
}
}
}
Example use:
autoResizeColumns(listView1);
The method isn't that well tested, but at least it works in the context I'm using it in.
It's possible indeed to use MeasureText and then to calculate how much space is left and somehow distribute between all columns. But this is quick-and-dirty approach which I have quickly coded:
/// <summary>
/// Enables autoresizing for specific listview.
/// You can specify how much to scale in columnScaleNumbers array - length of that array
/// should match column count which you have.
/// </summary>
/// <param name="listView">control for which to enable auto-resize</param>
/// <param name="columnScaleNumbers">Percentage or numbers how much each column will be scaled.</param>
private void EnableAutoresize(ListView listView, params int[] columnScaleNumbers)
{
listView.View = View.Details;
for( int i = 0; i < columnScaleNumbers.Length; i++ )
{
if( i >= listView.Columns.Count )
break;
listView.Columns[i].Tag = columnScaleNumbers[i];
}
listView.SizeChanged += lvw_SizeChanged;
DoResize(listView);
}
void lvw_SizeChanged(object sender, EventArgs e)
{
ListView listView = sender as ListView;
DoResize(listView);
}
bool Resizing = false;
void DoResize( ListView listView )
{
// Don't allow overlapping of SizeChanged calls
if (!Resizing)
{
// Set the resizing flag
Resizing = true;
if (listView != null)
{
float totalColumnWidth = 0;
// Get the sum of all column tags
for (int i = 0; i < listView.Columns.Count; i++)
totalColumnWidth += Convert.ToInt32(listView.Columns[i].Tag);
// Calculate the percentage of space each column should
// occupy in reference to the other columns and then set the
// width of the column to that percentage of the visible space.
for (int i = 0; i < listView.Columns.Count; i++)
{
float colPercentage = (Convert.ToInt32(listView.Columns[i].Tag) / totalColumnWidth);
listView.Columns[i].Width = (int)(colPercentage * listView.ClientRectangle.Width);
}
}
}
// Clear the resizing flag
Resizing = false;
}
And depending how many columns you have - you specify each column "percentage" or simply number. For example for 3 columns - call looks like this:
EnableAutoresize(listView1, 6, 3, 1);
This will distribute column sizes as:
6 * 100% / (6 + 3 + 1) = 60% for first column,
30% for next and 10% for remaining.
This is somehow poor man quick implementation. :-)
In my case, I do this through the next steps (for two columns of data):
Creating a ColumnHeader object for each column.
Setting the size by AutoResize based on HeaderSize (on both columns)
Store that value in a Integer variable
Setting the size by AutoResize based on ColumnContent (on both columns)
Updating the value of each Integer variable through the Max criteria between the old value and the new value (for each column).
Setting the column width size for each ColumnHeader object.
In VB.NET:
'Create two header objects as ColumnHeader Class
Dim header1, header2 As ColumnHeader
'Construcción de los objetos header
header1 = New ColumnHeader
header1.Text = "ID"
header1.TextAlign = HorizontalAlignment.Right
header1.Width = 10
header2 = New ColumnHeader
header2.Text = "Combinaciones a Procesar"
header2.TextAlign = HorizontalAlignment.Left
header2.Width = 10
'Add two columns using your news headers objects
ListView.Columns.Add(header1)
ListView.Columns.Add(header2)
'Fill three rows of data, for each column
ListView.Items.Add(New ListViewItem({"A1", "B1"}))
ListView.Items.Add(New ListViewItem({"A2", "B2"}))
ListView.Items.Add(New ListViewItem({"A3", "B3"}))
'Change the size of each column
Dim headsz1, headsz2 As Integer
SelectionInTable.ListView.AutoResizeColumn(0, ColumnHeaderAutoResizeStyle.HeaderSize)
SelectionInTable.ListView.AutoResizeColumn(1, ColumnHeaderAutoResizeStyle.HeaderSize)
headsz1 = header1.Width
headsz2 = header2.Width
SelectionInTable.ListView.AutoResizeColumn(0, ColumnHeaderAutoResizeStyle.ColumnContent)
SelectionInTable.ListView.AutoResizeColumn(1, ColumnHeaderAutoResizeStyle.ColumnContent)
headsz1 = Math.Max(headsz1, header1.Width)
headsz2 = Math.Max(headsz2, header2.Width)
header1.Width = headsz1
header2.Width = headsz2
Here's a C# solution that can be used for any ListView. It assumes your column count and headers won't change for any given list view. Get rid of the listViewHeaderWidths dictionary if you want to recalculate header widths every time (if headers change, or number of columns changes).
private Dictionary<string, int[]> listViewHeaderWidths = new Dictionary<string, int[]>();
private void ResizeListViewColumns(ListView lv)
{
int[] headerWidths = listViewHeaderWidths.ContainsKey(lv.Name) ? listViewHeaderWidths[lv.Name] : null;
lv.BeginUpdate();
if (headerWidths == null)
{
lv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
headerWidths = new int[lv.Columns.Count];
for (int i = 0; i < lv.Columns.Count; i++)
{
headerWidths[i] = lv.Columns[i].Width;
}
listViewHeaderWidths.Add(lv.Name, headerWidths);
}
lv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
for(int j = 0; j < lv.Columns.Count; j++)
{
lv.Columns[j].Width = Math.Max(lv.Columns[j].Width, headerWidths[j]);
}
lv.EndUpdate();
}
Anton Kedrov answer is best one but in my case i have a listview with more than 50 columns and i update its data frequently in this case i notice listview's this.AutoResizeColumns performs much faster work so i m writing this solution also
First Method by setting with to -2
public void AutoUpdateColumnWidth(ListView lv)
{
for (int i = 0; i <= lv.Columns.Count - 1; i++) {
lv.Columns(i).Width = -2;
}
}
Second method i used (less flicker on multiple calls)
public void AutoUpdateColumnWidth(ListView lv)
{
ListViewItem nLstItem = new ListViewItem(lv.Columns(0).Text);
for (int i = 1; i <= lv.Columns.Count - 1; i++) {
nLstItem.SubItems.Add(lv.Columns(i).Text);
}
v.Items.Add(nLstItem);
lv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
lv.Items.RemoveAt(nLstItem.Index);
}
This is simple (although it took me a while to figure out)...
We know that the width must be at least as great as the column headers, so that we see all of the header text. Beyond that, the width can expand larger to accommodate contents. Hence, we do the following:
Autosize the columns to header.
Iterate through the columns and set the minimum width property for each column to the current column width (which guarantees your columns will never get too small to see the header).
From now on, autosize columns by content.
It is not necessary to track widths separately and reset them as other posters suggest. Setting the minimum width for the column solves the issue until the header text is changed, in which case you set the minimum width to 0, autosize just the modified column, and then set the minimum width to the current width again.
EDIT: My apologies, I forgot that I was not using the standard listview, but instead the 3rd party product BetterListView (a free version is available). The standard listview columns don't appear to support minimum width. I do recommend BetterListView highly as a great alternative (much better feature set and performance).

Windows Forms Top Property Bug

I am trying to populate a container with any number of controls that have the same height and width. I allow this container to be shrunk or grown by the user and the container will organize the controls so that it fits the most controls on one row as possible. Here is the code to organize it:
int row = 0;
int column = 0;
for (int i = 1; i <= controls.Count; i++)
{
controls.Values[i-1].Top = row * controls.Values[0].Height;
controls.Values[i-1].Left = column * controls.Values[0].Width;
if (i % controlsPerRow == 0)
{
// This finishes a row
row++;
column = 0;
}
else
{
column++;
}
}
The problem i run into is that on the first iteration of the loop, I will be multiplying the control height by the row and assigning that value to the control Top property. The first row is 0 and the first height is 165. 0 * 165 = 0, but the Top property contains a magical -20 after assigning the 0.
Anyone have any idea how this can happen?
You're trying to rewrite the FlowLayoutPanel.
Consider using it instead.
Also, it looks like your controls field is a Dictionary<Something, Control>.
Be aware that the iteration order of Dictionary.Values is not guaranteed, meaning that you aren't looping over the controls in the order that they were added to the dictionary.

Categories