I'm using the COM interface to Excel and I would like to get hold of the formatted textual representation of a cell rather than the true underlying value.
For example, suppose the cell contains the number 1.23456
and the user has specified the number format with 1 decimal place. Then I'd like to be able to read the string "1.2"
. I know that I can use Range.Text
but this fails in a couple of significant ways. Range.Text
returns what the user sees in the view of the sheet and so if the cell is hidden then the empty string is returned. If the cell's width is low then a truncated string is returned. Range.Text
also falls down because it is limited to 1024 characters.
Another use case is when the cell evaluates to an error, e.g. #DIV/0!
, #NAME?
, #REF!
etc. I know that I can read Range.Value
and test if the variant is type varError
(I'm using Delphi, in VBA it would be vbError
). What I can't figure out is how to get hold of the text representation #DIV/0!
etc. Again Range.Text
returns this, but not if the cell is hidden or is too narrow.
EDIT
I think that the limit on Range.Text is actually 255 characters.
Building on Steven's answer: try this VBA code.
It does not handle the Accounting format properly because of the lining-up requirement, but its not clear what you would want to do in that case anyway.
Sub testing()
Dim oRng As Range
Dim var As Variant
Set oRng = Range("a3")
If IsError(oRng) Then
var = cstrError(oRng.Value)
Else
var = oRng.Value2
If IsNumeric(var) Then var = Format(var, oRng.NumberFormatLocal)
End If
MsgBox Len(var) & " " & var
End Sub
Function cstrError(vError As Variant) As String
Select Case CLng(vError)
Case xlErrDiv0
cstrError = "#DIV/0!"
Case xlErrNA
cstrError = "#N/A"
Case xlErrName
cstrError = "#NAME?"
Case xlErrNull
cstrError = "#NULL!"
Case xlErrNum
cstrError = "#NUM!"
Case xlErrValue
cstrError = "#VALUE!"
Case xlErrRef
cstrError = "#REF!"
Case Else
cstrError = "#N/A"
End Select
End Function
To get the text of a hidden cell that does not contain an error:
Application.WorksheetFunction.Text(the_cell.Value, the_cell.NumberFormat)
If it's an error, this fails. So you may want to first check:
Application.WorksheetFunction.IsError(the_cell)
Unfortunately, it's difficult to figure out what kind of error you have as the Error.Type
function is not available from VBA or COM object. A work around for this would be to write that formula in another cell on the same sheet and read its value.
Thanks a lot Charles for your answer and helpful comments. I've now pieced together a Delphi/COM version of what I need which is as follows:
function GetCell(const Sheet: ExcelWorksheet; const Row, Col: Integer): string;
function ErrorText(const Cell: ExcelRange; hr: HRESULT): string;
const
ErrorBase=HRESULT($800A0000);
var
i: Integer;
begin
Result := Cell.Text;
for i := 1 to Length(Result) do begin
if Result[i]<>'#' then begin
exit;
end;
end;
if hr=ErrorBase or xlErrDiv0 then begin
Result := '#DIV/0!';
end else if hr=ErrorBase or xlErrNA then begin
Result := '#N/A';
end else if hr=ErrorBase or xlErrName then begin
Result := '#NAME?';
end else if hr=ErrorBase or xlErrNull then begin
Result := '#NULL!';
end else if hr=ErrorBase or xlErrNum then begin
Result := '#NUM!';
end else if hr=ErrorBase or xlErrRef then begin
Result := '#REF!';
end else if hr=ErrorBase or xlErrValue then begin
Result := '#VALUE!';
end else begin
Result := 'an error';
end;
end;
var
Cell: ExcelRange;
hr: HRESULT;
begin
Cell := GetCellAsRange(Sheet, Row, Col);
if VarIsError(Cell.Value, hr) then begin
raise ECellValueError.CreateFmt(
'Cell %s contains %s.',
[R1C1toA1(Row,Col), ErrorText(Cell, hr)]
);
end else if VarIsNumeric(Cell.Value) then begin
Result := Sheet.Application.WorksheetFunction.Text(Cell.Value, Cell.NumberFormatLocal);
end else begin
Result := ConvertToString(Cell.Value);
end;
end;