Setting correct printer in MS Word through automation - delphi

I have the following automation code:
lPrintSetup := fWordObject.Application.Dialogs.Item(wdDialogFilePrintSetup);
lPrintSetup.Printer := 'MyPrinter';
lPrintSetup.DoNotSetAsSysDefault := True;
lPrintSetup.Execute;
lPrintSetup := Null;
The Printer property is giving me some problems, sometimes Execute crashes with an EOleException (0x800A1460 (error code 5216): There is a printer error) because of a wrong printername.
I have printer information of all printers in a _PRINTER_INFO_2 record which I obtained by a EnumPrinters API-call. How can I compose the right printername for Word given the information in a _PRINTER_INFO_2 record? It has work with at least Windows 2000, Word 2000 and Citrix.
Some background info:
Our application first filled the Printername with a self constructed printername. This gave problems with Citrix clients, so for Citrix clients we took the _PRINTER_INFO_2.pPortName and deleted the Client:#: part.
This is working for the majority of our customers, but sometimes still the printer error shows up.
What I have tried so far (on Windows XP SP3, Word 2007):
Just take the _PRINTER_INFO_2.pPrinterName. Problem is that when you modify printernames on purpose (renaming 'PDFCreator' to 'HP DESKJET 520 on MYPC') it crashes on the latter (while selecting this printer in Word works).
Composing a printername like this: lPrintSetup.Printer := PRINTER_INFO_2.pPrinterName + ' on ' + PRINTER_INFO_2.pPortname. Seems to work always! But googling around showed that ' on ' is localized, so I'm not sure if that's going to work on non-english Windows versions. Edit: does not work always :(
Another solution I found on the web:
When reading the printername from Word it has the form of "Printername on Ne01:", where Ne01 is ranged from Ne00: to Ne99:. The solution suggested taking the printername and just try to set it while looping from Ne00: to Ne99:. When .Execute doesn't crash, you've got the right one. I'm not very fond of this 'trail and error' method.

I'm not sure if you've tried this, or if its of any use, but you can get a list of all the printers on the system from the Printer.Printers object make sure you add Printers to the Uses clause of your unit.
This should then list the actual names on the system and you may be able to use this information to do what you want.

As stated you can get a list of printer names using the Printer.Printers which is a TStringList with the name of the printer on each item.
This code gives the default printer name
Printer.Printers[Printer.PrinterIndex]

Some minutes ago I learned, that word2k not only wants Printernames like "Printername on Ne01:" it only wants the port (NEnn) uppercase "Printername on NE01:"

I figured it out. Word has the printername in the form of "Printername on NE01:". Ne01: is the printerport as specified in the devices section of win.ini. So now I compose the printername as _PRINTER_INFO_2.pPrinterName + ' on ' + <PrinterPort from win.ini> and set that name for the printer property of the FilePrintSetup dialog.
This is much better than to resort to the trail-and-error method mentioned in my question.

Related

Printing a Crystal Report directly to printer

I am creating a label printing function in a program that needs to create labels for the given information. I have created a label in Crystal Reports 9 but I'm having trouble printing it.
I don't want to save the label, I just want it to print directly after the system has created it.
Dim ap9 As craxdrt.Application
Dim rpt9 As craxdrt.Report
Dim dbt As craxdrt.DatabaseTable
Set ap9 = New craxdrt.Application
On Error GoTo errError2
Set iniFile = New CIniFile
On Error GoTo errError3
Set rpt9 = ap9.OpenReport(iniFile.pathReports & REPORT_LABEL_IN)
On Error GoTo errError4
For Each dbt In rpt9.Database.Tables
dbt.Location = iniFile.pathDbCosmet
If dbt.ConnectionProperties.count <= 5 Then
dbt.ConnectionProperties.Add "Database Password", iniFile.passwordCosmet
End If
Next
rpt9.RecordSelectionFormula = sFormula
rpt9.PrintOut False, CInt(txtPacksReceived.Text)
The following code allows me to select a printer
and after clicking 'Print' at that point I am shown another dialog
However, the code executes fine, there are no errors, but the print queue doesn't show any documents and the report doesn't print.
Is there some reason why I'm not able to print my labels?
I've had problems with Zebra printers in the past that all turned out to be driver related. Have you uninstalled the printer driver and reinstalled it? Otherwise try unplugging and removing the device, plugging it into a different port and trying again?
Most likely your report doesn't contain any data - some logical error in selection/suppression formulas or similar.
If you print to any other printer, does something print out? Tracing SQL (assuming your report is bound to SQL server), can you see issued query? Does it look correct?

Error loading file with full name containing spaces in directory with delphi

I am using XE8, win 8.1.
When trying load a file with spaces in directory, I am getting a exception of syntax name of the file or directory is invalid.
If I use imageen dialog to preview the file, no erros are found.
I did two tests with the procedure load_file1 and load_file2 and I have the same problem.
Is there a wrokaround to solve it?
function get_file:string;
begin
result:='"C:\Compartilhada\dicomserver versoes\dicomserverx\data\Genesis-1000\1.2.410.200013.1.215.1.200912141600580009_0001_000001_13061821270002.dcm"'
end;
procedure load_file1;
var fStm:Tstream;
p1:string;
begin
p1:=get_file;
fStm := tFileStream.Create( p1, fmOpenRead or fmShareDenyNone ); //->Error Here
try
TBlobField(FieldByName('dicom')).LoadFromStream(fStm);
Post;
finally
fSTm.Free;
end;
end;
procedure load_file2;
p1:string;
begin
p1:=get_file;
TBlobField(FieldByName('dicom')).LoadFromFile(p1); //-->Error Here
Post;
end;
Remove the double quote marks from your string. It should be:
'C:\Compartilhada\dicomserver versoes\dicomserverx\data\Genesis-1000\1.2.410.200013.1.215.1.200912141600580009_0001_000001_13061821270002.dcm'
You might use " for paths containing spaces in some situations, for instance a command interpreter. But at the API level, it is simply not needed. And indeed it is a mistake as you have discovered. The double quote character " is actually a reserved character in a file name. That is documented on MSDN:
Naming Files, Paths, and Namespaces: Naming Conventions
The following fundamental rules enable applications to create and process valid names for files and directories, regardless of the file system:
...
Use any character in the current code page for a name, including Unicode characters and characters in the extended character set (128–255), except for the following:
The following reserved characters:
< (less than)
> (greater than)
: (colon)
" (double quote)
/ (forward slash)
\ (backslash)
| (vertical bar or pipe)
? (question mark)
* (asterisk)
...
...
In comments below you indicate that the code in the question does not reflect your actual problem. Which makes me wonder how you expect us to help. Your real problem is not the error message produced by the specific code, but that your debugging skills are letting you down. Let me try to explain how to debug a problem like this.
First of all, you are passing a file name to LoadFromFile or TFileStream.Create. These calls fail with an error that indicates that the file name is not valid.
So, when faced with that knowledge, the first step is to check the value of the file name that you are passing. Use debugging techniques to do that. Either the IDE debugger, or logging.
Once you have identified what value you are actually passing to these functions you can try to work out what is invalid about it.
To repeat, your real problem is not with the specifics, but in your debugging skills. You should take this as an opportunity to learn more about debugging. Stack Overflow is not a substitute for debugging. Learn to debug better, and your life as a programmer will become very much easier.

Decimal rounding and printer selection

I use Delphi RAD Studio 2010 and DecimalRounding_JH1.pas from http://cc.embarcadero.com/item/21909.
It works good, but I don't know why, in some old machines (Pentium IV with Windows XP SP3), the round fails after access to printer.printerindex. I have checked that the problem not is Windows XP due it works in other machines with this OS.
I have made a simple project that round an extended value with DecimalRounding_JH1 (drHalfUp) with two decimals (1.105 -> rounds to 1.11). But If I read printer.printerindex, then 1.105 rounds to 1.10).
I thought it could be the "FDIV bug", but compiling with "FDIV safe" doesn't resolve the problem.
The code:
var d1,d2:extended;
i:integer;
begin
d1:=1.105;
d2:=DecimalRounding_JH1.DecimalRoundExt(d1,2,drHalfUp);
memo1.lines.add(FloatToStr(d2)); // --> shows 1.11 (OK)
i:=Printer.printerindex;
d2:=DecimalRounding_JH1.DecimalRoundExt(d1,2,drHalfUp);
memo1.lines.add(FloatToStr(d2)); // --> shows 1.10 (ERROR!!!)
...
I know that it is very strange, but I've tested it and It's as I said.
What could I do?
Edited:
If I add Printer.printerindex:=1; (for example) before i:=Printer.printerindex; then again it works good. Reading printer unit, the difference is about execute "SetToDefaultPrinter" or not:
function TPrinter.GetPrinterIndex: Integer;
begin
if FPrinterIndex = -1 then SetToDefaultPrinter;
Result := FPrinterIndex;
end;
thanks in advance.
Certain parts of the system printer libraries have a rather nasty habit of modifying the 8087 control word. You should restore it to its default value after using methods and properties of Printer.
For example, you might write it like this:
Set8087CW(Default8087CW);
The comments in my codebase suggest that you only need to do this after the VCL printer code has been initialized for the first time. So you could deal with this in your program's startup. Read Printer.PrinterIndex and then immediately set the control word to its desired value.

