NPOI - Adding cell comment to the left of a cell - c#

I am currently using this code to show a comment for a given cell :
public static void AddCellComment(ICell cell, IDrawing patr)
{
var commentString = "Something";
var anchor = new XSSFClientAnchor
{
Col1 = cell.ColumnIndex,
Col2 = cell.ColumnIndex + 2,
Row1 = cell.RowIndex,
Row2 = cell.RowIndex + 1
};
var comment = patr.CreateCellComment(anchor);
comment.String = new XSSFRichTextString(commentString);
cell.CellComment = comment;
}
This successfully creates a 2 column by 1 row wide comment for a given cell to the right of the cell. I tried putting cell.ColumnIndex - 2 in Col1 or Col2 and it either results in a corrupted workbook where none of the comments work or in an invisible comment. Is there a way to have the comment display to the left of the cell?

All right, so here are the results of my experiments:
First off, if you hope you can precisely define the position of the comment when its owning cell is hovered, you'll be disappointed.
This is impossible. It is simply not supported by Excel.
When hovering a cell that has a comment, Excel decides by itself where to place the comment and seems to always show it on the right of the cell. You can try to move the comment around in edition mode, it will still be displayed at the same place when the cell is hovered. (I experienced it myself and had confirmation in this tutorial link.
Then, knowing that, what you have control upon is:
The size of the comment box.
Its location, but only when in edition mode.
Both characteristics are governed by XSSFClientAnchor properties. As per npoi source code, Col1, Row1 and Col2, Row2 define two cells that in turn represent the area (and location in edition mode only) of the comment: cell #1 will be included in the area whereas, cell #2 will not.
The first cell must be the top-left of the comment area (precisely, the top-left of cell #1 will be the top-left of the comment box)
The second one must be the bottom-right of the area (precisely, the top-left of cell #2 will be the bottom-right of the comment box)
I think this rule explains why some of your attempts ended with weird or empty comments (I reproduced some of those too): given the above rule, you must always have: Col2 > Col1 and Row2 > Row1. Although I didn't test it, I also suspect that (absolutely) negative columns or rows wouldn't work, hence when subtracting values from the input cell's column or row, you should make sure the result does not end up < 0...
One last note concerning XSSFClientAnchor: there are 4 other properties that can help you fine tune the size and (edition mode only) placement of the comment: Dx1, Dy1, Dx2 and Dy2: these four properties allow you to add/subtract some size to both cells x and y coordinates. They are expressed in a weird unit: EMU. You can fit 9525 EMU in a pixel.
With all this knowledge, I crafted a simple test (based on a mix of yours and an npoi tutorial). Here it is:
private static void Main()
{
var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet("My sheet");
var row = sheet.CreateRow(10);
var cell = row.CreateCell(10);
cell.SetCellValue("Here");
var patr = sheet.CreateDrawingPatriarch();
AddCellComment(cell, patr);
using var stream = new FileStream(#"c:\temp\test.xlsx", FileMode.Create, FileAccess.Write);
workbook.Write(stream);
}
private static int PixelsToEmus(int pixels) => pixels * Units.EMU_PER_PIXEL;
private static void AddCellComment(ICell cell, IDrawing patr)
{
// Let's make a 3x2 cells comment area, then tweak it a bit
var anchor = new XSSFClientAnchor
{
// Top left cell
Col1 = 5, // 6th column
Row1 = 5, // 6th row
// Bottom right cell
Col2 = 8, // 3 cells wide
Row2 = 7, // 2 cells high
// Top left shift
Dx1 = PixelsToEmus(10), // 10 pixels to the left of 6th column's left border
Dy1 = PixelsToEmus(10), // 10 pixels to the bottom of 6th row's top border
// Bottom right shift
Dx2 = PixelsToEmus(30), // 30-10=20 pixels wider than 3 columns
Dy2 = PixelsToEmus(10), // exactly as high as 2 rows
};
var comment = patr.CreateCellComment(anchor);
comment.String = new XSSFRichTextString("Something");
cell.CellComment = comment;
}
When running this, I end up with these results (showing both the hovered placement and the edition mode placement):
To be complete, I double-checked what was written in the resulting xslx (after having unzipped it, i took a look at test\xl\drawings\vmlDrawing1.vml and inparticular the <x:Anchor> tag of the Note object in whichwe find the exact values we set in the program:
<x:Anchor>5, 10, 5, 10, 8, 30, 7, 10</x:Anchor>
The source code of npoi helped me (and hopefully you) understand how averything was working:
XSSFClientAnchor class
XSSFDrawing.CreateCellComment method
PS: For these tests, I used a .NET Core 3.1 app and NPOI v2.5.2 Nuget package

Related

C# MS Word Interoperability - how to stretch-fill last column in table

I am using Microsoft.Office.Interop.Word in a WinForms application written in c# to create a word document programmatically. I need to create a table with the first two columns having a predefined fixed width and the third (last) column stretched to fill the remaining width of the useable page (if I do not specify any widths, by default, the table would stretch itself to fill the useable width of the page and all 3 columns would be resized equally, which is not what I need).
I can preset the size of the first two columns like this:
myTable.Columns[1].Width = 130;
myTable.Columns[2].Width = 70;
That part is straight forward and works ok. But I couldn't work out how to adjust the third column to stretch-fill the rest of the page width. Here's my last attempt which resulted in the third column extending way beyond the edge of the page:
myTable.Columns[3].Width = wordApp.UsableWidth - myTable.Columns[1].Width - myTable.Columns[2].Width;
I also tried not specifying a value for the width of the 3rd column hoping that it would take up the rest of the width of the table by default - but instead, the 3rd column was adjusted to some seemingly arbitrary value (with the content of cells on this column wrapped to fit) and the overall width of the table shrank to fit all 3 columns which was less than the useable width of the page.
I tried playing with different values for the following table attributes without success:
myTable.AllowAutoFit = true;
myTable.PreferredWidth = wordApp.UsableWidth;
If anyone could help, that would be greatly appreciated. Thank you.
I included the relevant block of code if this helps in anyway:
Microsoft.Office.Interop.Word.Table myTable = wordDoc.Tables.Add(paraTitle.Range, 2, 3, ref missing, ref missing);
myTable.Borders.Enable = 1;
myTable.AllowAutoFit = true;
myTable.PreferredWidth = wordApp.UsableWidth;
//myTable.PreferredWidthType = WdPreferredWidthType.wdPreferredWidthAuto;
myTable.Columns[1].Width = 130;
myTable.Columns[2].Width = 70;
myTable.Columns[3].Width = wordApp.UsableWidth - myTable.Columns[1].Width - myTable.Columns[2].Width;
myTable.Rows[1].Cells[1].Range.Text = "Items returned by:";
myTable.Rows[1].Cells[1].Range.Bold = 1;
myTable.Rows[1].Cells[2].Range.Text = "Name";
myTable.Rows[1].Cells[2].Range.Bold = 1;
myTable.Rows[1].Cells[3].Range.Text = "(person's name and initials...)";
myTable.Rows[1].Cells[3].Range.Bold = 0;
myTable.Rows[2].Cells[2].Range.Text = "Date / Time";
myTable.Rows[2].Cells[2].Range.Bold = 1;
myTable.Rows[2].Cells[3].Range.Text = "(timestamp...)";
myTable.Rows[2].Cells[3].Range.Bold = 0;
UsableWidth refers to the Window width of the Word Application, not the document page width.
Instead use:
doc.PageSetup.PageWidth - (doc.PageSetup.LeftMargin + doc.PageSetup.RightMargin);

Autofit Row Height of Merged Cell in EPPlus

I'm using EPPlus and C# and trying to autosize/autofit the height of a row to accommodate the height needed to show all of the contents of a merged cell with text wrapping. However no matter what I try the text always truncates. Since I'm repeating this process with various text sizes on various worksheets, I don't want to hard code the row height (except to enforce a minimum height for the row). If possible I'd like to do this within EPPlus/C#.
With the cells A2:E2 merged and WrapText = true:
Cell with Text Truncated
Here's what it should look like with desired Cell Height
Here's my relevant and short C# code
Int32 intToCol;
intToCol = 5;
eppWorksheet.Cells[2, 1, 2, intToCol].Merge = true;
eppWorksheet.Cells[2, 1].Style.WrapText = true;
//Check if at the minimum height. If not, resize the row
if (eppWorksheet.Row(2).Height < 35.25)
{
eppWorksheet.Row(2).Height = 35.25;
}
I've looked at Autofit rows in EPPlus and it didn't seem to directly answer my question unless I'm reading it wrong.
Here is the solution in a reusable method. Pass in the text value, font used for the cell, summed width of the columns merged, and receive back the row height. Set the row height with the result.
Use of Method
eppWorksheet.Row(2).Height = MeasureTextHeight(cell.Value, cell.Style.Font, [enter the SUM of column widths A-E]);
Reuseable Method
public double MeasureTextHeight(string text, ExcelFont font, double width)
{
if (text.IsNullOrEmpty()) return 0.0;
var bitmap = _bitmap ?? (_bitmap = new Bitmap(1, 1));
var graphics = _graphics ?? (_graphics = Graphics.FromImage(bitmap));
var pixelWidth = Convert.ToInt32(width * 7); //7 pixels per excel column width
var fontSize = font.Size * 1.01f;
var drawingFont = new Font(font.Name, fontSize);
var size = graphics.MeasureString(text, drawingFont, pixelWidth, new StringFormat { FormatFlags = StringFormatFlags.MeasureTrailingSpaces });
//72 DPI and 96 points per inch. Excel height in points with max of 409 per Excel requirements.
return Math.Min(Convert.ToDouble(size.Height) * 72 / 96, 409);
}
I have used a workaround for this and I a had print area A:Q.
I copied merged cells value to column z.
set width of column z to merge cells width.
Then set auto row height true in format.
Hide the z column.
Set print area A:Q
Cons:
There are duplicate data. But we are okay since report is printing and not print z column.
Pros:
Row height works correctly not like calculation method.
Had to tweak the code a little bit by removing the multiplication factor at the return line. May be because i am using this code to get the width of the column.
ws1.Column(colIndx).Width * 7
The multiplication factor is the number of columns been merged.

C# DataGridView visual settings

I'm look for a way to make some changes to the DataGridview in C# shown in this picture:
It consists of two columns and in this case 6 rows.
It's supposed to be an checklist, you are reading: "Battery....ON" and so on.
To get the dots between the Left and right column, I'm simply adding many dot's after and in front of each string.
The Battery string looks like this:
"BATTERY...............................".
The "ON" string on the right column would look like this:
"..............ON"
As you can see, there is still a gap between the dots, how do I get rid of this?
CellBorderStyle ist set to:
checklist_dataGridView.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
Additionally, there is a slight height difference between the left and right column, this is the result of
checklist_dataGridView.Columns[1].DefaultCellStyle.WrapMode = DataGridViewTriState.True;
This is supposed to to make the right column text go from right to left.
Without this, the right column would only show "..........................."
Is there any better way to align everything properly?
Thanks for your help
Axel R
Edit:
I've solved the problem by making a single column and simply counting the width of the string. If the string has not reached the width of the column, there will be one dot added to the string. This works very well for me.
You could get close to the layout you want by using custom cell painting of the gridlines. You could custom paint the bottom gridlines as a green dotted line and skip adding the dots to the cell values. The only difference is that the lines would go across the entire grid horizontally.
First make sure the left column is bottom left aligned, and the right column is bottom right aligned:
checklist_dataGridView.Columns[0].DefaultCellStyle.Alignment = DataGridViewContentAlignment.BottomLeft;
checklist_dataGridView.Columns[0].DefaultCellStyle.ForeColor = System.Drawing.Color.LightGreen;
checklist_dataGridView.Columns[1].DefaultCellStyle.Alignment = DataGridViewContentAlignment.BottomRight;
checklist_dataGridView.Columns[1].DefaultCellStyle.ForeColor = System.Drawing.Color.LightGreen;
To custom paint the cells with a dotted green line on the bottom, you can implement a DataGridView CellPainting handler:
private void checklist_dataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex > -1 )
{
e.Handled = true;
e.Graphics.FillRectangle(System.Drawing.Brushes.Black, e.CellBounds);
using (Pen p = new Pen(Brushes.LightGreen))
{
p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawLine(p, new Point(0, e.CellBounds.Bottom - 1), new Point(e.CellBounds.Right, e.CellBounds.Bottom - 1));
}
e.PaintContent(e.ClipBounds);
}
}
Sorry... you can't get rid of the gap because the checklist_dataGridView.DefaultCellStyle.Padding = new Padding(0, 0, 0, 0); doesn't take negative values.
Now to make your original code work I will just add a space dot space dot space dot like " . . . . . . . . ."
This way you don't see just the dots on the cell and the text will get wrapped around. Also if you end with a dot on the left cell and start with a dot on the right cell then the gap will be close to the space distance and it will be less noticeable.
Don't forget this so your text wraps:
checklist_dataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
checklist_dataGridView.DefaultCellStyle.WrapMode = DataGridViewTriState.True;

How to add spikes to line on Winform chart?

I am drawing a line on a graph from numbers read from a text file. There is a number on each line of the file which corresponds to the X co-ordinate while the Y co-ordinate is the line it is on.
The requirements have now changed to include "special events" where if the number on the line is followed by the word special a spike will appear like image below:
Currently the only way I can find is to use a line for each spike, however there could be a large of these special events and so needs to be modular. This seems an efficient and bad way to program it.
Is it possible to add the spikes to the same graph line? Or is it possible to use just one additional line and have it broken (invisible) and only show where the spikes are meant to be seen?
I have looked at using bar graphs but due to other items on the graph I cannot.
The DataPoints of a Line Chart are connected so it is not possble to really break it apart. However each segment leading to a DataPoint can have its own color and that includes Color.Transparent which lends itself to a simple trick..
Without adding extra Series or Annotations, your two questions can be solved like this:
To simply add the 'spikes' you show us in the 2nd graph, all you need to do is to insert 2 suitable datapoints, the 2nd being identical to the point the spike is connected to.
To add an unconnected line you need to 'jump' to its beginning by adding one extra point with a transparent color.
Here are two example methods:
void addSpike(Series s, int index, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
s.Points.Insert(index+1, dp1);
s.Points.Insert(index+2, dp);
}
void addLine(Series s, int index, double spikeDist, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeDist, dp.YValues[0]);
DataPoint dp2 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
DataPoint dp0 = dp.Clone();
dp1.Color = Color.Transparent;
dp2.Color = dp.Color;
dp2.BorderWidth = 2; // optional
dp0.Color = Color.Transparent;
s.Points.Insert(index + 1, dp1);
s.Points.Insert(index + 2, dp2);
s.Points.Insert(index + 3, dp0);
}
You can call them like this:
addSpike(chart1.Series[0], 3, 50d);
addLine(chart1.Series[0], 6, 30d, 80d);
Note that they add 2 or 3 DataPoints to the Points collection!
Of course you can set the Color and width (aka BorderWidth) of the extra lines as you wish and also include them in the params list..
If you want to keep the points collection unchanged you also can simply create one 'spikes series' and add the spike points there. The trick is to 'jump' to the new points with a transparent line!

iTextSharp PdfPtable multipage

I am new to iTextSharp, and want to convert my DataGridView to a PdfPtable.
When i write the table to PDF, it shows up nicely, except when the amount of columns is relatively large. Then it accomodates by making the column width smaller.
Instead, I would like to start on a new page, with more of the table shown, with headers.
Below is my code for the table:
PdfPTable table = new PdfPTable( view.Columns.Count );
float[] widths = new float[view.Columns.Count];
for ( int i = 0; i < view.Columns.Count; i++ ) {
widths[i] = view.Columns[i].Width;
}
table.SetWidths( widths );
table.HorizontalAlignment = 1; //Center
PdfPCell cell = null;
//Headers
foreach ( DataGridViewColumn c in view.Columns ) {
cell = new PdfPCell( new Phrase( new Chunk( c.HeaderText, _standardFont ) ) );
cell.HorizontalAlignment = PdfPCell.ALIGN_CENTER;
cell.VerticalAlignment = PdfPCell.ALIGN_CENTER;
table.AddCell( cell );
}
//Rest of table
for ( int i = 1; i < view.Rows.Count; i++ ) {
for ( int j = 0; j < view.Columns.Count; j++ ) {
bool hasValue = view.Rows[i].Cells[j].Value == null ? false : true;
if ( hasValue ) {
cell = new PdfPCell( new Phrase( view.Rows[i].Cells[j].Value.ToString(), _standardFont ) );
} else {
cell = new PdfPCell( new Phrase( "", _standardFont ) );
}
cell.HorizontalAlignment = PdfPCell.ALIGN_CENTER;
cell.VerticalAlignment = PdfPCell.ALIGN_CENTER;
table.AddCell( cell );
}
}
One solution would be to somehow know how many columns could be printed fullsize on a page, and then add a new page, with more of the grid displayed there, but i am unsure how to detect a new page. The problem here is that there seems to be no way of changing the number of columns of a PdfPtable, so I have to be able to calculate the effect in width when adding columns beforehand.
EDIT: I want to split the pages vertically
The problem you're facing, is the fact that PDF isn't HTML. A page in a PDF has a fixed size. You don't have a scrollbar, and the content doesn't change when you resize the viewer window.
I'm going to answer using JAVA examples from Chapter 4 of my book. If you need the C# version of these examples, you'll need this URL: http://tinyurl.com/itextsharpIIA2C04
First this: whether or not a table looks nice is something a machine can't judge. It's something only a human can do.
Seems like you want to control the width of the columns. The example ColumnWidths gives you some examples on how to do that. In your case, you should use a solution that involves using absolute values for the width of the table. You'll recognize these solutions because they all have this line in common:
table.setLockedWidth(true);
Using an absolute width is mandatory if you want to calculate the height of each row or of the complete table. A Frequently Asked Question is "I've created a table and when I ask for its total width, it returns zero." It can be answered by a counter-question: "How can iText calculate the height of a table, if you didn't define the width first?" This is a rhetorical question: it's impossible to calculate the height before the width has been defined. That's shown in the TableHeight example.
Once you've defined the widths of the table (and its columns), you can calculate the height, and you're ready to position the table at absolute positions. The Zhang example shows how it's done. In this example, a table with 4 columns is created. The first two colums are drawn using this commando:
table.writeSelectedRows(0, 2, 0, -1, 236, 806, canvas);
Where the first 0 defines the first column that needs to be drawn, and the 2 defines the first column that isn't drawn. The remaining columns are drawn on the next page, using this commando:
table.writeSelectedRows(2, -1, 0, -1, 36, 806, canvas);
Where 2 defines the first column that needs to be drawn, and -1 indicates that all remaining columns should be drawn.
In this case, 0 and -1 are used for the rows. If your table contains more rows than fit on one page, you'll have to do the math to make sure you don't have any rows that "fall off the page."
Note that the 36 and 806 represent an X and a Y coordinate. As your taking control over the layout, you're responsible to calculate those coordinates.

Categories

Resources