Excel Add-in - Delphi equivilent to VB - c#

I'm porting an Excel add-in from visual VB to Delphi 2006. Most of it is working but I am stuck on these two VB lines:
oXL.Selection.QueryTable
oXL.Selection <> ""
where oXL is defined as Excel.Application.
In Delphi ExcelApplication.Selection requires an index but in VB it doesn't. I couldn't find anything similar in C# either. I have tried ExcelApplication.ActiveCell which works as long as there is an existing query, otherwise Excel crashes.
Does anyone know what this translates into for Delphi, or C#?
Also if oXL.Selection is an interface, how can you perform oXL.Selection <> ""?
Thank you.

When automating Excel from Delphi using interfaces, a lot of methods take a LCID. You can use LOCALE_USER_DEFAULT for that.
var
oxlSelection: ExcelRange;
ExcelApplication.ActiveCell.QueryTable;
if Supports(fExcelApplication.Selection[LOCALE_USER_DEFAULT], ExcelRange, oxlSelection)
and (VarToStr(oxlSelection.Text) <> '') then
begin
//do something
end;

No worries, I had forgotten that you can just cast the application IDispatch interface to an OleVariant and then call the method.
But what I've done instead is just the following
try
ExcelApplication.ActiveCell.QueryTable.Refresh(False);
except
end;
This seems to be the only way to make it work without crashing excel.

I've run across this problem a lot of times, the solution is very simple.
Always use 0 for the localeID and everything will work as excepted.
This will make Excel fill in its default locale.
ExcelApplication.ActiveCell.QueryTable;
if OleVariant(ExcelApplication.Selection[0]).Value <> '' then .....
You can use variants and then you don't suffer this requirement, but in that case:
your code will run slower (all that variant magic takes time)
you will not have context help on your ExcelApplication methods and properties.
Note that selection, like cells returns a IDispatch, that you have to cast to a Olevariant, in order to work with it.
The same annoying thing happens in VBA, except there the cast is implicit.

Related

Excel-DNA: grouping rows via C API feature of Excel-DNA

I'm familiar with how to group a range in Excel VSTO/COM interop:
ws.EnableOutlining = true;
ws.Outline.SummaryRow = XlSummaryRow.xlSummaryAbove;
var rng = GetRangeSomeHow();
rng.EntireRow.Group();
rng.EntireRow.OutlineLevel = someLevel;
What is the most efficient way to do this in Excel-DNA? I would imagine there must be a C-API way to do it, encapsulated cleverly in Excel-DNA somehow, but for the life of me, I can't figure it out via online documentation (incl. Google).
There's a lot of posts using code similar to my sample above, but these are pretty expensive calls, especially since I need to do this ~5000 times overall (I have a really big data set).
EDIT:
So there seems to be this method call:
XlCall.Excel(XlCall.xlfGroup...)
The only problem is, I have no idea what the parameters are. It seems an ExcelReference should be passed in, but how is the .EntireRow resolved? Will the C API just handle it for me - in which case I just need to pass a new ExcelReference(1,100,1,1) and be done with it... or is there more to this?
Thanks in advance to anyone who can answer my question!
I don't think the C API GROUP function is te one you're looking for. The documentation says:
GROUP
Creates a single object from several selected objects and returns the
object identifier of the group (for example, "Group 5"). Use GROUP to
combine a number of objects so that you can move or resize them
together.
If no object is selected, only one object is selected, or a group is
already selected, GROUP returns the #VALUE! error value and interrupts
the macro.
I'd suggest you use the COM object model for this kind of thing, even in an Excel-DNA add-in. The C API has not really been updated over the years for the general sheet manipulation like this case, so you're likely to run into some features that don't work right or are incomplete relative to the COM object model.
From your Excel-DNA add-in, just make sure your get hold of the right Application root object with a call to ExcelDnaUtil.Application.
For improved performance of this kind of sheet editing, you pretty much have to use the same tricks as from VBA or VSTO - disable screen updating and calculations etc.

Spreadsheet Gear #value when result.Number contains the correct return value

