System.DBNull to String error in excel - c#

I have my own custom file type similar to .CSV except with custom delimiters. The delimiter for the comma is (char)20 (looks like a square) and for the quotes it is (char)254 (looks like þ). I have created an Excel 2010 Add-in in Visual studio to parse through the document that replaces all custom delimiters with commas and double-quotes so that it is in .CSV format.
The program also creates a new toolbar and button that will start the process. It works fine on some documents but not on others and if you try to do it twice in one instance of Excel it comes up with the error "Cannot implicitly convert type 'System.DBNull' to 'string'". This is because the row.Text property is being read as {}.
Now my question is what is causing the row.Text property to be read as {} instead of the text that is inside the cell? Also why does this occur on some documents but not others even though they use the same encoding?
An example of what is in a cell is (NOTE-the comma symbol won't print here):
þITEM_IDþþBEGDOCþþENDDOCþþBEGATTþþENDATTþþPARENT_ATTACHMENTþþATTACHMENT_BATESþþ etc.
EDIT Here is my code:
public partial class ThisAddIn
{
Office.CommandBarButton toolbarCommand;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Office.CommandBar toolbar = Application.CommandBars.Add("My Toolbar",Office.MsoBarPosition.msoBarTop,false,true);
toolbarCommand = (Office.CommandBarButton)
toolbar.Controls.Add(
Office.MsoControlType.msoControlButton,
missing,
missing,
missing,
true);
toolbarCommand.Caption = "Toolbar Button";
toolbarCommand.FaceId = 59;
toolbarCommand.Click += new Office._CommandBarButtonEvents_ClickEventHandler(toolbarCommand_Click);
toolbar.Visible = true;
}
void toolbarCommand_Click(Office.CommandBarButton Ctrl, ref bool CancelDefault)
{
Excel.Worksheet activeWorksheet = ((Excel.Worksheet)Application.ActiveSheet);
try
{
IterateRows(activeWorksheet);
}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
Ctrl.Click+=new Office._CommandBarButtonEvents_ClickEventHandler(toolbarCommand_Click);
}
public void IterateRows(Excel.Worksheet worksheet)
{
//Get the used Range
Excel.Range usedRange = worksheet.UsedRange;
Excel.Worksheet activeWorksheet = ((Excel.Worksheet)Application.ActiveSheet);
//Iterate the rows in the used range
if (usedRange.Rows.Count > 1)
{
foreach (Excel.Range row in usedRange.Rows)
{
//MessageBox.Show(row.Text);
char quote = (char)254;
string data = row.Text;
row.Columns[1] = data.Replace(quote, '"').Replace((char)20, ',');
row.TextToColumns(Type.Missing, Excel.XlTextParsingType.xlDelimited, Excel.XlTextQualifier.xlTextQualifierDoubleQuote, Type.Missing, Type.Missing, Type.Missing, true);
}
}
}

Related

Attaching a Picture to Word using ContentControl

I have a word template file within some content-control that one of them is a Picture-content-control, tagged with sign.
I read many documents and some questions like mines here and reached to some code like this one, but it just doesn't work and do not add the picture to document, I don't understand where is the problem.
public void AddSignHidden(Microsoft.Office.Interop.Word._Document Doc, string SignImagePath)
{
ContentControls Ccontrol = Doc.SelectContentControlsByTag("__sign");
foreach (ContentControl nativeControl in Ccontrol)
{
nativeControl.Range.InlineShapes.AddPicture(SignImagePath, Type.Missing, true, Type.Missing);
}
Doc.Save();
Doc.Close();
Doc = null;
}

Selecting a specific sheet in VSTO Excel Add-In

I'm trying to select a specific sheet (by name or index) with my excel Add-In with no avail.
My addin file ThisAddIn.cs has:
public Excel.Workbook GetActiveWorkbook()
{
return (Excel.Workbook)Application.ActiveWorkbook;
}
And my Ribbon1.cs has:
namespace Test3
{
public partial class Ribbon1
{
private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
{
Debug.WriteLine("Hello");
Workbook currentwb = Globals.ThisAddIn.GetActiveWorkbook();
Worksheet scratch = currentwb.Worksheets.Item[1] as Worksheet; // Error blocks here
if (scratch == null)
return;
// Worksheet scratch = currentwb.Worksheets["Sheets1"];
scratch.Range["A1"].Value = "Hello";
}
}
}
But I get a System.NullReferenceException: 'Object reference not set to an instance of an object.'
I'm new to c# (come from Python) and am very confused why this doesn't work. Any help would be greatly appreciated.
The issue was I was trying to load the Workbook instance before it was loaded. In other words, trying to grab the instance in Ribbon Load was too early. Grabbing the instance on a ButtonClick event worked fine!
Try
Excel.Application ExApp = Globals.ThisAddIn.Application as Excel.Application;
Worksheet sheet = Globals.Factory.GetVstoObject(ExApp.ActiveWorkbook.Worksheets[1]);
Worksheet vsheet = Globals.Factory.GetVstoObject(ExApp.ActiveWorkbook.Worksheets[2]);
sheet.Cells[1, 1] = "A1";
vsheet.Cells[1, 1] = "A1 from second sheet";

Parse table using Microsoft.Office.Interop.Word, get only text from first column?

I am working on writing a program that will parse text data from a Microsoft Word 2010 document. Specifically, I want to get text from each cell in the first column of every table in the document.
For reference, the document looks likes this:
I only need text from the cells in the first column on each page. I'm going to add this text into an internal datatable.
My code, so far, looks like this:
private void button1_Click(object sender, EventArgs e)
{
// Create an instance of the Open File Dialog Box
var openFileDialog1 = new OpenFileDialog();
// Set filter options and filter index
openFileDialog1.Filter = "Word Documents (.docx)|*.docx|All files (*.*)|*.*";
openFileDialog1.FilterIndex = 1;
openFileDialog1.Multiselect = false;
// Call the ShowDialog method to show the dialog box.
openFileDialog1.ShowDialog();
txtDocument.Text = openFileDialog1.FileName;
var word = new Microsoft.Office.Interop.Word.Application();
object miss = System.Reflection.Missing.Value;
object path = openFileDialog1.FileName;
object readOnly = true;
var docs = word.Documents.Open(ref path, ref miss, ref readOnly,
ref miss, ref miss, ref miss, ref miss,
ref miss, ref miss, ref miss, ref miss,
ref miss, ref miss, ref miss, ref miss,
ref miss);
// Datatable to store text from Word doc
var dt = new System.Data.DataTable();
dt.Columns.Add("Text");
// Loop through each table in the document,
// grab only text from cells in the first column
// in each table.
foreach (Table tb in docs.Tables)
{
// insert code here to get text from cells in first column
// and insert into datatable.
}
((_Document)docs).Close();
((_Application)word).Quit();
}
I'm stuck at the part where I grab the text from each cell and add it to my datatable. Can someone offer me some pointers? I'd sure appreciate it.
Thanks!
I don't know how you would like to store it in your database, but to read the text I think you could loop out the rows and pick the first column in each:
foreach (Table tb in docs.Tables) {
for (int row = 1; row <= tb.Rows.Count; row++) {
var cell = tb.Cell(row, 1);
var text = cell.Range.Text;
// text now contains the content of the cell.
}
}

Adding formula to Excel with C# - making the formula shown

I wanted to add formulas to an Excel workSheet.
I managed to do so with the Formula property.
The problem is that when I open the worksheet in Excel, I can see that the formula works - but I can only see the result in the cell. I can't see the formula that was calculated in the Formula Bar at the top of Excel.
Obviously if I enter a formula in Excel itself I can see the result in the cell and the formula in the Formula Bar.
Some of my code:
for (int i = 0; i < nOfColumns / 3; i++)
{
Range cells = workSheet.Range[workSheet.Cells[2, i * 3 + 3], workSheet.Cells[lastRowNumber, i * 3 + 3]];
cells.FormulaR1C1 = "=IF(EXACT(RC[-2],RC[-1]),TRUE,ABS(RC[-2]/RC[-1]-1))";
}
below is a test code. even after I save the workbook - the FormulaHidden is false and I can successfully retrieve the formula insterted. really frustrated
Microsoft.Office.Interop.Excel.Application excelApp = null;
Workbooks workBooks = null;
Workbook workBook = null;
Worksheet workSheet;
try
{
excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.DisplayAlerts = false;
workBooks = excelApp.Workbooks;
workBook = workBooks.Open(filePath, AddToMru: false);
workSheet = workBook.Worksheets.get_Item(1);
int nOfColumns = workSheet.UsedRange.Columns.Count;
int lastRowNumber = workSheet.UsedRange.Rows.Count;
Range rng = workSheet.Range["C1"];
rng.Formula = "=SUM(B2:B4)";
String formula = rng.Formula; //retrieve the formula successfully
rng.FormulaHidden = false;
workSheet.Unprotect();
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlExclusive);
formula = rng.Formula; //retrieve the formula successfully
bool hidden = rng.FormulaHidden;
}
catch (Exception e)
{
throw;
}
finally
{
if (workBook != null)
{
workBook.Close();
workBook = null;
}
if (workBooks != null)
{
workBooks.Close();
workBooks = null;
}
if (excelApp != null)
{
excelApp.Quit();
excelApp = null;
}
}
}
Anyone know how to make the formula shown, when adding the formulas programatically ?
finally !!! figured it out. this behavior is caused by the SaveAs flags.
changed
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlExclusive);
to
workBook.SaveAs(filePath, AccessMode: XlSaveAsAccessMode.xlShared);
now the only thing left is to understand what exactly is the different between the two flags. :)
Hiding the formula (by checking Hidden checkbox on Format Cells dialog) & protecting the worksheet (thereafter) will cause the formula to not show in the formula bar.
Example VBA code
Range("C1").FormulaHidden = True 'set this property to false to make formula visible.
Sheet1.Protect
EDIT: In order to see the formula in the formula bar
Range("C1").FormulaHidden = False
Sheet1.Unprotect
Go to the Formula tab on the tool bar, and click "Show Formulas".
I think localization could be involved in this weird behaviour.
Some time ago, working in Excel, I had the impression that formulas got stored in localized language (I was using italian), then undergo a conversion when compiled. This could make sense, because localized constants are an essential part of the spreadsheet data.
I'm sorry I haven't now Excel available, so I can't be more precise, but I think you could try to localize to english your spreadsheet, or set the formula text in your local language.

