Writing Images to Excel file using EPPlus - c#

I am trying to make the application generate an Excel file from a list of string type and a list of image type. I have the user enter a line then makes them take a screenshot and so on until G is pressed and then it should generate an Excel file with the format as so:
Row 1- Title-0-
Row 2- Header-1-
Row 3- Image-0-
Row 4- Header-2-
Row 5- Image-1-
Row 6- Header-3-
Row 7- Image-2-
Row 8- Header-4-
Row 9- Image-3-
Row 10- Header-5-
Row 11- Image-4-
...and so on until its done all in the collections.
I have created the List and List and I know that they both contain Strings and Images before I hit G as I have looked inspected the collections debug mode.
This is the code I have so far and the excel file looks right except there are no Images to be seen however it is re-sizing the rows to the pictures heights. I have never worked with Images before so think I could be missing something important but not sure what.
The Collections are passed into this method from a calling method
String collection is named "withHeadersList",
Image collection is named "withImgList".
Generate Excel Method:
public static bool GenerateTestPlan(List<String> withHeadersList, List<Image> withImgList, string stringOutputPath)
{
ExcelPackage newExcelPackage = CreateExcelPackage(withHeadersList[0]);
ExcelWorksheet newExcelWorksheet = CreateWorkSheet(newExcelPackage, "Sheet1");
SetCellValue(newExcelWorksheet, 1, 1, withHeadersList[0]); //Title
newExcelWorksheet.Row(1).Style.Font.Size = 35;
newExcelWorksheet.Row(1).Style.Font.Bold = true;
int pRowIndex = 3;
int hRowIndex = 2;
for (int i = 1; i < withHeadersList.Count; i++)
{
SetCellValue(newExcelWorksheet, hRowIndex, 1, withHeadersList[i]);
newExcelWorksheet.Row(hRowIndex).Style.Font.Size = 20;
newExcelWorksheet.Row(pRowIndex).Height = withImgList[i - 1].Height; //Set row height to height of screenshot
var img = newExcelWorksheet.Drawings.AddPicture(withHeadersList[i], withImgList[i - 1]); //Add Images (THINK THIS LAST PARAMETER IS THE PROBLEM)
img.SetPosition(pRowIndex, Pixel2MTU(2), 1, Pixel2MTU(2));
img.SetSize(withImgList[i - 1].Width, withImgList[i - 1].Height);
hRowIndex += 2;
pRowIndex += 2;
}
SaveExcelPackage(newExcelPackage, stringOutputPath);
return true;
}
Excel File here
As you see it's like the images are just not being rendered.

Your issue is most certainly with this line:
img.SetPosition(pRowIndex, Pixel2MTU(2), 1, Pixel2MTU(2));
I'm not sure why you are converting pixels to anything considering SetPosition is looking for the offset in pixels. From the metadata:
// Summary:
// Set the top left corner of a drawing. Note that resizing columns / rows after
// using this function will effect the position of the drawing
//
// Parameters:
// Row:
// Start row
//
// RowOffsetPixels:
// Offset in pixels
//
// Column:
// Start Column
//
// ColumnOffsetPixels:
// Offset in pixels
public void SetPosition(int Row, int RowOffsetPixels, int Column, int ColumnOffsetPixels);
I would recommend just passing through small values, such as 2, for the RowOffestPixels and ColumnOffsetPixels parameters:
img.SetPosition(pRowIndex, 2, 1, 2);
I found a method called Pixel2MTU(int pixels) on codeproject from a quick google search. The method is as follows:
public int Pixel2MTU(int pixels)
{
int mtus = pixels * 9525;
return mtus;
}
If this is the same method you are using, your images might be at the very far bottom right of your excel document.

Related

C# PDF table add row in wrong position