I am working on a project porting VBA code to C# for Spreadsheet Gear. My team has successfully ported around 150 custom Excel Add-in functions. For one of the functions, our regression spreadsheet returns #value for 4 out of 148 function calls, with the remainder returning the expected result. When I step through the code, everything functions as expected and the correct result gets written to result.Number, but resolves to #value in Spreadsheet Gear. I'm totally baffled! Please help me - I don't even know where to start.
The end of the function is as follows:
if (retval < 0)
{
result.Text = UtilFuncs.ErrorNum(retval);
}
else
{
result.Number = retval;
}
retval contains the correct result returned from a call to a separate DLL. And when I hover over result.Number, it also contains the correct result. But I get a #value.
I can provide more code if necessary. My big question is why it works for all but 4 of them.
Without a more concrete example, it's about impossible to provide a definitive answer to this. I'll take a shot in the dark, though.
If you use IArguments methods like IArguments.GetNumber(...),
GetLogical(...), etc., to access your custom function's arguments, you might read through the documentation for these. For instance, see the following remark for GetLogical(...):
Non-logical arguments are converted to a logical value if possible.
Otherwise, false is returned by this method, and an internal flag is
set indicating that an error has occurred, causing the result of the
formula to be an error.
So if you pass in a non-boolean value into an argument where you are expecting a Boolean, your function may end up having this internal "error" flag being set and so result in a #VALUE! error being displayed for this cell, regardless of what you set the result object at the end of your method.
If this is the case, you can use the IArguments.ClearError(...) method to clear the error.

C#->Python SVN Pre-Commit Hook: How to access svn:keywords?

