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);
Related
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 created a column chart in my application which look like this:
As you can see the positive values are green and the negative values are red. I need to represent this in the legend. I just don't know how.
What I already tried:
I added CustomItems to the Legend. Here is the code:
Legend currentLegend = chart.Legends.FindByName(chart.Series[series].Legend);
if (currentLegend != null)
{
currentLegend.LegendStyle = LegendStyle.Table;
LegendItem li = new LegendItem();
li.Name = series;
li.Color = Color.Red;
li.BorderColor = Color.Transparent;
currentLegend.CustomItems.Add(li);
}
This results in the following representation:
I could live with that. But as soon as I add further series to the chart the order of the elements gets destroyed. Here is an example:
I would like to have one of the two options:
keep the positive and negative color together
or an even better solution could be to have just one tile in the legend which is double colored. Something like this:
Could you please help me solving this issue?
Many thanks in advance!
Yes, you can do that. Note however that you can't really modify the original Legend. So for a perfect result you would need to create a new custom Legend instead.
See here for an example that does that; note especially the positioning..!
But maybe you can get away a little easier; see below!
The first rule to understand is that added LegendItems always go to the end of the list. So you can't keep them together, unless your added Series are at the start. You can do that by using Series.Insert(..) but using those two-color rectangles is much nicer, imo..
To show the graphics you want, simply create them as bitmaps, either on disk or on the fly and store them in the Images collection of the chart:
Legend L = chart1.Legends[0];
Series S = chart1.Series[0];
// either load an image from disk (or resources)
Image img = Image.FromFile(someImage);
// or create it on the fly:
Bitmap bmp = new Bitmap(32, 14);
using (Graphics G = Graphics.FromImage(bmp))
{
G.Clear(Color.Red);
G.FillPolygon(Brushes.LimeGreen, new Point[] { new Point(0,0),
new Point(32,0), new Point(0,14)});
}
Now add it to the chart's NamedImage collection:
chart1.Images.Add(new NamedImage("dia", bmp));
Now you can create as many LegendItems as you need:
LegendItem newItem = new LegendItem();
newItem.ImageStyle = LegendImageStyle.Rectangle;
newItem.Cells.Add(LegendCellType.Image, "dia", ContentAlignment.MiddleLeft);
newItem.Cells.Add(LegendCellType.Text, S.Name, ContentAlignment.MiddleLeft);
And add them to the Legend:
L.CustomItems.Add(newItem);
Unfortunately you can't delete the original item.
What you can do, besides creating a new Legend from scratch, is this:
Clear the text like this:
S.LegendText = " "; // blank, not empty!
As you have set the Colors of all the DataPoints anyway, you can also get rid of the blue rectangle:
S.Color = Color.Transparent;
This will also make all points without colors transparent, so make sure to color them all!
Note that some space in the Legend it still taken!
Here is the result, with a few colored points and your line series added:
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;
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!
I see from Microsoft's documentation that I can access the particular border edges of a cell using the 'xlBordersIndex' property and for example set the border style for the left edge of a cell:
range.Borders[Microsoft.Office.Interop.Excel.XlBordersIndex.xlEdgeLeft].LineStyle = Excel.XlLineStyle.xlContinuous;
But what if I just want to draw all borders? I have tried
range.BorderAround2();
but that just draws a box around the range itself, which I understand. So then I tried
range.Cells.BorderAround2();
thinking that it would go through each of the cells within the range and place all borders around each cell. This is not what occurred. So in order to get all borders around all cells in a range, must I manually access each of the four border indices?
private void AllBorders(Excel.Borders _borders)
{
_borders[Excel.XlBordersIndex.xlEdgeLeft].LineStyle = Excel.XlLineStyle.xlContinuous;
_borders[Excel.XlBordersIndex.xlEdgeRight].LineStyle = Excel.XlLineStyle.xlContinuous;
_borders[Excel.XlBordersIndex.xlEdgeTop].LineStyle = Excel.XlLineStyle.xlContinuous;
_borders[Excel.XlBordersIndex.xlEdgeBottom].LineStyle = Excel.XlLineStyle.xlContinuous;
_borders.Color = Color.Black;
}
oRange = SHEET2.get_Range("a1", "a10");
oRange.Borders.get_Item(Excel.XlBordersIndex.xlEdgeLeft).LineStyle = Excel.XlLineStyle.xlContinuous;
oRange.Borders.get_Item(Excel.XlBordersIndex.xlEdgeRight).LineStyle = Excel.XlLineStyle.xlContinuous;
oRange.Borders.get_Item(Excel.XlBordersIndex.xlInsideHorizontal).LineStyle = Excel.XlLineStyle.xlContinuous;
oRange.Borders.get_Item(Excel.XlBordersIndex.xlInsideVertical).LineStyle = Excel.XlLineStyle.xlContinuous;
Finally, I got it. I did this without impacting the performance too. I am taking a simple excel to explain here :
Before
I managed to store the range as A1:C4 in a variable dynamically in exRange and used the below code to give border
((Range)excelSheet.get_Range(exRange)).Cells.Borders.LineStyle = XlLineStyle.xlContinuous;
After
I'm not yet familiar wit C#, but in VBA there are Range.Borders(xlInsideVertical) and Range.Borders(xlInsideHorizontal) properties. Try to use macro-recorder and apply all borders for any workbook region. Perhaps that will help.
For Each range In ranges
For Each row As Range In .Range(range).Rows
row.Cells.BorderAround(XlLineStyle.xlContinuous)
row.Cells.Borders.Item(XlBordersIndex.xlInsideHorizontal).LineStyle = XlLineStyle.xlContinuous
row.Cells.Borders.Item(XlBordersIndex.xlInsideVertical).LineStyle = XlLineStyle.xlContinuous
Next
Next
Why not to do simply:
Excel.Range tRange = xlWorkSheet.UsedRange;
tRange.Borders.LineStyle = Excel.XlLineStyle.xlContinuous;
tRange.Borders.Weight = Excel.XlBorderWeight.xlThin;
Note: apply border after the row and cell (range) filled with data to get range simply using function .UsedRange()
Microsoft.Office.Interop.Excel.Range tRange = xlWorkSheet.UsedRange;
tRange.Borders.LineStyle = Microsoft.Office.Interop.Excel.XlLineStyle.xlContinuous;
tRange.Borders.Weight = Microsoft.Office.Interop.Excel.XlBorderWeight.xlThin;
Excel.Range tRange = xlWorkSheet.UsedRange;
tRange.Borders.LineStyle = Excel.XlLineStyle.xlContinuous;
tRange.Borders.Weight = Excel.XlBorderWeight.xlThin;
That way is not working, you will get something like this: