C# - Select Cell at Bottom Right corner - c#

I am looking for a way to move my selected cell from the top left to the bottom right. I was trying to use xlDirection but that highlights everything and doesnt allow me to do a combination of movements.
A B C
1 4 7
2 5 8
3 6 9
I start at A and now only want to focus on 9. The size of the excels change so i cant specify the actual cell to look for each time.
I was hoping there are similar commands as Ctrl+Down or Ctrl+Right that would put me on the cell.

can't you send keystrokes to the object?

A reference for all key strokes can be found here:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel._application.sendkeys?view=excel-pia
public class Navigator
{
private Excel.Application excel;
private Excel.Workbook workbook;
public void NavigateToBottomRight(string filePath, string worksheetName)
{
excel = new Excel.Application();
excel.Visible = true;
workbook = excel.Workbooks.Open(filePath);
var worksheet = workbook.Worksheets.Cast<Excel.Worksheet>().FirstOrDefault(x => x.Name == worksheetName);
Excel.Range cell = worksheet.Cells[1, 1];
cell.Activate();
string controlRight = "^{Right}";
string controlDown = "^{Down}";
excel.SendKeys(controlRight, true);
excel.SendKeys(controlDown, true);
//Do other work here
workbook.Save();
excel.Quit();
}
}
Hope this helps!

Related

Unmerging excel rows, and duplicate data c#

This image show what i basically want to do
I have plenty excel files that i need to prepare before inserting the data into a SQL database, one of the steps is unmerge excel cells and duplicate the data, i'm doing this doc parse with c#
I found a solution with VBA Macro Excel here
Sub UnMergeFill()
Dim cell As Range, joinedCells As Range
For Each cell In ThisWorkbook.ActiveSheet.UsedRange
If cell.MergeCells Then
Set joinedCells = cell.MergeArea
cell.MergeCells = False
joinedCells.Value = cell.Value
End If
Next
End Sub
But i need to do it on c# with microsoft.office.interop.excel
Does anyone know if there's a way to do this?
C# code is very similar:
private void UnMergeFill(Workbook wb)
{
foreach (Range cell in ((_Worksheet)wb.ActiveSheet).UsedRange)
{
if (cell.MergeCells)
{
var joinedCells = cell.MergeArea;
cell.MergeCells = false;
joinedCells.Value = cell.Value;
}
}
}

Display cell with vertical text using EPPlus