I've been asked to re-add an extra check to our pre-commit hook to ensure svn:keywords are set on certain paths in our repos (e.g. scripts that need the Revision and URL values injecting). We had a clunky pre-commit hook written in C# using SharpSvn doing this originally, but we've migrated our SVN server to Linux, so I rewrote the hook in Python. It has most of the functionality, but I missed out the keyword checking.
In C#, the code used is like so:
SvnPropertyCollection propCollection;
svnLookClient.GetPropertyList(svnHookArgs.LookOrigin, item.Path, out propCollection);
....
if (item.Path.Contains("some path") && !propCollection.Contains("svn:keywords"))
{ /*Fail the commit here*/ }
I've found out the hard way that the transaction properties do not contain svn:keywords, even when I make a commit where all I have done is set them; calling svn.fs.svn_fs_txn_proplist on the transaction gives me the following properties:
svn:log
svn:txn-client-compat-version
svn:txn-user-agent
svn:author
svn:date
My Python code looks like this:
def check_keywords_are_set(transaction, repos_name):
commit_has_source_files = False
source_extensions = ('.sql', '.hlr') #Only care about source files
transaction_root = svn.fs.svn_fs_txn_root(transaction)
changed_paths = svn.fs.paths_changed(transaction_root)
for path, change in changed_paths.iteritems():
if repos_name == 'repo1' or (repos_name == 'repo2' and ('some path' in path): #These are the paths I want to enforce keywords being set on
if path.endswith(source_extensions):
commit_has_source_files = True
if not commit_has_source_files:
return True
#debugging code here:
transaction_props = svn.fs.svn_fs_txn_proplist(transaction)
sys.stdout.write('Transaction prop list:\n{0}\n'.format(transaction_props))
#end debugging
keywords = svn.fs.svn_fs_txn_prop(transaction, 'svn:keywords')
#keywords is always None
Looking back at the C# code, I can see references to svnlook, so I guess I'll have to use this. However, I'm getting confused between 'revision properties' and all other 'properties'. I'm also not sure how to write this so that if a developer is adding the missing keywords to files within a folder that should have them, or we're creating a new folder where this will later be enforced, it won't throw a false negative. The documentation for doing this in Python is quite poor and generally requires reading the C API source, which unfortunately I cannot get my head around (I am not a C developer), let alone translate into Python. Any assistance would be greatly appreciated.

How do I call a COM-function with a _variant_t parameter (type "long")?

I want to port a certain function call to C#. The two lines are as follows:
m_pBrowserApp->get_Document(&pVoid);
m_pLayoutAnalyzer->Analyze4(pVoid, _variant_t(5L));
m_pBrowserApp is the ActiveX browser object and pVoid is its document property. I can get that by calling WebBrowserBase.ActiveXInstance.Document. However, I have no idea how to create a _variant_t(5L) in C#. Since the call is not a VT_BYREF, it "should just work" by calling it like this:
ILayoutAnalyzer2 vips = new LayoutAnalyzer2();
vips.Initialize(0);
SHDocVw.WebBrowser_V1 axBrowser = (SHDocVw.WebBrowser_V1)this.webBrowser1.ActiveXInstance;
var doc = axBrowser.Document as mshtml.HTMLDocument;
vips.Analyze4(doc, (Object)5L); // fails with HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH)
But it doesn't. It fails with a DISP_E_TYPEMISMATCH error.
I'm pretty sure the Document property is valid. So the question remains: How to I properly pass a long wrapped in a variant via interop?
Variants go back to the mid 1990s, a time when longs were consider long for having 32 bits. This is just a few years after the first 32-bit operating systems became available, an integer was still 16 bits in VB6 for example. Not so in C# and .NET in general, a 32-bit programming environment by design that never had to deal with 16-bit back-compat. So use a C# int, not a long.
Drop the L from the literal.

adding stock data to amibroker using c#

I have had a hard time getting and answer to this and i would really , really appreciate some help on this.
i have been on this for over 2 weeks without headway.
i want to use c# to add a line of stock data to amibroker but i just cant find a CLEAR response on how to instantiate it in C#.
In VB , I would do it something like;
Dim AmiBroker = CreateObject("Broker.Application")
sSymbol = ArrayRow(0).ToUpper
Stock = AmiBroker.Stocks.Add(sSymbol)
iDate = ArrayRow(1).ToLower
quote = Stock.Quotations.Add(iDate)
quote.Open = CSng(ArrayRow(2))
quote.High = CSng(ArrayRow(3))
quote.Low = CSng(ArrayRow(4))
quote.Close = CSng(ArrayRow(5))
quote.Volume = CLng(ArrayRow(6))
The problem is that CreateObject will not work in C# in this instance.
I found the code below somewhere online but i cant seem to understand how to achieve the above.
Type objClassType;
objClassType = Type.GetTypeFromProgID("Broker.Application");
// Instantiate AmiBroker
objApp = Activator.CreateInstance(objClassType);
objStocks = objApp.GetType().InvokeMember("Stocks", BindingFlags.GetProperty,null, objApp, null);
Can anyone help me here?
Thanks
The VB code uses something called late binding against a "COM IDispatch" compatible component. Late binding is not supported by C# (up to C# version 3). The C# compiler only compiles code it knows how bind to (called early bind).
To do what you want to do, it would be easier to generate a proxy dll via Visual Studio - select add reference on a project, then select the tab COM, and then search for that ami broker component in the list. This will generate a proxy dll which you can program against using similar code as the one you have showed for VB.
In C# 3.0, you'll discover that you sometimes have to use Type.Missing and that you have to do some additional explicit casting, even though you'd think that it doesn't seem logical.
C# 4.0 has something called dynamic, which allows you to write much cleaner code when accessing COM components.
See my answer here for the code:
https://stackoverflow.com/a/20101274/1581495
I actually use this method now. I save text files from MetaTrader then import them realtime into AmiBroker. Doing it this way is essentially like importing quotes using the ASCII import, so you'll need to make sure that you prepare your import format file. For me, a line of sample data looks like this:
EURAUD,20170607,00:00:00.4885,1.50174,1.50231,1 //Symbol, Date, Time (HH:MM:SS.tttt), Bid, Ask, Volume
I use the default.format file, which looks like this:
$FORMAT TICKER,DATE_YMD,TIME,CLOSE,AUX1,VOLUME
$SEPARATOR ,
$AUTOADD 0
$BREAKONERR 0
$SKIPLINES 0
Find the guide and some examples here on importing and formats:
https://www.amibroker.com/guide/d_ascii.html
EDIT: this might also help with importing
http://www.amibroker.com/kb/2016/01/23/how-to-create-custom-import-definition-for-ascii-importer/

Categories