Delphi: MAPILOGON ErrorCode 1

I tried with many versions of MAPISend, but I everytime got error in one place.
The MAPILogon returns with errorcode 1.
dwRet := MapiLogon(Handle,
nil,
nil,
MAPI_DIALOG or MAPI_NEW_SESSION,
0, #MAPI_Session);
I tried with "MAPISend component", this code:
http://prog.hu/tudastar/60044-6/Delphi-Email+csatolt+file+thunderbird.html
and 2 of others.
Interesting, that Acrobat Reader CAN use the MAPI with Attach to email function, and the "Send" "In Mail" context menu also working.
I don't understand why it isn't working, in my machine (Win7) it is working fine.
Then machines where I failed have WinXP OS, and they used Thunderbird.
What I can do to successfully logon into MAPI?
THanks:
dd
I think I found the problem.
The problem, that Delphi needs a Registry value named MAPI under
"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Messaging Subsystem"
If this not present, it send 1 error code to you what is the base of the confusion.
This value must be string. The sysadmin wrote DWORD, and this caused the problem.
Thanks for your help:
dd
I am using RapWare components, http://www.rapware.nl/
Hth's.
Stanko.
I'm not sure about any Delphi-specific issues, but you don't need to call MAPILogon before calling MAPISendMail. If you do, I wouldn't check the return value. That's why Acrobat Reader was working and your SMAPI client was not.

Find out what process registered a global hotkey? (Windows API)

As far as I've been able to find out, Windows doesn't offer an API function to tell what application has registered a global hotkey (via RegisterHotkey). I can only find out that a hotkey is registered if RegisterHotkey returns false, but not who "owns" the hotkey.
In the absence of a direct API, could there be a roundabout way? Windows maintains the handle associated with each registred hotkey - it's a little maddening that there should be no way of getting at this information.
Example of something that likely wouldn't work: send (simulate) a registered hotkey, then intercept the hotkey message Windows will send to the process that registered it. First, I don't think intercepting the message would reveal the destination window handle. Second, even if it were possible, it would be a bad thing to do, since sending hotkeys would trigger all sorts of potentially unwanted activity from various programs.
It's nothing critical, but I've seen frequent requests for such functionality, and have myself been a victim of applications that register hotkeys without even disclosing it anywhere in the UI or docs.
(Working in Delphi, and no more than an apprentice at WinAPI, please be kind.)
One possible way is to use the Visual Studio tool Spy++.
Give this a try:
Run the tool (for me, it's at C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\spyxx_amd64.exe or you can download it). Note: there is spyxx.exe (32-bit version) and spyxx_amd64.exe (64-bit version) - if you don't see anything in 64-bit use the 32-bit version (ie.catches messages only in same architecture)
In the menu bar, select Spy -> Log messages... (or hit Ctrl + M)
Check All Windows in System in the Additional Windows frame
Switch to the Messages tab
Click the Clear All button
Select WM_HOTKEY in the listbox, or check Keyboard in Message Groups (if you're OK with more potential noise)
Click the OK button
Press the hotkey in question (Win + R, for example)
Select the WM_HOTKEY line in the Messages (All Windows) window, right click, and select Properties... in the context menu
In the Message Properties dialog, click the Window Handle link (this will be the handle for the window that received the message)
Click the Synchronize button on the Window Properties dialog. This will show the window in the main Spy++ window treeview (if it's windows itself or some popup application it shows nothing).
On the Window Properties dialog, select the Process tab
Click the Process ID link. This will show you the process (In my Win + R case: EXPLORER)
Your question piqued my interest, so I've done a bit of digging and while, unfortunately I don't have a proper answer for you, I thought I'd share what I have.
I found this example of creating keyboard hook (in Delphi) written in 1998, but is compilable in Delphi 2007 with a couple of tweaks.
It's a DLL with a call to SetWindowsHookEx that passes through a callback function, which can then intercept key strokes: In this case, it's tinkering with them for fun, changing left cursor to right, etc. A simple app then calls the DLL and reports back its results based on a TTimer event. If you're interested I can post the Delphi 2007 based code.
It's well documented and commented and you potentially could use it as a basis of working out where a key press is going. If you could get the handle of the application that sent the key strokes, you could track it back that way. With that handle you'd be able to get the information you need quite easily.
Other apps have tried determining hotkeys by going through their Shortcuts since they can contain a Shortcut key, which is just another term for hotkey. However most applications don't tend to set this property so it might not return much. If you are interested in that route, Delphi has access to IShellLink COM interface which you could use to load a shortcut up from and get its hotkey:
uses ShlObj, ComObj, ShellAPI, ActiveX, CommCtrl;
procedure GetShellLinkHotKey;
var
LinkFile : WideString;
SL: IShellLink;
PF: IPersistFile;
HotKey : Word;
HotKeyMod: Byte;
HotKeyText : string;
begin
LinkFile := 'C:\Temp\Temp.lnk';
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IShellLink, SL));
// The IShellLink implementer must also support the IPersistFile
// interface. Get an interface pointer to it.
PF := SL as IPersistFile;
// Load file into IPersistFile object
OleCheck(PF.Load(PWideChar(LinkFile), STGM_READ));
// Resolve the link by calling the Resolve interface function.
OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_NO_UI));
// Get hotkey info
OleCheck(SL.GetHotKey(HotKey));
// Extract the HotKey and Modifier properties.
HotKeyText := '';
HotKeyMod := Hi(HotKey);
if (HotKeyMod and HOTKEYF_ALT) = HOTKEYF_ALT then
HotKeyText := 'ALT+';
if (HotKeyMod and HOTKEYF_CONTROL) = HOTKEYF_CONTROL then
HotKeyText := HotKeyText + 'CTRL+';
if (HotKeyMod and HOTKEYF_SHIFT) = HOTKEYF_SHIFT then
HotKeyText := HotKeyText + 'SHIFT+';
if (HotKeyMod and HOTKEYF_EXT) = HOTKEYF_EXT then
HotKeyText := HotKeyText + 'Extended+';
HotKeyText := HotKeyText + Char(Lo(HotKey));
if (HotKeyText = '') or (HotKeyText = #0) then
HotKeyText := 'None';
ShowMessage('Shortcut Key - ' + HotKeyText);
end;
If you've got access to Safari Books Online, there is a good section about working with shortcuts / shell links in the Borland Delphi 6 Developer's Guide by Steve Teixeira and Xavier Pacheco. My example above is a butchered version from there and this site.
Hope that helps!
After some research, it appears that you'd need to get access to the internal structure that MS uses to store the hotkeys. ReactOS has a clean room implementation that implements the GetHotKey call by iterating an internal list and extracting the hotkey that matches the parameters to the call.
Depending on how close ReactOS' implementation is to the MS implementation, you may be able to poke around in memory to find the structure, but that's over my head...
BOOL FASTCALL
GetHotKey (UINT fsModifiers,
UINT vk,
struct _ETHREAD **Thread,
HWND *hWnd,
int *id)
{
PHOT_KEY_ITEM HotKeyItem;
LIST_FOR_EACH(HotKeyItem, &gHotkeyList, HOT_KEY_ITEM, ListEntry)
{
if (HotKeyItem->fsModifiers == fsModifiers &&
HotKeyItem->vk == vk)
{
if (Thread != NULL)
*Thread = HotKeyItem->Thread;
if (hWnd != NULL)
*hWnd = HotKeyItem->hWnd;
if (id != NULL)
*id = HotKeyItem->id;
return TRUE;
}
}
return FALSE;
}
I presume this thread on sysinternals was asked by someone related to this question, but I thought I'd link to it anyway to keep the two together. The thread looks very intriguing, but I suspect that some deep dive spelunking would need to happen to figure this out without access to the MS internals.
Off the top of my head, you might try enumerating all windows with EnumWindows, then in the callback, send WM_GETHOTKEY to each window.
Edit: Apparrently I was wrong about that. MSDN has more information:
WM_HOTKEY is unrelated to the WM_GETHOTKEY and WM_SETHOTKEY hot keys. The WM_HOTKEY message is sent for generic hot keys while the WM_SETHOTKEY and WM_GETHOTKEY messages relate to window activation hot keys.
Note: Here is a program purporting to have the functionality you are looking for. You could try decompiling it.
Another thread mentions a global NT level keyboard hook:
Re-assign/override hotkey (Win + L) to lock windows
maybe you can get the handle of the process that called the hook that way, which you can then resolve to the process name
(disclaimer: I had it in my bookmarks, haven't really tried/tested)
I know you can intercept the stream of messages in any window within your own process - what we used to call subclassing in VB6. (Though I do not remember the function, perhaps SetWindowLong?) I am unsure if you can do this for windows outside your own process. But for the sake of this post lets assume you find a way to do that. Then you can simply intercept the messages for all top level windows, monitor for the WM_HOTKEY message. You wouldn't be able to know all the keys right off the bat, but as they were pressed you could easily figure out what application was using them. If you persisted your results to disk and reloaded each time your monitor application was run you could increase the performance of your application over time.
This doesn't exactly answer the part of the question that is about the Windows API, but it answers the part of the question that is about a list of global hotkeys and the applications that "own" them.
The free Hotkey Explorer at http://hkcmdr.anymania.com/ shows a list of all global hotkeys and the applications that own them. This just has helped me figure out why an application-specific shortcut key stopped working and how to fix it (by reconfiguring the registered global hotkey in the app that had it registered), within a few seconds.

Resources