Using EPPlus, how can i change a cells text to display as a vertical text like this,
In excel, you can do this by clicking on this button when setting cell orientation,
I'm trying to play around with the .TextRotation but this does not achieve what I want, setting it to something like 180 degrees will give me something like this,
ws.Cells[row, 2].Style.TextRotation = 180;, .TextRotation only accepts an integer value so I was wondering how I can get the "Text" buttons value,
Its definitely a bug you found. There is a way but it is pretty ugly. You can use the StyleID created by the cell when you change it to anything other than the default:
[TestMethod]
public void Text_Rotate_Test()
{
//https://stackoverflow.com/questions/57603348/display-cell-with-vertical-text-using-epplus
var fileInfo = new FileInfo(#"c:\temp\Text_Rotate_Test.xlsx");
if (fileInfo.Exists)
fileInfo.Delete();
using (var pck = new ExcelPackage(fileInfo))
{
var workbook = pck.Workbook;
var worksheet = workbook.Worksheets.Add("Sheet1");
var cell = worksheet.Cells[1, 1];
cell.Value = "Test Text Value";
//Trigger epplus to create a new style specific for the cell.
//This needs to be done even thought it will be overridden in
//order to ref by index. But have to be careful not to step
//on other styles so make it as unique as it needs to be.
cell.Style.TextRotation = 180;
//Make sure the update the xml before looking up by index
workbook.Styles.UpdateXml();
workbook.Styles.CellXfs[cell.StyleID].TextRotation = 255;
pck.Save();
}
}
Which gives this:

In Excel, how to show data in hidden rows and columns

This is what I have tried to do to have my chart show the data in the columns I have hidden:
Excel.Application oExcelApp = new Excel.Application;
Excel._Workbook oWB = oExcelApp.Workbooks.Add();
Excel._Worksheet oWS = oWB.ActiveSheet;
Excel.ChartObjects oCharts = (Excel.ChartObjects)oWS.ChartObjects();
Excel.ChartObject oChart = oCharts.Add(10, 80, 300, 250);
Excel.Chart chart = oChart.Chart;
// these three lines work
chart.HasTitle = true;
chart.ChartTitle.Text = "Chart Title";
chart.HasLegend = false;
// I get a compile error for this line
// because the HasHiddenContent property is read-only
chart.HasHiddenContent = true;
The HasHiddenContent property is the only one I can find that looks like it might be what I'm looking for, but it is read-only. Which property can I use to tell the chart to show data in hidden columns (and rows)?
After doing the "record the macro for what you want to do and then convert VBA to c#," I found the answer to my question.
The correct property to toggle showing data in hidden columns and rows is:
chart.PlotVisibleOnly = false;

How to get the row number of the last row when you are printing an excel sheet (EPPlus)

I want to add one picture (displaying "DRAFT") by printable Excel worksheet in C# EPPlus.
I need to know if there is a way to find the last visible row of each page of a worksheet when you are printing it. I can't pretend that it will always be a fix number of row per page because it depends on the content of the cells.
Here is my current code that use a fix number of row per page (30) to insert image. This result in approximately one image per printable page except that in each new page the image is not at the same place. (Slightly off, depending on content of cells.)
public void InsertDraftImage(ExcelWorksheet worksheet, FileInfo draft_image)
{
int maxRowNumber = worksheet.Dimension.End.Row;
int rowByPage = 30;
int numberOfPage = (maxRowNumber / rowByPage) + 1;
ExcelPicture picture = null;
for(int i = 0; i < numberOfPage; i++)
{
if(draft_image != null)
{
picture = worksheet.Drawings.AddPicture(i.ToString(), draft_image);
picture.SetSize(609, 545); //original image size
picture.SetPosition(i * rowByPage, 0, 1, 0);
picture.EditAs = eEditAs.Absolute;
}
}
After trying to implement the missing code in 'ExcelHeaderFooter.cs' from the EPPlus with a workmate without success, we finally did it by following Ernie suggestion!!
There is my final code to insert a picture into each page of a printable excel file generate with EPPlus in C#.
It is done by adding the picture in the footer and setting the Boolean ScaleWithDoc to false (default = true).
public void InsertDraftImage(ExcelWorksheet worksheet, FileInfo draft_image)
{
ExcelHeaderFooterText footer = worksheet.HeaderFooter.OddFooter; //all page have same footer
footer.InsertPicture(draft_image, PictureAlignment.Centered);
}
Added this code in my method to create the ExcelWorksheet (all the other excel style, populate, settings).
XmlAttribute temp = worksheet.WorksheetXml.CreateAttribute("scaleWithDoc");
temp.Value = "0";
worksheet.WorksheetXml.GetElementsByTagName("headerFooter")[0].Attributes.Append(temp);
package.Save();

Color non-consecutive cells in an Excel sheet

This is what happens:
xlValues is set as an Excel.Range object.
I have tried the following as well, all giving me the same error:
//xlValueRange = xlSheet...
.get_Range("A1:A5,A15:A25,A50:A65");
.UsedRange.Range["A1:A5,A15:A25,A50:A65"];
.Range["A1:A5,A15:A25,A50:A65"];
xlApp.ActiveWorkbook.ActiveSheet.Range["A1:A5,A15:A25,A50:A65"];
//I have also tried these alternatives with ".Select()" after the brackets and
//", Type.Missing" inside the brackets
//This works though...
xlSheet.Range["A1:A5"];
I'm trying to recolor specific cells in an excel sheet, I have found a solution by using two loops but it's simply too slow. Running through a column of 30 000 cells takes minutes.
I have never done anything like this before and I used this tutorial to get me started.
This solution uses a bool array with cells to be colored set to true.(recolored)
//using Excel = Microsoft.Office.Interop.Excel;
xlApp = new Excel.Application();
xlApp.Visible = true;
xlBook = xlApp.Workbooks.Add(Type.Missing);
xlSheet = (Excel.Worksheet)xlBook.Sheets[1];
for (int i = 1; i < columns + 1; i++)
{
for (int j = 1; j < rows + 1; j++)
{
if (recolored[j, i])
xlSheet.Cells[j+1, i+1].Interior.Color = Excel.XlRgbColor.rgbRed;
}
}
}
What I would like to do is something like this:
Excel.XlRgbColor[,] color;
//Loop to fill color with Excel.XlRgbColor.rgbRed at desired cells.
var startCell = (Excel.Range)xlSheet.Cells[1, 1];
var endCell = (Excel.Range)xlSheet.Cells[rows, columns];
var xlRange = xlSheet.Range[startCell, endCell];
xlRange.Interior.Color = color;
This one gives me an error on the final line though;
Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))
My first guess would be to make an Excel.Range object that covers the cells I want to have red and use that object in place of xlRange, something like this:
RangeObject.Interior.Color = Excel.XlRgbColor.rgbRed;
I don't know if it's possible to make an Excel.Range object with gaps like that though, I could use some help on this one.
You can select non-consecutive cells by using comma-separated list of ranges like this:
this.Application.ActiveWorkbook.ActiveSheet.Range["A2:A4,B3:B16"].Select();
You can then re-color the selection using:
Selection.Interior.Color = ColorTranslator.ToOle(Color.Yellow);
This will get rid of the coloring loop you're having trouble with.
Also, in a VSTO add-in, you should normally never need to do new Excel.Application() in your code. this.Application in the Add-in class should give you access to the active instance of Excel.
UPDATE
Here's a piece of code that should help you pin-point your problem. I added a Ribbon to my add-in and a simple button to the Ribbon. Behind the click event of this button, I have added the following code:
private void button1_Click(object sender, RibbonControlEventArgs e)
{
try
{
var App = Globals.ThisAddIn.Application;
if (App == null)
System.Windows.Forms.MessageBox.Show("App is null");
else
{
var Sheet = App.ActiveSheet;
if (Sheet == null)
System.Windows.Forms.MessageBox.Show("Sheet is null");
else
{
var Rng = Sheet.Range["A1:A5,A15:A25,A50:A65"];
if (Rng == null)
System.Windows.Forms.MessageBox.Show("Rng is null");
else
{
Rng.Select();
}
}
}
}
catch (Exception ee)
{
System.Windows.Forms.MessageBox.Show("Exception: " + ee.Message);
}
}
On my end this code runs successfully and selects the non-contiguous range of cells. Try this on your end and let me know what you see.
UPDATE 2
The same code works for me in a WinForms application with reference to Excel 14.0 (will hopefully work with Excel 12.0 too). Just a couple of minor changes are required. Here's the full code.
private void button1_Click(object sender, RibbonControlEventArgs e)
{
try
{
var App = new Microsoft.Office.Interop.Excel.Application();
if (App == null)
System.Windows.Forms.MessageBox.Show("App is null");
else
{
App.Workbooks.Add();
var Sheet = App.ActiveSheet;
if (Sheet == null)
System.Windows.Forms.MessageBox.Show("Sheet is null");
else
{
Microsoft.Office.Interop.Excel.Range Rng = Sheet.get_Range("A1");
Rng.Select();
Rng = Sheet.get_Range("A1:A5,A15:A25,A50:A65");
if (Rng == null)
System.Windows.Forms.MessageBox.Show("Rng is null");
else
{
Rng.Select();
App.Selection.Interior.Color = Microsoft.Office.Interop.Excel.XlRgbColor.rgbYellow;
App.ActiveWorkbook.SaveAs("testtest.xlsx");
App.Quit();
}
}
}
}
catch (Exception ee)
{
System.Windows.Forms.MessageBox.Show("Exception: " + ee.Message);
}
}
I had the same problem and it turned out that it was a bad list separator - in my case instead of comma there should be a semicolon.
So instead of
.Range["A1:A5,A15:A25,A50:A65"];
try:
private string listSep = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator;
.Range["A1:A5" + listSep + "A15:A25" + listSep + "A50:A65"];
[RangeObject].Interior.Color changes the cell background color. Use this
[RangeObject].Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Red);
For cell text, use
[RangeObject].Font.Color
I've struggled with this issue for a long time, too. But today I believe I finally found the solution (and the cause).
The problem is that Excel uses the current regional settings to determine the comma operator, ie. the separator between two ranges (don't ask me why - to me it's as insane as localizing the function names).
Anyway, on my computer I have the Czech locale set so the separator to use is a semicolon, not a comma! If I use it in the parameter for the Range method, it works perfectly.
From the discussion I got the impression that you are Swedish so you likely have the Swedish locale set. Its default list separator is also a semicolon, so it seems likely to me that this might solve your problem, too. You can always check the separator set in the computer regional settings by calling
System.Globalization.CultureInfo.InstalledUICulture.TextInfo.ListSeparator
Hope this helps!
The maximum length of the range address string is 255. So, you need to chunk your list so that the combined range address of each section is less than 255 long.

Categories