A user has provided me with an Excel document that has textboxes in a few of the cells. I have the usual setup code to load the Excel application, get the worksheet, and then start iterating the used range. When I try to get the value of the cell that contains the textbox, the value is null.
foreach (Range row in usedRange.Rows) {
object[,] valueArray = (object[,])row.get_Value(XlRangeValueDataType.xlRangeValueDefault);
var value = valueArray[1,10]; // This is null for textbox cells
}
Is there a special method I should use to get the value of the textbox that appears in an Excel worksheet?
Edit with fix and explanation
Stewbob's suggestion of iterating the shapes got me in the right direction. But using the following code, I was getting null exceptions:
for (int i=1; i<shapes.Count;i++){
var item = shapes.Range[i].Item(1);
string myString = item.TextFrame2.TextRange.Characters.Text.ToString();
}
After looking at the object in Quickwatch, I noticed something odd about the shape. It was of type msoOLEControlObject. It turns out the values on this Excel document are cut and pasted into Excel from a webpage. Excel was not creating textboxes but OLE boxes. The OLE box did have a 'Value' property so I could access the textboxes value as such:
var shapes = ws.Shapes;
for (int i=1; i<shapes.Count;i++){
var item = shapes.Range[i].Item(1);
var myText = item.OLEFormat.Object;
if (myText.Object != null) {
if (myText.Object.Value != null) {
Console.WriteLine(myText.Object.Value.ToString());
}
}
}
So make sure if you are dealing with pasted objects that you check the value property and not the TextRange property.
If you know the name of the Text Box, you can reference it this way:
ActiveSheet.Shapes.Range(Array("TextBox 1")).Select
If you don't know the name, you can use ActiveSheet.Shapes to iterate through all the shapes on the worksheet.
Getting to the actual text in the TextBox is not very straightforward in VBA. The following code iterates through all the Shape objects on the active worksheet:
Dim shp As Shape
Dim myText As String
For Each shp In ActiveSheet.Shapes
myText = shp.TextFrame2.TextRange.Characters.Text
Next
Though I see that you are working in C#, so the above code will be a little different, but it at least gives you the object model to get to the text inside the TextBox.
Well, the TextBox isn't actually inside any cell (although it may appear to be).
Instead, you have to get it from the Shapes collection in the WorkSheet.
Related
I have multiple text blocks I want to compare the text values of with whatever the current random value is in my array, so I can "gray out" the text block when it gets "opened".
This code works for 1 text block, but I was wondering how I could do it for all 26 of my blocks w/o having to type out each one? Is there a way I can reference all my blocks, kind of like using the same click event for all my buttons?
if (money[turns]==Convert.ToInt32(tb0.Text))
{
tb0.Foreground = Brushes.Gray;
}
As addition to Alexei, if you want only compare specific TextBlock, first you can insert your TextBlock into a List
List<TextBlock> listTextBlock = new List<TextBlock>();
list.Add(tb0);
list.Add(tb1);
... so you just using loop to check each TextBlock.
foreach(TextBlock item in listTextBlock)
{
if (money[turns]==Convert.ToInt32(item.Text))
{
item.Foreground = Brushes.Gray;
}
}
If Windows.Forms you can iterate through all your controls of type TextBox and compare your text to the one in each TextBox. The code should like this:
bool exists = GetAll(this, typeof(TextBox)).Any(t => t.Text.Equals(yourText));
As already suggested, it would be better to use databinding and do the search in your objects, rather than into your interface.
I am using spreadsheetlight library to read Excel sheet(.xslx) values using c#.
I can read the cell value using following code
for (int col = stats.StartColumnIndex; col <= stats.EndColumnIndex; col++)
{
var value= sheet.GetCellValueAsString(stats.StartRowIndex, col); //where sheet is current sheet in excel file
}
I am getting the cell value. But how can I get the data type of the cell? I have checked in documentation but didn't find the solution.
Note: For .xls type of excel files i am using ExcelLibrary.dll library where i can easily get the datatype of cells using below code
for (int i = 0; i <= cells.LastColIndex; i++)
{
var type = cells[0, i].Format.FormatType;
}
but there is no similar method in spreadsheetlight.
Here is the answer from the developer Vincent Tang after I asked him as I wasn't sure how to use DataType:
Yes use SLCell.DataType. It's an enumeration, but for most data, you'll be working with Number, SharedString and String.
Text data will be SharedString, and possibly String if the text is directly embedded in the worksheet. There's a GetSharedStrings() or something like that.
For numeric data, it will be Number.
For dates, it's a little tricky. The data type is also Number (ignore the Date enumeration because Microsoft Excel isn't using it). For dates, you also have to check the FormatCode, which is in the SLStyle for the SLCell. Use the GetStyles() to get a list. The SLCell.StyleIndex gives you the index to that list.
For example, if your SLCell has cell value "15" and data type SharedString, then look for index 15 in the list of shared strings. If it's "blah" with String data type, then that's it.
If it's 56789 with Number type, then that's it.
Unless the FormatCode is "mm-yyyy" (or some other date format code), then 56789 is actually the number of days since 1 Jan 1900.
He also recommended using GetCellList() in order to obtain the list of SLCell objects in the sheet. However, for some reason that function was not available in my version of SL, so I used GetCells() instead. That returns a dictionary of SLCell objects, with keys of type SLCellPoint.
So for example to get the DataType (which is a CellValues object) of cell A1 do this:
using (SLDocument slDoc = new SLDocument("Worksheet1.xlsx", "Sheet1")) {
slCP = SLCellPoint;
slCP.ColumnIndex = SLConvert.ToColumnIndex("A"); ///Obviously 1 but useful function to know
slCP.RowIndex = 1;
CellValues slCV = slDoc.GetCells(slCP).DataType;
}
By the way, I also had a problem with opening the chm help file. Try this:
Right Click on the chm file and select properties
Click on Unblock button at the bottom of the General Tab
To get the value of the cell try following code
var cellValue = (string)(excelWorksheet.Cells[10, 2] as Excel.Range).Value;
Use this link for more details
Check out the SLCell.DataType Property. Spreadsheetlight documentation mentions that this returns the Cell datatype, in class Spreadsheetlight.SLCell
public CellValues DataType { get; set; }
PS: On a side note, I figured out how to open the chm documentation. Try opening the chm file in Winzip, it opens without any issues.
Hope it helps. Thanks
Well, After a lot of trail and error methods i got to find a solution for this.
Based on the formatCode of a cell we can decide the formatType of the cell.
Using GetCellStyle method we can get the formatcode of the cell. Using this formatCode we can decide the formatType.
var FieldType = GetDataType(sheet.GetCellStyle(rowIndex, columnIndex).FormatCode);
private string GetDataType(string formatCode)
{
if (formatCode.Contains("h:mm") || formatCode.Contains("mm:ss"))
{
return "Time";
}
else if (formatCode.Contains("[$-409]") || formatCode.Contains("[$-F800]") || formatCode.Contains("m/d"))
{
return "Date";
}
else if (formatCode.Contains("#,##0.0"))
{
return "Currency";
}
else if (formatCode.Last() == '%')
{
return "Percentage";
}
else if (formatCode.IndexOf("0") == 0)
{
return "Numeric";
}
else
{
return "String";
}
}
This method worked for 99% of the cases.
Hope it helps you.
I have multiple XAML TextBoxes, each of which manipulate a corresponding value in an array, when the value in the TextBox is changed, using a C# method which dynamically checks which TextBox has called the method.
<TextBox x:Name="_0_0" TextChanged="_x_y_TextChanged"/>
<TextBox x:Name="_0_1" TextChanged="_x_y_TextChanged"/>
<TextBox x:Name="_0_2" TextChanged="_x_y_TextChanged"/>
// And so on.....
each of which manipulate a corresponding value in an array, when the value in the TextBox is changed, using a C# method which dynamically checks which TextBox has called the method.
private void _x_y_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox current = (TextBox)sender;
string currentname = current.Name;
string rowstring = currentname.Substring(1, 1);
string columnstring = currentname.Substring(3, 1);
int row = Convert.ToInt32(rowstring);
int column = Convert.ToInt32(columnstring);
// I've then detected the name of the textbox which has called it...
So this information can be used to dynamically store information from a TextBox in a corresponding array index - or whatever you want to do with it...
My question however, is:
How can I create a method which uses index locations in my array, to call the relevant TextBox and update its text?
Use FindName(string) to find the text box by name as follows (where container is a control that contains all of the text boxes):
private void UpdateTextBox(int row, int column, string text)
{
TextBox textBox = container.FindName("_" + row + "_" + column) as TextBox;
if(textbox != null)
{
textbox.Text = text;
}
}
There are two ways you might go:
If you have a lot of data to manage, or if you can't predict the length of the array, it would be better to bind to a collection instead of manually poking data into and out of an array. If you create a class derived from ObservableCollection instead of using an array the data <> ui relationship is pretty trivial.
if you really need to do this manually, maybe it would be better to stick the index into the 'tag' field of your text boxes. You could (a) see it clearly in your xaml, (b) parse it easily and (c) if you used a variation on the formula here:
Find all controls in WPF Window by type
you could iterate over the textboxes in window and find the right one by looking at its tag index:
foreach (TextBox t in FindVisualChildren<TextBox>(this))
{
if ((int) t.Tag) == my_index )
{
t.Text = "my_text_goes_here";
}
}
I would go in the direction of the answer I gave on this question:
form anchor/dock
In short, I would create a class that holds the actual values and then create a collection that holds information classes.
Then I would not use the event "TextChanged" on the TextBoxes, rather "sniff" for changes on the Dependency Property used to hold the text. This can easily be done in the Dependency Property.
Last, I would use an ItemsControl or ItemsPresenter to show the controls. Number of controls will follow number of items in the collection.
I suggest using MVVM pattern, data template, and ItemsControl for handling this problem effectively.
I was searching for some solutions but failed to find one, currently I have a problem on reading an excel file using OpenXML. With perfect data, there won't be any problem, but with data with blanks, the columns seems to be moving to the left, producing an error saying that the index was not right since it actually moved to the left. I found a solution wherein you can place in cells in between, but when I tried it, an error saying that an object reference was not set to an instance of an object while reading the certain cell with this code (source is from the answer in here for inserting cells How do I have Open XML spreadsheet "uncollapse" cells in a spreadsheet?)
public static string GetCellValue(SpreadsheetDocument document, Cell cell)
{
SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart;
string value = cell.CellValue.InnerXml;
if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
{
return stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;
}
else if (cell == null)
{
return null;
}
else
{
return value;
}
}
any other ways wherein I can read blank cells as blank without moving the data to the left?
All help will be appreciated! :)
Thanks!
In Open XML, xml file does not contain an entry for the blank cell that's why blank cells are skipped. I faced the same problem. The only solution is apply some logic.
For Example:
When we read a cell we can get its ColumnName (A,B,C etc.) by the following code
string cellIndex = GetColumnName( objCurrentSrcCell.CellReference );
where
public static string GetColumnName(string cellReference)
{
// Create a regular expression to match the column name portion of the cell name.
Regex regex = new Regex("[A-Za-z]+");
Match match = regex.Match(cellReference);
return match.Value;
}
you can store these cells in a Hashtable where key can be the cell ColumnName and value can be the object of the cell. And when writing fetch cells from the hash object serially on some basis or your logic like...
you may loop from A to Z and read the cells at particular key like
if(objHashTable.Contains(yourKey))
{
Cell objCell = (Cell) objHashTable[yourKey];
//Insertcell or process cell
}
else
{
//do process for the empty cell like you may add a new blank cell
Cell objCell = new Cell();
//Insert cell or process cell
}
This is the only way to work with open xml. adding a blank cell during reading is a waste of time. You can add more logic according to you
try this. this will definitely work. or if you find a better solution, do tell me
Have a nice day :)
I'm trying to read data from an Excel document in C# using Microsofts COM Interop.
So far, I'm able to load the document and read some data from it. However, I need to read data from two different columns and output these as json (for a jquery ajax call)
I've made a quick prototype of how my Excel document is structured with the hope that it's a bit easier to explain ;-)
The method I have is called GetExcelDataByCategory(string categoryName) where the categoryName parameter would be used to find which column to get the data from.
So, i.e., if I'm making the call with "Category 2" as parameter, I need to get all the values in the C columns rows and the corresponding dates from the A column, so the output will look like this:
Which then needs to be transformed/parsed into JSON.
I've searched high and low on how to achieve this, but with no luck so far :-( I'm aware that I can use the get_Range() method to select a range, but it seems you need to explicitly tell the method which row and which column to get the data from. I.e.: get_Range("A1, C1")
This is my first experience with reading data from an Excel document, so I guess there's a lot to learn ;-) Is there a way to get the output on my second image?
Any help/hint is greatly appreciated! :-)
Thanks in advance.
All the best,
Bo
This is what I would do:
using Excel = Microsoft.Office.Interop.Excel;
Excel.Application xlApp = new Excel.Application();
Excel.Workbook xlWorkbook = xlApp.Workbooks.Open("path to book");
Excel.Worksheet xlSheet = xlWorkbook.Sheets[1]; // get first sheet
Excel.Range xlRange = xlSheet.UsedRange; // get the entire used range
int numberOfRows = xlRange.Rows.Count;
int numberOfCols = xlRange.Columns.Count;
List<int> columnsToRead = new List<int>();
// find the columns that correspond with the string columnName which
// would be passed into your method
for(int i=1; i<=numberOfCols; i++)
{
if(xlRange.Cells[1,i].Value2 != null) // ADDED IN EDIT
{
if(xlRange.Cells[1,i].Value2.ToString().Equals(categoryName))
{
columnsToRead.Add(i);
}
}
}
List<string> columnValue = new List<string>();
// loop over each column number and add results to the list
foreach(int c in columnsToRead)
{
// start at 2 because the first row is 1 and the header row
for(int r = 2; r <= numberOfRows; r++)
{
if(xlRange.Cells[r,c].Value2 != null) // ADDED IN EDIT
{
columnValue.Add(xlRange.Cells[r,c].Value2.ToString());
}
}
}
This is the code I would use to read the Excel. Right now it reads every column that has the heading (designated by whatever is in the first row) and then all the rows there. It isn't exactly what you asked (it doesn't format into JSON) but I think it is enough to get you over the hump.
EDIT: Looks like there are a few blank cells that are causing problems. A blank cell will be NULL in the Interop and thus we get errors if we try to call Value2 or Value2.ToString() since they don't exist. I added code to check to make sure that the cell isn't null before doing anything with it. It prevent the errors.
for Excel-parsing and creation you can use ExcelDataReader: http://exceldatareader.codeplex.com/
and for json you can use json.net: http://json.codeplex.com/
Both are fairly easy to use. Just have a look at the websites.