[EDIT due to misunderstanding of the answer]
I'm doing a simple program in C# with PDF file creation with iText7.
In this PDF i'm adding a table whose first cell starts at a certain position in the file.
I don't know if I set the position correctly, but everytime I add another cell with tab.StartNewRow() the resulting new table is repositioned taking THAT last cell as position reference, putting the previously added cells from that point up, while I want to add the cells from that point down.
Which method should I use? That's my code:
Previously I set the position of the first table cell using tab1.SetFixedPosition(20, heigh, width);
and then, in order to add the other cells:
if (mylistbox.Items.Count > 0)
{
tab1.AddCell("FIRST CELL");
tab1.StartNewRow();
for (int i = 0; i < mylistbox.Items.Count; i++)
{
tab1.AddCell(mylistbox.Items[i].ToString());
tab1.StartNewRow();
}
doc.Add(tab1);
}
[EDIT #2] in order to explain my issue better
I have to put 5 tables, which have to grow from a certain point DOWN, positioned at equal distances, same height and width in the doc. This image explains how it should result:
In a WPF application, I have a ListBox with 5 items, numbered 1 through 5. This should be very similar to WinForms.
The CreatePercentArray takes a size which is equal to the amount of columns in a row.
An interesting article about tables: link
private void CreateListBoxTable(Document pdfDoc)
{
// Create an array where each item has an equal width, and use the entire pdf width
// The CreatePercentArray takes a size which is equal to the amount of columns in a row
// By using percentages, they will automatically adapt
// Use CreatePointArray for exacter measurements
var table = new Table(UnitValue.CreatePercentArray(2)).UseAllAvailableWidth();
if (!MyListBox.Items.IsEmpty)
{
foreach (var listBoxItem in MyListBox.Items)
{
table.AddCell(((ListBoxItem) listBoxItem).Content.ToString());
}
}
// Adds table to document
pdfDoc.Add(table);
// Closes document
pdfDoc.Close();
}

C# WindowsForm Add DatagridView Content into PowerPoint's slide

I'm working wth .NET 4.7.2, Windowsform.
I have a datagridview and I manage to generate a powerpoint file pptx.
I made a first ppt slide and I'd like to add the datagridview content into the second ppt slide given that I need to have the option to change the data within the PPt slide.
Microsoft.Office.Interop.PowerPoint.Application pptApp = new Microsoft.Office.Interop.PowerPoint.Application();
pptApp.Visible = Microsoft.Office.Core.MsoTriState.msoTrue;
Microsoft.Office.Interop.PowerPoint.Slides slides;
Microsoft.Office.Interop.PowerPoint._Slide slide;
Microsoft.Office.Interop.PowerPoint._Slide slide2;
Microsoft.Office.Interop.PowerPoint.TextRange objText;
// Create File
Presentation pptPresentation = pptApp.Presentations.Add(Microsoft.Office.Core.MsoTriState.msoTrue);
CustomLayout customLayout = pptPresentation.SlideMaster.CustomLayouts[PpSlideLayout.ppLayoutText];
// new Slide
slides = pptPresentation.Slides;
slide = slides.AddSlide(1, customLayout);
slide2 = slides.AddSlide(1, customLayout);
// title
objText = slide.Shapes[1].TextFrame.TextRange;
objText.Text = "Bonds Screner Report";
objText.Font.Name = "Haboro Contrast Ext Light";
objText.Font.Size = 32;
Shape shape1 = slide.Shapes[2];
slide.Shapes.AddPicture("C:\\mylogo.png", Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue, shape1.Left, shape1.Top, shape1.Width, shape1.Height);
slide.NotesPage.Shapes[2].TextFrame.TextRange.Text = "Disclaimer";
dataGridViewBonds.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
dataGridViewBonds.SelectAll();
DataObject obj = dataGridViewBonds.GetClipboardContent();
Clipboard.SetDataObject(obj, true);
Shape shapegrid = slide2.Shapes[2];
I know I'm not so far by now but I miss smething. Any help would be appreciated !
I am familiar with Excel interop and have used it many times and most likely have become numb to the awkward ways in which interop works. Using PowerPoint interop can be very frustrating for numerous reasons, however, the biggest I feel is the lack of documentation and the differences between the different MS versions.
In addition, I looked for a third-party PowerPoint library and “Aspose” looked like the only option, unfortunately it is not a “free” option. I will assume there is a free third-party option and I just did not look in the right place… Or there may be a totally different way to do this possibly with XML. I am confident I am preaching to the choir.
Therefore, what I have been able to put together may work for you. For starters, looking at your current posted code, there is one part missing that you need to get the “copied” grid cells into the slide…
slide.Shapes.Paste();
This will paste the “copied” cells from the grid into an “unformatted” table into the slide. This will copy the “row header” if it is displayed in the grid in addition to the “new row” if the grids AllowUserToAddRows is set to true. If this “unformatted paste” works for you, then you are good to go.
If you prefer to have at least a minimally formatted table and ignore the row headers and last empty row… It may be easier to simply “create” a new Table in the slide with the size we want along with the correct number of rows and columns. Granted, this may be more work, however, using the paste is going require this anyway “IF” you want the table formatted.
The method (below) takes a power point _Slide and a DataGridView. The code “creates” a new Table in the slide based on the number of rows and columns in the given grid. With this approach, the table will be “formatted” using the default “Table Style” in the presentation. So, this may give you the formatting you want by simply “creating” the table as opposed to “pasting” the table.
I have tried to “apply” one of the existing “Table Styles” in power point, however, the examples I saw used something like…
table.ApplyStyle("{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}");
Which uses a GUID id to identify “which” style to use. I am not sure why MS decided on this GUID approach… this is beyond me, and it worked for “some” styles but not all.
Also, more common-sense solutions that showed something like…
table.StylePreset = TableStylePreset.MediumStyle2Accent2;
Unfortunately using my 2019 version of Office PowerPoint, this property does not exist. I have abandoned further research on this as it appears to be version dependent. Very annoying!
Given this, it may be easier if we format the cells individually as we want. We will need to add the cells text from the grid into the individual cells anyway, so we could also format the individual cells at the same time. Again, I am confident there is a better way, however, I could not find one.
Below the InsertTableIntoSlide(_Slide slide, DataGridView dgv) method takes a slide and a grid as parameters. It will add a table to the slide with data from the given grid. A brief code trace is below.
First a check is made to get the number of total rows in the grid (not including the headers) totRows. If the grids AllowUsersToAddRows is true, then the total rows variable is decremented by 1 to ignore this new row. Next the number of columns in the grid is set to the variable totCols. The top left X and Y point is defined topLeftX and topLeftY to position the table in the slide along with the tables width and height.
ADDED NOTE: Using the AllowUserToAddRows property to determine the number of rows … may NOT work as described above and will “miss” the last row… “IF” AllowUserToAddRows is true (default) AND the grid is data bound to a data source that does NOT allow new rows to be added. In that case you do NOT want to decrement the totRows variable.
Next a “Table” “Shape” is added to the slide using the previous variables to define the base table dimensions. Next are two loops. The first loop adds the header cells to the first row in the table. Then a second loop to add the data from the cells in the grid… to the table cells in the slide.
The commented-out code is left as an example such that you want to do some specific formatting for the individual cells. This was not need in my case since the “default” table style was close to the formatting I wanted.
Also, a note that “ForeColor” is the “Back ground” color of the cell/shape. Strange!
I hope this helps and again, sympathize more about having to use PowerPoint interop… I could not.
private void InsertTableIntoSlide(_Slide slide, DataGridView dgv) {
try {
int totRows;
if (dgv.AllowUserToAddRows) {
totRows = dgv.Rows.Count - 1;
}
else {
totRows = dgv.Rows.Count;
}
int totCols = dgv.Columns.Count;
int topLeftX = 10;
int topLeftY = 10;
int width = 400;
int height = 100;
// add extra row for header row
Shape shape = slide.Shapes.AddTable(totRows + 1, totCols, topLeftX, topLeftY, width, height);
Table table = shape.Table;
for (int i = 0; i < dgv.Columns.Count; i++) {
table.Cell(1, i+1).Shape.TextFrame.TextRange.Text = dgv.Columns[i].HeaderText;
//table.Cell(1, i+1).Shape.Fill.ForeColor.RGB = ColorTranslator.ToOle(Color.Blue);
//table.Cell(1, i+1).Shape.TextFrame.TextRange.Font.Bold = Microsoft.Office.Core.MsoTriState.msoTrue;
//table.Cell(1, i+1).Shape.TextFrame.TextRange.Font.Color.RGB = ColorTranslator.ToOle(Color.White);
}
int curRow = 2;
for (int i = 0; i < totRows; i++) {
for (int j = 0; j < totCols; j++) {
if (dgv.Rows[i].Cells[j].Value != null) {
table.Cell(curRow, j + 1).Shape.TextFrame.TextRange.Text = dgv.Rows[i].Cells[j].Value.ToString();
//table.Cell(curRow, j + 1).Shape.Fill.ForeColor.RGB = ColorTranslator.ToOle(Color.LightGreen);
//table.Cell(curRow, j + 1).Shape.TextFrame.TextRange.Font.Bold = Microsoft.Office.Core.MsoTriState.msoTrue;
//table.Cell(curRow, j + 1).Shape.TextFrame.TextRange.Font.Color.RGB = ColorTranslator.ToOle(Color.Black);
}
}
curRow++;
}
}
catch (Exception ex) {
MessageBox.Show("Error: " + ex.Message);
}
}

Autofit text in merged rows in Excel interop objects. C#

I have a method that writes data in excel, but there are parts that have a lot of text and the method wrap the text into the column but I can´t do it in the rows because they are merged. So i get something like this:
And I need to get it like This:
Is there a way to do it by code? Using Interop Objects. Thanks for any help.
I don't know whether this is correct or not, but I've come up with the following solution. The idea is the following:
remember rows count
calculate total height of all rows in merged area
calculate percentage of every row according to total height
unmerge cells
autofit rows
remember the height of first row (i.e. data row) - a new height
main: apply percentage (on stage 3) to new height
merge cells back (with the help of rows count on stage 1)
As you can see, this method is universal - i.e. it will work on any rows count in merged area and it will honor the previous ratio of each row according to new height. You can download sample workbook with code.
VBA
Sub Test()
Call AutoFitMergedCells(Range("D11"))
End Sub
Sub AutoFitMergedCells(cell As Range)
Dim dOldHeight#, dNewHeight#, dPercent#, arow, addr, rows_count
Dim dicCells As New Dictionary, dicHeights As New Dictionary
'// turn off flickering
Application.ScreenUpdating = False
With cell
'// remember rows count for merging cells back
rows_count = .MergeArea.Count
'// every dictionary entry holds following info:
'// 1) original height of all rows in merged cells
'// 2) percentage of row's height to height of all rows in merged area
For Each arow In .MergeArea.Rows
With arow.Cells(1)
Set dicHeights = New Dictionary
dicHeights("height") = .RowHeight
dicHeights("percent") = 0
dicCells.Add Key:=.Address(0, 0), Item:=dicHeights
End With
Next
'// total height of all rows
For Each addr In dicCells.Keys()
dOldHeight = dOldHeight + dicCells(addr)("height")
Next
'// update the percentage of every row
For Each addr In dicCells.Keys()
dicCells(addr)("percent") = dicCells(addr)("height") / dOldHeight
Next
.UnMerge
.EntireRow.AutoFit
'// remember new height
dNewHeight = .RowHeight
'// this applies percentage of previous row's height to new height
For Each addr In dicCells.Keys()
Range(addr).EntireRow.RowHeight = dicCells(addr)("percent") * dNewHeight
Next
'// merge back
.Resize(rows_count).Merge
End With
Application.ScreenUpdating = True
End Sub
UPDATE
C#
using System.Diagnostics;
using Excel = Microsoft.Office.Interop.Excel;
private void ProcessMergedCells()
{
var xlApp = new Excel.Application { Visible = false, ScreenUpdating = false };
// get Excel process in order to kill it after the work is done
var xlHandle = new IntPtr(xlApp.Hwnd);
var xlProc = Process
.GetProcesses()
.First(p => p.MainWindowHandle == xlHandle);
var book = xlApp.Workbooks.Open(#"C:\AutoFitMergedCells.xlsm");
var sheet = book.Sheets[1] as Excel.Worksheet;
// obtain merged cells any way you like
// here I just populate array with arbitrary cells
var merged_ranges = new Excel.Range[]
{
sheet.Range["D11"],
sheet.Range["D13"]
};
// process merged cells
foreach(var merged_range in merged_ranges)
{
AutoFitMergedCells(merged_range);
}
// quit with saving
book.Close(SaveChanges: true);
xlApp.Quit();
// clean up
GC.Collect();
GC.WaitForFullGCComplete();
// kill Excel for sure
xlProc.Kill();
}
private void AutoFitMergedCells(Excel.Range merged_range)
{
double dOldHeight = 0d, dNewHeight = 0d;
var dicCells = new Dictionary<string, Dictionary<string, double>>();
// remember rows count for merging cells back
int rows_count = merged_range.MergeArea.Count;
// every dictionary entry holds following info:
// 1) original height of all rows in merged cells
// 2) percentage of row's height to height of all rows in merged area
foreach (Excel.Range the_row in merged_range.MergeArea.Rows)
{
// we need only top-left cell
var first_cell = the_row.Cells[1];
var dicHeights = new Dictionary<string, double>
{
["height"] = first_cell.RowHeight,
["percent"] = 0d
};
dicCells[first_cell.Address[0, 0]] = dicHeights;
}
// total height of all rows
foreach (string addr in dicCells.Keys)
dOldHeight += dicCells[addr]["height"];
// update the percentage of every row
foreach (string addr in dicCells.Keys)
dicCells[addr]["percent"] = dicCells[addr]["height"] / dOldHeight;
// unmerge range and autofit
merged_range.UnMerge();
merged_range.EntireRow.AutoFit();
// remember new height
dNewHeight = merged_range.RowHeight;
// this applies percentage of previous row's height to new height
var sheet = merged_range.Parent;
foreach (string addr in dicCells.Keys)
sheet.Range[addr].EntireRow.RowHeight = dicCells[addr]["percent"] * dNewHeight;
// merge back
merged_range.Resize[rows_count].Merge();
}
I don't recall where I saw this, but I once saw a hack to accomplish this. In a nutshell, you take the text in question from the merged cell, paste it into a non-merged cell in the same column and do an autofit on that one cell to see how tall it should be. Then you take that height of the safe cell and manually set it to each of the merged rows, dividing by the total number of rows that were merged.
It's ugly, but it does work. In your example, if it's always two rows, that makes it a lot easier because you can always divide by two and increment the row count by two. If not, you just need to factor that in. Assuming C1 is our "safe cell," it would looks something like this:
Excel.Range testCell = ws.Cells[1, 3];
testCell.WrapText = true;
for (int row = 11; row < ws.UsedRange.Rows.Count; row += 2)
{
testCell.Value2 = ws.Cells[row, 4].Value2;
testCell.Rows.AutoFit();
ws.Range[string.Format("{0}:{1}", row, row + 1)].RowHeight = testCell.RowHeight / 2;
}

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).

Only the last row is displaying on my DataGridView

The following code is a method that I am calling every time I want to add a row to my tableDocTypes DataGridView. The debug shows that the RowCount is incrementing correctly and all the data passed is correct but once the form displays it only shows the last row. Also the last row that displays is in the row that should be white space so I can't add any new rows since there is no blank cell next to the star. My guess is that I shouldn’t be using tableDocTypes.RowCount - 1 as an index but it appears to be incrementing correctly and I can't find anything else to use. Any information would be helpful.
private void addRowToDocTypeTable(bool docTypeValid, string formName, string formCabinet, string docType)
{
tableDocTypes.Rows.Add();
if (!docTypeValid)
{
// Change color to yellow and display IndexWarning icons
tableDocTypes.Rows[tableDocTypes.RowCount - 1].Cells[columnDocTypeImage.Index].Value = Properties.Resources.IndexWarning;
tableDocTypes.Rows[tableDocTypes.RowCount - 1].Cells[columnDocType.Index].Style.BackColor = Color.LightGoldenrodYellow;
}
else
{
// Index is good so display IndexCorrect and leave line white
tableDocTypes.Rows[tableDocTypes.RowCount - 1].Cells[columnDocTypeImage.Index].Value = Properties.Resources.IndexCorrect;
}
tableDocTypes.Rows[tableDocTypes.RowCount - 1].Cells[columnFormName.Index].Value = formName;
tableDocTypes.Rows[tableDocTypes.RowCount - 1].Cells[columnFormCabinet.Index].Value = formCabinet;
tableDocTypes.Rows[tableDocTypes.RowCount - 1].Cells[columnDocType.Index].Value = docType;
// Assign the rename menue to only the image column
tableDocTypes.Rows[tableDocTypes.RowCount - 1].Cells[columnDocTypeImage.Index].ContextMenuStrip = menuDocTypeEdit;
}

Categories