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

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)

Related

Some fields of my Excel databar are not colored

I have a problem when creating an Excel dataBar programatically. I have some fields that are not colored (and they are on the range).
I adapted a VBA code in C# to do this.
The VBA code :
`Sub Macro1()
'
' Macro1 Macro
'
'
Range("F6:F14").Select
Selection.FormatConditions.AddDatabar
Selection.FormatConditions(Selection.FormatConditions.Count).ShowValue = True
Selection.FormatConditions(Selection.FormatConditions.Count).SetFirstPriority
With Selection.FormatConditions(1)
.MinPoint.Modify newtype:=xlConditionValueAutomaticMin
.MaxPoint.Modify newtype:=xlConditionValueAutomaticMax
End With
With Selection.FormatConditions(1).BarColor
.Color = 2668287
.TintAndShade = 0
End With
Selection.FormatConditions(1).BarFillType = xlDataBarFillSolid
Selection.FormatConditions(1).Direction = xlContext
Selection.FormatConditions(1).NegativeBarFormat.ColorType = xlDataBarColor
Selection.FormatConditions(1).BarBorder.Type = xlDataBarBorderNone
Selection.FormatConditions(1).AxisPosition = xlDataBarAxisAutomatic
With Selection.FormatConditions(1).AxisColor
.Color = 0
.TintAndShade = 0
End With
With Selection.FormatConditions(1).NegativeBarFormat.Color
.Color = 255
.TintAndShade = 0
End With
Range("H11").Select
End Sub`
Adapted in C# :
Excel.Range last =
wsMce.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell,
Type.Missing);
Excel.Range range = wsMce.get_Range("E6:E" + last.Row);
var dataBar = range.FormatConditions.AddDatabar();
dataBar.ShowValue = true;
//dataBar.MinPoint.Modify = Excel.XlConditionValueTypes.xlConditionValueAutomaticMin;
//dataBar.SetFirstPriority.MinPoint.Modify = Excel.XlConditionValueTypes.xlConditionValueAutomaticMin;
//dataBar.SetFirstPriority.MaxPoint.Modify = Excel.XlConditionValueTypes.xlConditionValueAutomaticMax;
dataBar.BarColor.Color = Color.Orange;
dataBar.BarColor.TintAndShade = 0;
dataBar.BarFillType = Excel.XlDataBarFillType.xlDataBarFillSolid;
dataBar.Direction = Constants.xlContext;
dataBar.NegativeBarFormat.ColorType = Excel.XlDataBarNegativeColorType.xlDataBarColor;
dataBar.BarBorder.Type = Excel.XlDataBarBorderType.xlDataBarBorderNone;
dataBar.AxisPosition = Excel.XlDataBarAxisPosition.xlDataBarAxisAutomatic;
dataBar.AxisColor.Color = 0;
dataBar.AxisColor.TintAndShade = 0;
dataBar.NegativeBarFormat.Color.Color = 255;
dataBar.NegativeBarFormat.Color.TintAndShade = 0;
I did'nt succeed to adapt the MinPoint and MaxPoint properties and maybe that's where the color problem comes from.
I tried this
dataBar.SetFirstPriority.MinPoint.Modify = Excel.XlConditionValueTypes.xlConditionValueAutomaticMin;
dataBar.SetFirstPriority.MaxPoint.Modify = Excel.XlConditionValueTypes.xlConditionValueAutomaticMax;
And this
dataBar.MinPoint.Modify = Excel.XlConditionValueTypes.xlConditionValueAutomaticMin;
dataBar.MaxPoint.Modify = Excel.XlConditionValueTypes.xlConditionValueAutomaticMax;
And also this
dataBar.MinPoint = Excel.XlConditionValueTypes.xlConditionValueAutomaticMin;
dataBar.MaxPoint = Excel.XlConditionValueTypes.xlConditionValueAutomaticMax;
but it always raises an exception.
Thank for your helps !
Regards
EDIT : I also find that in VBA Selection.FormatConditions(1).ScopeType = xlDataFieldScope but i didn't know how to adapt it in C#, i tried that but not working : dataBar.ScopeType = Excel.XlPivotConditionScope.xlDataFieldScope;
Well, I finally found the answer, it's tricky...
The Scope type raise an exception because the range has to be in the column of data and the problem of color is also due to the minpoint and maxpoint property
How to use :
dataBar.MinPoint.Modify(Excel.XlConditionValueTypes.xlConditionValueAutomaticMin);
dataBar.MaxPoint.Modify(Excel.XlConditionValueTypes.xlConditionValueAutomaticMax);
dataBar.ScopeType = Excel.XlPivotConditionScope.xlDataFieldScope;

Write a List<string> to an Excel.Range

