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;
Related
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
I want an aligned string across multiple rows in a right-click menu in excel. The right click menu is added like this:
CommandBar contextMenu = Globals.ThisWorkbook.Application.CommandBars["List Range Popup"];
CommandBarPopup subMenu = (CommandBarPopup)contextMenu.Controls.Add(Type: MsoControlType.msoControlPopup, Before: 1, Temporary: true);
subMenu.Caption = "Assumptions Drill Down";
I then iterate database results and add to the subMenu in the proceeding rows
Current result as below (using string padding):
As the character spaces are not even (non-monospaced font) the columns are not aligned. I've tried to do something like the below to get the pixel size of the string and then covert back to the required string padding integer but I cant get it to work:
private static double GetStringSize(string stringText)
{
Bitmap b = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(b);
SizeF size = g.MeasureString(stringText,
new System.Drawing.Font("Calibri", 10, FontStyle.Regular, GraphicsUnit.Point));
return size.Width;
}
Is there a way to set font on a right click to monospaced (i dont believe so)
If I cant do (1.) how can I make sure spacing in each column is even - so for example evenly align a user of 'iiiii' vs 'QQQQQ'
?
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!
Could somebody tell me how to add borders around the outside of a range of cells in another colour? Ideally I would like to be able to do this with a single method I will have to do this multiple times. After searching for this I found two methods that would apparently do this - BorderAround and BorderAround2. I suppose my first question is what is the difference between these two methods? I tried using each of these and only BorderAround2 was recognised?
Anyway, `BorderAround2' almost does what I wanted. I used the following line of code which did put a border around the outside of the range, but it was black, rather than red:
ws.get_Range("B2", "E3").BorderAround2(Excel.XlLineStyle.xlContinuous, Excel.XlBorderWeight.xlThick, Excel.XlColorIndex.xlColorIndexNone, Color.FromArgb(255, 0, 0), Type.Missing);
The MSDN documentation for this method states:
You must specify either ColorIndex or Color, but not both.
How do I go about doing this? If I set the ColourIndex parameter to Type.Missing or to null or miss it out completely, it produces an error. Any help would be appreciated.
Finally I should point out that I found a workaround solution here where you set the set the various edges separately, but as I say, I was hoping to do this using a single method as it has to be repeated multiple times.
To add a border to one or more sides of an Excel Range (range of cells, which can normally be comprised of 1..many rows and 1..many columns, but for this specific scenario, we probably want to stick with one row and 1..many columns), you only need do three things:
0) Define the range
1) Get a reference to the Range's Borders array
2) Assign a border to one or more of the Border array's edges/sides (top, bottom, left, right)
First, define the range over which you want to operate on like so:
var rowToBottomBorderizeRange = _xlSheet.Range[_xlSheet.Cells[rowToBottomBorderize, ITEMDESC_COL], _xlSheet.Cells[rowToBottomBorderize, TOTALS_COL]];
Next, obtain a reference to the Range's Borders array like this:
Borders border = rowToBottomBorderizeRange.Borders;
Finally, assign a border to one or more of the Border array's edges; for example, if you want to add a boder to the bottom, like so:
border[XlBordersIndex.xlEdgeBottom].LineStyle = XlLineStyle.xlContinuous;
Putting it all together, the code could be:
var rowToBottomBorderizeRange = _xlSheet.Range[_xlSheet.Cells[rowToBottomBorderize, ITEMDESC_COL], _xlSheet.Cells[rowToBottomBorderize, TOTALS_COL]];
Borders border = rowToBottomBorderizeRange.Borders;
border[XlBordersIndex.xlEdgeBottom].LineStyle = XlLineStyle.xlContinuous;
If you do this in several places, you could make a method out of it:
private void AddBottomBorder(int rowToBottomBorderize)
{
var rowToBottomBorderizeRange = _xlSheet.Range[_xlSheet.Cells[rowToBottomBorderize, ITEMDESC_COL], _xlSheet.Cells[rowToBottomBorderize, TOTALS_COL]];
Borders border = rowToBottomBorderizeRange.Borders;
border[XlBordersIndex.xlEdgeBottom].LineStyle = XlLineStyle.xlContinuous;
}
The example above shows just adding a bottom border, but you can add top, left, or right border lines just as easily, replacing "xlEdgeBottom" with "xlEdgeTop", "xlEdgeRight", or "xlEdgeLeft"
Or, you could add borders all around a range like this:
private void Add360Borders(int rowToBorderize)
{
var rowToBottomBorderizeRange = _xlSheet.Range[_xlSheet.Cells[rowToBorderize, ITEMDESC_COL], _xlSheet.Cells[rowToBorderize, TOTALS_COL]];
Borders border = rowToBorderizeRange.Borders;
border[XlBordersIndex.xlEdgeBottom].LineStyle = XlLineStyle.xlContinuous;
border[XlBordersIndex.xlEdgeTop].LineStyle = XlLineStyle.xlContinuous;
border[XlBordersIndex.xlEdgeLeft].LineStyle = XlLineStyle.xlContinuous;
border[XlBordersIndex.xlEdgeRight].LineStyle = XlLineStyle.xlContinuous;
}
Note: You will need to define the sheet like this:
private Worksheet _xlSheet;
...and reference the Microsoft.Office.Interop.Excel assembly in your solution.
Try:
ws.get_Range("B2", "E3").Borders.LineStyle = Excel.XlLineStyle.xlContinuous;
ws.get_Range("B2", "E3").Borders.Color = ColorTranslator.ToOle(Color.Red);
I have already set the DataGridView.DefaultCellStyle.WrapMode = DataGridViewTriState.True
But this WrapMode does not wrap columns with single word without spaces. Is there any way we can "break-word" along with WrapMode? Or any other solution?
You can play with the CellPainting event.
The DrawString respects the bounding Rectangle and wraps wherever it hits the right boundary.
You can uncomment the condition to apply only to cells which go over a limit you set.
For best control you would have to measure the length of the FormattedValue to find out the exact limit.
You may also want to fine-tune the draw position, if you have special alignments in your cells.
private void DGV1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.Value == null) return;
if (e.FormattedValue.GetType() != typeof( System.String) ) return;
bool selected = (e.State & DataGridViewElementStates.Selected)
== DataGridViewElementStates.Selected;
string s = e.FormattedValue.ToString();
//if (s.Length > 20) // Apply to all or only those breaking your limits
{
e.PaintBackground(e.CellBounds, selected);
e.Graphics.DrawString(s, DGV1.Font, selected ?
SystemBrushes.HighlightText : SystemBrushes.ControlText,
new Rectangle(e.CellBounds.X + 1, e.CellBounds.Y + 2,
e.CellBounds.Width - 2, e.CellBounds.Height - 4));
e.Handled = true;
}
}
Setting the Row.Heights is up to you. If you go for measuring the FormattedValue you will get a RectangleF back; so you'll also know the necessary Height for that Cell. Comparing it to the current Row.Height you could gradually adapt it for each Row, i.e. make it larger each time it is necessary.. I didn't include, because it will result in Rows with varying Heights and that may be unwanted/unnecessary in your case. If you're interested, I can post the code, though..
hi Shimply Add following Line after DataBinding
DGLogs.Columns[0].DefaultCellStyle.WrapMode=DataGridViewTriState.True;
set
AutosizecolumnMode=AllCells
AutosizeRowMode=AllCells
in Property windows
Output will be as hown belo Image