Select and merge a range of cells - c#

I am trying to select the range A1 to C3 to affect a value but this code is not working:
worksheet.Select["A1:C3"].Value = "toto";
I am able to affect the value to each of the cell with this code (but that's not what I want):
worksheet.Cells["A1:C3"].Value = "toto";
I want to merge all cells from A1 to C3, and that this new cell contains toto value;

You first have to merge the cells like this:
worksheet.Cells["A1:C3"].Merge = true;
then to set the value you would either do this:
worksheet.Cells["A1:C3"].Value = "toto";
or set A1 to the value (since it's merged)
worksheet.Cells["A1"].Value = "toto";

Kelsey's method is more diect but if you want to use the Select methods for some reason:
[TestMethod]
public void MergeCellTest()
{
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
if (existingFile.Exists)
existingFile.Delete();
using (var package = new ExcelPackage(existingFile))
{
var workbook = package.Workbook;
var worksheet = workbook.Worksheets.Add("newsheet");
worksheet.Select("A1:C3");
worksheet.SelectedRange.Merge = true;
worksheet.SelectedRange.Value = "toto";
package.Save();
}
}

Related

How to format cell with number using ClosedXML?

by using following code I am only formatting the respective cell but while I am selecting the whole column it's showing the general format as label please see the attach imageenter image description here
enter image description here
var ws = wb.Worksheets.Add("Contacts");
//Adding text
//Title
ws.Cell("B2").Value = "Contacts";
//First Names
ws.Cell("B3").Value = "FName";
ws.Cell("B4").Value = "John";
ws.Cell("B5").Value = "Hank";
ws.Cell("B6").SetValue("Dagny"); // Another way to set the value
//Last Names
ws.Cell("C3").Value = "LName";
ws.Cell("C4").Value = "Galt";
ws.Cell("C5").Value = "Rearden";
ws.Cell("C6").SetValue("Taggart"); // Another way to set the value
//Adding more data types
//Is an outcast?
ws.Cell("D3").Value = "Outcast";
ws.Cell("D4").Value = true;
ws.Cell("D5").Value = false;
ws.Cell("D6").SetValue(false); // Another way to set the value
//Date of Birth
ws.Cell("E3").Value = "DOB";
ws.Cell("E4").Value = new DateTime(1919, 1, 21);
ws.Cell("E5").Value = new DateTime(1907, 3, 4);
ws.Cell("E6").SetValue(new DateTime(1921, 12, 15)); // Another way to set the value
//Income
ws.Cell("F3").Value = "Income";
ws.Cell("F4").Value = 2000;
ws.Cell("F5").Value = 40000;
ws.Cell("F6").SetValue(10000); // Another way to set the value
//Defining ranges
//From worksheet
var rngTable = ws.Range("B2:F6");
//From another range
var rngDates = rngTable.Range("D3:D5"); // The address is relative to rngTable (NOT the worksheet)
var rngNumbers = rngTable.Range("E3:E5"); // The address is relative to rngTable (NOT the worksheet)
//Formatting dates and numbers
//Using a OpenXML's predefined formats
rngDates.Style.NumberFormat.NumberFormatId = 15;
//Using a custom format
rngNumbers.Style.NumberFormat.Format = "$ #,##0";

Select collection of cells

I need to select a collection of cells in a worksheet. I could find how to select a range, but not when the cells are "isolated".
For example "$D$4", $G$9" ...
My code:
var excelApp = Globals.ThisAddIn.Application;
List<string> unlockedCells = new List<string>();
foreach (_Excel.Range cells in excelApp.ActiveSheet.UsedRange)
{
if (!cells.Locked)
{
unlockedCells.Add(cells.Address);
}
}
unlockedCells.ForEach(_c =>
{
excelApp.Range[_c].Select();
});
The problem here is that every time a new range is selected, the previous selection is lost.
Another approach. It doesn't work, raises exception Exception from HRESULT: 0x800A03EC at Microsoft.Office.Interop.Excel._Application.get_Range(Object Cell1, Object Cell2)
The range I get is the following: "$D$8,$E$8,$D$9,$E$9,$D$10,$E$10,$D$11,$E$11,$D$12,$E$12"
StringBuilder output = new StringBuilder();
...
output.Append(String.Format("{0},", cells.Address));
string rangeDef = output.ToString().Left(output.Length - 1);
excelApp.Range[rangeDef].Select();
How could I achieve it?
Office Version 2016
Although the excelApp itself does have a Range property, the workbook or worksheet is ambiguous. Instead, try to reference the Range of a certain worksheet:
StringBuilder output = new StringBuilder();
output.Append("C4,");
output.Append("D5,");
output.Append("E6,");
string rangeDef = output.ToString().Substring(0, output.Length - 1);
Worksheet worksheet = excelApp.ActiveSheet;
Range range = worksheet.Range[rangeDef];
range.Value = "test";
Worked for me for some minimal testing.
List<string> cells = new List<string>();
cells.Add("$C$4");
cells.Add("$D$5");
cells.Add("$H$10");
for (int idx = 0; idx < cells.Count; idx++)
cells[idx] = String.Format("'{0}'!{1}", (excelApp.ActiveSheet as _Excel.Worksheet).Name, cells[idx]);
string rangeDef = String.Format("={0}", String.Join(";", cells));
var sheet = (excelApp.ActiveSheet as _Excel.Worksheet).get_Range(rangeDef, Type.Missing).Select();
It seems I was missing the worksheet name.

Changing color of a line serie in EPPlus

Is there an easier way to change the color of a line serie?
I tried using this. But the serieNode is producing a NullReferenceException at serieNode.AppendChild(spPr);.
Here's the code that generates the graph:
private void GenerateLicenseUsageStatsChart(FileInfo excelFileInfo, FileInfo
csvFileInfo, DateTime lastCheckedDate)
{
string worksheetsName = "Sheet1";
const bool firstRowIsHeader = false;
var excelTextFormat = new ExcelTextFormat { Delimiter = ',' };
// excelTextFormat.EOL = "\r";
using (ExcelPackage package = new ExcelPackage(excelFileInfo))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add(worksheetsName);
worksheet.Cells["A1"].LoadFromText(csvFileInfo, excelTextFormat,
OfficeOpenXml.Table.TableStyles.None, firstRowIsHeader);
var workbook = package.Workbook;
ExcelWorksheet workSheet = workbook.Worksheets[1];
var chart = workSheet.Drawings.AddChart("chart",
eChartType.ColumnClustered);
chart.SetPosition(10, 250);
chart.SetSize(700, 500);
chart.Title.Text = $"LicenseUsageStats {lastCheckedDate:MMM}
{lastCheckedDate.Year}";
chart.YAxis.MajorUnit = 1;
chart.YAxis.MinorUnit = 1;
chart.XAxis.MajorTickMark = eAxisTickMark.None;
chart.XAxis.MinorTickMark = eAxisTickMark.None;
chart.XAxis.Title.Text = "Hour";
chart.XAxis.Title.Font.Size = 10;
var maxLicensesSerie = chart.Series.Add("B2:B25", "A2:A25");
maxLicensesSerie.Header = "Max Licenses Used";
var avgLicensesSerie = chart.Series.Add("C2:C25", "A2: A25");
avgLicensesSerie.Header = "Avg Licenses Used";
var maxLineChart = (ExcelLineChart)
chart.PlotArea.ChartTypes.Add(eChartType.Line);
var maxThreshLineSerie = maxLineChart.Series.Add("D2:D25",
"A2:A25");
maxThreshLineSerie.Header = "Max";
SetLineChartColor(maxLineChart, 0, Color.Red);
var warningLineChart =
(ExcelLineChart)chart.PlotArea.ChartTypes.Add(eChartType.Line);
var warningThreshLineSerie =
warningLineChart.Series.Add("E2:E25", "A2:A25");
warningThreshLineSerie.Header = "Warning";
SetLineChartColor(warningLineChart, 1, Color.Yellow);
worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns();
// workSheet.Column(4).Hidden = true;
// workSheet.Column(5).Hidden = true;
package.Save();
}
}
My end goal:
Since excel doesn't have a neat way of creating horizontal lines, I created the max and warning columns. I would like to hide those columns as well, without them affecting the graph. Maybe I could "hide" them by moving them to another sheet?
I see the problem. The function assumes that the index of the serie matches the number of series in the chart object collection. But since it is a mixed chart that is not the case after casting the result of the Add. This is a bit of a hack but it will work (I really should think a little harder about how to match up the numbers):
public static void SetLineChartColor(this ExcelChart chart, int serieIdx, int chartSeriesIndex, Color color)
{
var chartXml = chart.ChartXml;
var nsa = chart.WorkSheet.Drawings.NameSpaceManager.LookupNamespace("a");
var nsuri = chartXml.DocumentElement.NamespaceURI;
var nsm = new XmlNamespaceManager(chartXml.NameTable);
nsm.AddNamespace("a", nsa);
nsm.AddNamespace("c", nsuri);
var serieNode = chart.ChartXml.SelectSingleNode($#"c:chartSpace/c:chart/c:plotArea/c:lineChart/c:ser[c:idx[#val='{serieIdx}']]", nsm);
var serie = chart.Series[chartSeriesIndex];
var points = serie.Series.Length;
//Add reference to the color for the legend
var srgbClr = chartXml.CreateNode(XmlNodeType.Element, "srgbClr", nsa);
var att = chartXml.CreateAttribute("val");
att.Value = $"{color.R:X2}{color.G:X2}{color.B:X2}";
srgbClr.Attributes.Append(att);
var solidFill = chartXml.CreateNode(XmlNodeType.Element, "solidFill", nsa);
solidFill.AppendChild(srgbClr);
var ln = chartXml.CreateNode(XmlNodeType.Element, "ln", nsa);
ln.AppendChild(solidFill);
var spPr = chartXml.CreateNode(XmlNodeType.Element, "spPr", nsuri);
spPr.AppendChild(ln);
serieNode.AppendChild(spPr);
}
And you can call it like this:
maxLineChart.SetLineChartColor(2, 0, Color.Red);
warningLineChart.SetLineChartColor(3, 0, Color.Yellow);

WinForm DataGridView Multi-Line

This is my DataGridView.
I'd like to make multiple lines.
From: 1abcdefghijklmno
To: 1abcdefghijklmno
pqrstuvwxyzabcde
fghijklmnopqrstu
vwxyz
What do I have to do ?
My codes:
private void Form1_Load(object sender, EventArgs e)
{
var myArray1 = new string[] { "1abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "2abc" };
var myArray2 = new string[] { "3abc", "4abc" };
var myArray = new string[][] { myArray1, myArray2 };
foreach( var x in myArray )
dataGridView1.Rows.Add(x);
}
and another code page..
DataGridView part
// dataGridView1
dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridView1.DefaultCellStyle = dataGridViewCellStyle3;
dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dataGridView1.RowsDefaultCellStyle = dataGridViewCellStyle4;
this.dataGridView1.RowTemplate.DefaultCellStyle.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
I think I did all I can do.
Columns parts 1
// Column1
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.Column1.DefaultCellStyle = dataGridViewCellStyle1;
Columns parts 2
// Column2
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.Column2.DefaultCellStyle = dataGridViewCellStyle2;
Regards
These two lines should do the trick:
dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
dataGridView1.RowsDefaultCellStyle.WrapMode = DataGridViewTriState.True;
But be aware, the the grid breaks new words to new lines and not single words. So if you write aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa you'll not get a wrap. But if you write aaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa you get a wrap.

How do you set the value of a cell using Excel Dna?

I've got the following code in my Excel DNA plugin
In my AutoOpen method I put the following code:
ExcelIntegration.RegisterUnhandledExceptionHandler(ex => ex.ToString());
I've got the following function that gets called from my excel sheet.
[ExcelFunction(Category = "Foo", Description = "Sets value of cell")]
public static Foo(String idx)
{
Excel.Application app = (Excel.Application)ExcelDnaUtil.Application;
Excel.Workbook wb = app.Workbooks[1];
Excel.Worksheet ws = GetSheet("Main");
// This gives us the row
Excel.Name idxRange = wb.Names.Item("COL_Main_Index");
var row = (int)app.WorksheetFunction.Match(idx, idxRange.RefersToRange, 0);
// Get the Column
Excel.Name buyOrderRange = wb.Names.Item("COL_Main_BuyOrder");
var col = (int)buyOrderRange.RefersToRange.Cells.Column;
// create the range and update it
Excel.Range r = (Excel.Range)ws.Cells[row, col];
r.Value ="Foo";
}
The issue is that I can't actually set any cell values. When I call the method it causes an error on the last line.
My error handler gives me the followign error:
{System.Runtime.InteropServices.COMException (0x800A03EC)
I've also tried to set the cell value like so:
r = (Excel.Range)ws.Cells[12, 22];
const int nCells = 1;
Object[] args1 = new Object[1];
args1[0] = nCells;
r.GetType().InvokeMember("Value2", BindingFlags.SetProperty, null, r, args1);
With the same result.
Can anyone point to what I might be doing wrong here?
Actually, you can write in any cell if you do this in an async job as a macro.
Simple Example:
using ExcelDna.Integration;
using Excel = Microsoft.Office.Interop.Excel;
[ExcelFunction(Category = "Foo", Description = "Sets value of cell")]
public static Foo(String idx)
{
Excel.Application app = (Excel.Application)ExcelDnaUtil.Application;
Excel.Range range = app.ActiveCell;
object[2,2] dummyData = new object[2, 2] {
{ "foo", "bar" },
{ 2500, 7500 }
};
var reference = new ExcelReference(
range.Row, range.Row + 2 - 1, // from-to-Row
range.Column - 1, range.Column + 2 - 1); // from-to-Column
// Cells are written via this async task
ExcelAsyncUtil.QueueAsMacro(() => { reference.SetValue(dummyData); });
// Value displayed in the current cell.
// It still is a UDF and can be executed multiple times via F2, Return.
return "=Foo()";
}
Writing into a single Cell:
int row = 5;
int column = 6;
var reference = new ExcelReference(row - 1, column - 1);
ExcelAsyncUtil.QueueAsMacro(() => { reference.SetValue("Foobar"); });
// edit:
just fyi, you can also use:
private void WriteArray(object[,] data)
{
Excel.Application app = (Excel.Application)ExcelDnaUtil.Application;
Excel.Worksheet worksheet= (Excel.Worksheet)app.ActiveWorkbook.ActiveSheet;
Excel.Range startCell = app.ActiveCell;
Excel.Range endCell = (Excel.Range)worksheet.Cells[startCell.Row + data.GetLength(0) - 1, startCell.Column + data.GetLength(1) - 1];
var writeRange = worksheet.Range[startCell, endCell];
writeRange.Value2 = data;
}
And then:
object[,] data = ...;
ExcelAsyncUtil.QueueAsMacro(() =>
{
WriteArray();
});
Excel does not allow you to set other worksheet cells from within a user-defined worksheet function. This is to preserve the dependency tree Excel uses to manage the recalculation. This is true whether you are using VBA, the C API or Excel-DNA.
Best is to add a ribbon button, context menu or shortcut key to effect the changes via a macro.
There are some ugly workarounds, but I would not recommend it.
Here is the answer using .NET VB
Dim row As Integer = 7
Dim column As Integer = 7
Dim reference = New ExcelReference(row, column)
ExcelAsyncUtil.QueueAsMacro(Sub()
reference.SetValue("Test")
End Sub)

Categories