I have the below excel Range:
workSheet.Range[workSheet.Cells[12, 2], workSheet.Cells[5000, 2]]
I have converted this excel range to a List and performed some data manipulations on the list.
object[,] cellValues = (object[,])inputRng.Value2;
List<string> lst = cellValues.Cast<object>().ToList().ConvertAll(x => Convert.ToString(x));
I want to assign the list back to the Excel Range.
Here's some starting code for you that does what you want, it could probably be refined.
Your main "issue" is your conversion from the multidimensional array (object[,] cellValues = (object[,])excelRange.Value;) to the List<string> - if you could somehow keep this as a multidimensional array, then that would help your "setting" code - it depends what your other code is doing.
When you want to "set" your changes back, I've shown 2 ways to do it:
either iterate through the cells (ranges) and set each value
or provide a multi-dimensional array of values in advance and get the range to use them in one shot
Here:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Office.Interop.Excel;
namespace WindowsFormsApplication10
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// Create starting spreadsheet
Microsoft.Office.Interop.Excel.Application excelApp;
Microsoft.Office.Interop.Excel.Workbook excelWorkbook;
Microsoft.Office.Interop.Excel.Worksheet excelWorksheet;
excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Visible = true;
excelWorkbook = excelApp.Workbooks.Add();
excelWorksheet = excelWorkbook.Sheets.Add();
excelWorksheet.Activate();
Microsoft.Office.Interop.Excel.Range excelRange = excelWorksheet.Range[excelWorksheet.Cells[12, 2], excelWorksheet.Cells[5000, 2]];
// Turn off updating to make it faster
excelApp.ScreenUpdating = false;
// Set some initial data
int i = 1;
foreach (Microsoft.Office.Interop.Excel.Range cell in excelRange.Cells)
{
cell.Value = i;
i++;
}
// Get the data from those cells as a list of strings
object[,] cellValues = (object[,])excelRange.Value;
List<string> lst = cellValues.Cast<object>().ToList().ConvertAll(x => Convert.ToString(x));
// Modify the strings in some way
for (int l = 0; l < lst.Count; l++)
{
lst[l] = lst[l] + "modified";
}
// Here are some different ways set the "cells" back
// Set the cells back with the changes
//------------------------------------
// Option 1: using a multidimensional array
/*
object[,] cellValuesToWrite = new string[excelRange.Rows.Count, excelRange.Columns.Count];
int z = 0;
foreach (string str in lst)
{
cellValuesToWrite[z,0] = lst[z];
z++;
}
excelRange.Value2 = cellValuesToWrite;
*/
// Option 2: iterating the range of cells and "setting" the value
/*
int z = 0;
foreach (Microsoft.Office.Interop.Excel.Range cell in excelRange.Cells)
{
cell.Value = lst[z];
z++;
}
excelRange.Value2 = lst;
*/
// Turn updating back on
excelApp.ScreenUpdating = true;
}
}
}

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.

Select and merge a range of cells

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();
}
}

Multiple Formats in one cell using c#

I want to have mutple format types in one cell in my workbook. For example I want my A1 cell to display " Name: Aaron Kruger ". When I programmatically add the name "Aaron Kruger" to the template, it automatically makes it bold. So instead it looks like this " Name:Aaron Kruger ". So I want Bold and non-bold both in the same cell. And maybe in the future I will want two different text sizes in the same cell.
Thanks,
Aaron Kruger
Here is the function I created:
public void inputData(int row, int column, string cellName, System.Windows.Forms.TextBox textBox, Excel.Worksheet sheet)
{
sheet.Cells[row, column] = sheet.get_Range(cellName, Type.Missing).Text + " " + textBox.Text; // adds value to sheet
}
Here are the arguments I pass in:
inputData(5, 1, "A5", tbTagNumber, xlSheet);
inputData(6, 1, "A6", tbCustomer, xlSheet);
inputData(7, 1, "A5", tbDataFile, xlSheet);
inputData(3, 6, "F3", tbJobNumber, xlSheet);
inputData(4, 6, "F4", tbMeterSN, xlSheet);
inputData(6, 6, "F6", tbPO, xlSheet);
inputData(7, 6, "F7", tbFlowplate, xlSheet);
inputData(4, 9, "I4", tbElectronicSN, xlSheet);
Range rng1 = ws.getRange("A1","E10");
for(int i=0;i<10;i++)
{
Range rngTst=rng.cells[i,i];
for(int j=0;j<rngTst.get_characters().count;j++)
{
rngTst.application.activecell.get_characters(j,j).font.color
}
}
or
int sFirstFoundAddress = currentFind.FormulaR1C1Local.ToString().IndexOf("NOT FOR CIRCULATION ");
get_Range(excel.Cells[1, 1],
excel.Cells[1, dtData.Columns.Count])
.get_Characters(sFirstFoundAddress, 20).Font.Color =
System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
Use Microsoft.Office.Interop.Excel.Range which gives you each character as Characters type. Now use its Font and other properties to style them.
Refer an example here: http://www.bloodforge.com/post/Extract-Formatted-Text-From-Excel-Cell-With-C.aspx
Microsoft.Office.Interop.Excel.Range Range = (Microsoft.Office.Interop.Excel.Range)Cell;
int TextLength = Range.Text.ToString().Length;
for (int CharCount = 1; CharCount <= TextLength; CharCount++)
{
Microsoft.Office.Interop.Excel.Characters charToTest = Range.get_Characters(CharCount, 1);
bool IsBold = (bool)charToTest.Font.Bold;
bool IsItalic = (bool)charToTest.Font.Italic;
// other formatting tests here
}
I recorded a macro, it shouldn't be hard to translate it to C#:
ActiveCell.FormulaR1C1 = "Test test"
Range("A1").Select
ActiveCell.FormulaR1C1 = "Test test"
With ActiveCell.Characters(Start:=1, Length:=5).Font
.Name = "Calibri"
.FontStyle = "Regular"
.Size = 11
.Strikethrough = False
.Superscript = False
.Subscript = False
.OutlineFont = False
.Shadow = False
.Underline = xlUnderlineStyleNone
.ThemeColor = xlThemeColorLight1
.TintAndShade = 0
.ThemeFont = xlThemeFontMinor
End With
With ActiveCell.Characters(Start:=6, Length:=4).Font
.Name = "Calibri"
.FontStyle = "Bold"
.Size = 11
.Strikethrough = False
.Superscript = False
.Subscript = False
.OutlineFont = False
.Shadow = False
.Underline = xlUnderlineStyleNone
.ThemeColor = xlThemeColorLight1
.TintAndShade = 0
.ThemeFont = xlThemeFontMinor
End With

Categories