How to "clean up" Microsoft.Office.Interop.Excel.Workbook

So I have a Microsoft.Office.Interop.Excel.Workbook object. It basically uses a template Excel file to construct itself. The Excel file contains a template column color for the results section, etc. and then the code basically just prints over those template columns, it doesn't actually customize the look of the file itself, only puts the data into it.
However, this is an issue because after it's done, our template accounts for the most POSSIBLE rows it can, but a lot of the times (most of the time), we use not even half of them.
What's the easiest way to remove all rows that DO NOT have cell data in them after the file has been created, working directly with the Microsoft.Office.Interop.Excel.Workbook object. We already have a "cleanup" method that runs after creation, but I want to add that logic to it. Here's our current cleanup:
private void CleanupExcel()
{
if (!_visible && _workbook != null)
{
_workbook.Close(false, Missing.Value, Missing.Value);
}
_workbook = null;
_sheet = null;
if (_excel != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(_excel);
// WW, 5/26/09: not sure if a problem here, but it probably is since the code was taken from here
// but in the indicator, Excel exists in the process even after the app is closed. The code here seems to fix it.
GC.Collect();
GC.WaitForPendingFinalizers();
}
_excel = null;
}
P.S. It's the first of two sheets in the document by the way. I also have access to the Microsoft.Office.Interop.Excel.Worksheet object if it's easier to do that way.
Assuming that all the empty rows are at the bottom of the sheets, you should be able to select them as a range and then delete them all, something like this I think:
Excel.Range range = _sheet.get_Range("A501", "A60000");
Excel.Range row = range.EntireRow;
rowDelete(Type.Missing);
If they're not at the bottom, maybe you could do a sort so that they all end up at the bottom and then use something similar to my code.
Try the following. It basically goes through a range (which I've hard-coded to be A1:A10), checks which rows are empty, marks them for deletion, then sweeps though and deletes them.
public void RemoveRows()
{
Excel.Range rng = Application.get_Range("A1", "A10");
List<int> rowsMarkedForDeletion = new List<int>();
for(int i = 0; i < rng.Rows.Count; i++)
{
if(Application.WorksheetFunction.CountA(rng[i + 1].EntireRow) == 0)
{
rowsMarkedForDeletion.Add(i + 1);
}
}
for(int i = rowsMarkedForDeletion.Count - 1; i >= 0; i--)
{
rng[rowsMarkedForDeletion[i]].EntireRow.Delete();
}
}
To give full credit, using COUNTA is a technique I learned from OzGrid.

Categories

Resources