Rather than use the standard PrintDialog I am building my own dialog.
I want to be able to invoke the Printer driver's own Setup dialog i.e. as if one had clicked on the Properties button from the PrintDialog.
Can you suggest a method of doing this?
I have not used this API before, but it seems to me like you can use the DocumentProperties function for this.
A minimal example (using the default printer):
var
PrinterName: string;
BufLen: Cardinal;
PrinterHandle: THandle;
begin
GetDefaultPrinter(nil, #BufLen);
SetLength(PrinterName, BufLen);
GetDefaultPrinter(PChar(PrinterName), #BufLen);
SetLength(PrinterName, BufLen - 1);
if not OpenPrinter(PChar(PrinterName), PrinterHandle, nil) then
begin
ShowMessage('Could not open printer.');
Exit;
end;
try
DocumentProperties(Handle, PrinterHandle, PChar(PrinterName), nil, nil, DM_IN_PROMPT)
// possibly do other things that might raise an exception
finally
ClosePrinter(PrinterHandle);
end;
The nil pointers can be replaced by DEVMODE structures that contain the initial settings and the settings selected by the user in the GUI, if you also add the corresponding flags. See the documentation for details.
Related
Adding a new ShortCut to the Delphi IDE is not too difficult because the Open Tools API provides a service for this. I am trying something apparently more complex: Add a Wordstar like additional ShortCut:
I want something to happen when the user presses
Shift+Ctrl+H followed by the single key X
where X should work regardless of the state of the Shift key.
This is my code:
procedure TGxKeyboardBinding.BindKeyboard(const BindingServices: IOTAKeyBindingServices);
const
DefaultKeyBindingsFlag = kfImplicitShift + kfImplicitModifier + kfImplicitKeypad;
var
GExpertsShortcut: Byte;
ShiftState: TShiftState;
FirstShortCut: TShortCut;
SecondShortCut: TShortCut;
begin
GExpertsShortcut := Ord('H');
ShiftState := [ssShift, ssCtrl];
FirstShortCut := ShortCut(GExpertsShortcut, ShiftState);
SecondShortCut := ShortCut(Ord('X'), []);
BindingServices.AddKeyBinding([FirstShortCut, SecondShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
end;
So, if I set ShiftState := [ssCtrl] pressing
Ctrl+H X
calls my TwoKeyBindingHandler method.
But with ShiftState := [ssShift, ssCtrl] pressing
Shift+Ctrl+H X
does nothing.
Oddly enough, when specifying ShiftState := [ssShift, ssCtrl] (which should only affect the first key) pressing
Shift+Ctrl+H Shift+X
calls my TwoKeyBindingHandler method, even though the second ShortCut is added without a modifier key.
Any idea? Is this maybe a known limitation/bug of the Delphi IDE/Open Tools API? Is there a known workaround?
I tried it in Delphi 2007 and Delphi 10 Seattle, no difference.
You should be able to do it using the GetKeyState function.
The program has two operations - Think of it as opening a drop down menu item. When ctr-shift-h is pressed your programme will need to flag that the 'Menu' is now open and that subsequent keypresses will either activate an option or close the 'menu' if an invalid key is presses.
function IsKeyDown(const VK: integer): boolean;
begin
IsKeyDown := GetKeyState(VK) and $8000 <> 0;
end;
procedure Form1.OnkeyDown(...)
begin
if Not H_MenuOpen then
if IsKeyDown(vk_Control) and IskeyDown(vk_Shift) and IsKeyDown(vk_H) then
begin
//Some Boolean in the form
H_MenuOpen:=True;
//Will probably need to invalidate some parameters here so that
//no control tries to process the key
exit;
end;
if H_MenuOpen then
begin
if key=vk_X then
begin
//x has been pressed
*Your code here*
//possibly invalidate some of the params again
exit;
end;
//Nothing valid
H_MenuOpen:=False;
end;
end;
OK, since apparently nobody has found an answer, here is what I ended up doing:
I had already planned to show a hint window listing all possible characters for the second key (actually that code was already working fine, using the approach suggested by Helen Fairgrieve in her answer to this question). Instead, I now register only a one-key shortcut:
BindingServices.AddKeyBinding([FirstShortCut],
TwoKeyBindingHandler, nil,
DefaultKeyBindingsFlag, '', '');
And in the TwoKeyBindingHandler method, I show a popup menu which contains those characters as the shortcuts. The IDE/VCL/Windows then handles the rest for me.
This is what it looks like:
It's not an answer to the actual question but it solves my problem. Sorry if you got here expecting something more.
I want to know how can I write a module to show something like clock or other thing on Borland Delphi 7 IDE status bar, because I know it's possible but I couldn't find how!
To insert a text in a StatusBar, you have to insert a panel first.
Just select your statusbar, find the property "Panels" (or perform double click over the statusbar) and click in "Add new".
After that, you can write what you want inside the panel in the property "Text" (you can insert one or more panels).
To do it programmatically, you can do something like this:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
StatusBar1.Panels[0].Text := 'Today is: ' + FormatDateTime('dd/mm/yyyy hh:nn:ss', Now);
end;
Since OP didn't replied with more details, I'm going to post a little demonstration how to reach a status bar of Delphi's edit window. I had no success with adding new distinct status panel w/o disturbing layout, so I'm just changing the text of INS/OVR indicator panel.
Disclaimer: I still do not have access to the machine with Delphi 7 installed, so I've done that in BDS ("Galileo") IDE. However, differences should be minor. I believe what main difference lies in the way how we locate edit window.
Key strings are: 'TEditWindow' for edit window class name and 'StatusBar' for TStatusBar control name owned by edit window. These strings are consistent across versions.
{ helper func, see below }
function FindForm(const ClassName: string): TForm;
var
I: Integer;
begin
Result := nil;
for I := 0 to Screen.FormCount - 1 do
begin
if Screen.Forms[I].ClassName = ClassName then
begin
Result := Screen.Forms[I];
Break;
end;
end;
end;
procedure Init;
var
EditWindow: TForm;
StatusBar: TStatusBar;
StatusPanel: TStatusPanel;
begin
EditWindow := FindForm('TEditWindow');
Assert(Assigned(EditWindow), 'no edit window');
StatusBar := EditWindow.FindComponent('StatusBar') as TStatusBar;
(BorlandIDEServices as IOTAMessageServices).AddTitleMessage(Format('StatusBar.Panels.Count = %d', [StatusBar.Panels.Count]));
//StatusPanel := StatusBar.Panels.Add;
StatusPanel := StatusBar.Panels[2];
StatusPanel.Text := 'HAI!';
end;
initialization
Init;
finalization
// nothing to clean up yet
Another note: As you see, I use Open Tools API to output debug messages only, to interact with IDE I do use Native VCL classes. Therefore, this code must be in package.
The code above is a relevant part of the unit which should be contained in package. Do not forget to add ToolsAPI to uses clause as well as other appropriate referenced units (up to you).
Package should require rtl, vcl and designide (important!).
Since I run the testcase directly from initialization section, installing the package is enough for testcase to run and produce some result.
I am trying to use UIAutomation to access/control Chrome browser (using that method based on what other people have used - eg to get the current URL).
For the purposes ok the exercise, I'm trying to replicate this question - Retrieve current URL from C# windows forms application - in Delphi. I've imported the TLB ok. However, my call to ElementFromHandle never locates an element.
The signature of the ElementFromHandle method is:
function ElementFromHandle(hwnd: Pointer; out element: IUIAutomationElement): HResult; stdcall;
My test is simply:
procedure TForm3.Button1Click(Sender: TObject);
var
UIAuto: IUIAutomation;
element: IUIAutomationElement;
value: WideString;
h: PInteger;
begin
new(h);
h^ := $1094E;
SetForegroundWindow(h^);
ShowWindow(h^, SW_SHOW);
UIAuto := CoCUIAutomation.Create;
UIAuto.ElementFromHandle(h, element);
if Assigned(element) then
begin
element.Get_CurrentName(value);
showmessage('found -' + value);
end
else
showMessage('not found');
end;
Calls to SetForegroundWindow and ShowWindow are just there in case it needed focus (but I doubted that it would make a difference and doesn't). I can confirm that the Handle I'm passing in ($1094E) is "correct" in so much as Spy++ shows that value for the Chrome Tab I'm trying to access. The active tab in Chrome always reports that Handle.
Is my implementation correct above? Is there more to using UIAutomation than what I have implemented above? I have never explored it before.
Thanks
EDIT
I have found if I use ElementFromPoint and pass in a (hardcoded) value of where I know my Tab sits in terms of X,Y - it does work. ie:
UIAuto := CoCUIAutomation.Create;
p.x := 2916;
p.y := 129;
UIAuto.ElementFromPoint(p, element);
if Assigned(element) then
The above snippet if placed in the above OnClick event does return an element instance and the one I'm expecting too (which is a bonus). So maybe I'm passing in an incorrect value for Hwnd in ElementFromHandle? ie, I'm using the "top" level handle of Chrome as found my MS Spy++:
This sits directly under (Desktop) in Spy++.
Your mistake is in the way that you pass the window handle to ElementFromHandle. You are meant to pass an HWND. Instead you pass the address of an HWND.
The function should really be:
function ElementFromHandle(hwnd: HWND;
out element: IUIAutomationElement): HResult; stdcall;
You should remove the call to New and instead do:
var
window: HWND;
....
window := HWND($1094E);
Then call the function like this:
if Succeeded(UIAuto.ElementFromHandle(window, element)) then
....
Perhaps your biggest fundamental problem is the complete absence of error checking. I think you need to adjust your mindset to realise that these API calls will not raise exceptions. They report failure through their return value. You must check every single API call for failure.
One common way to do that is to convert HRESULT values to exceptions in case of failure with calls to OleCheck. For example:
var
UIAuto: IUIAutomation;
element: IUIAutomationElement;
value: WideString;
window: HWND;
....
window := HWND($1094E);
SetForegroundWindow(window);
ShowWindow(window, SW_SHOW);
UIAuto := CoCUIAutomation.Create;
OleCheck(UIAuto.ElementFromHandle(window, element));
OleCheck(element.Get_CurrentName(value));
ShowMessage('found -' + value);
I am trying to get access to the IWebBrowser2 object from Internet Explorer 8 with the chrome plugin. I am able to access it when the chrome plugin isn't installed, but it doesn't work due to the class names etc different.
Without chrome plugin I can use:
function GetIEFromHWND(WHandle: HWND; var IE: IWebbrowser2): HRESULT;
var
hInst: HWND;
lRes: Cardinal;
MSG: Integer;
pDoc: IHTMLDocument2;
ObjectFromLresult: TObjectFromLresult;
begin
Result := 0;
hInst := LoadLibrary('Oleacc.dll');
#ObjectFromLresult := GetProcAddress(hInst, 'ObjectFromLresult');
if #ObjectFromLresult <> nil then begin
try
MSG := RegisterWindowMessage('WM_HTML_GETOBJECT');
SendMessageTimeOut(WHandle, MSG, 0, 0, SMTO_ABORTIFHUNG, 1000, lRes);
Result := ObjectFromLresult(lRes, IHTMLDocument2, 0, pDoc);
if Result = S_OK then
(pDoc.parentWindow as IServiceProvider).QueryService(IWebbrowserApp, IWebbrowser2, IE);
finally
FreeLibrary(hInst);
end;
end;
end;
This doesn't work (I'm presuming) because there's no IHTMLDocument2 interface (using MS Spy++ you can see that the window heirarachy is completely different).
I can access the instance of the "Tab" that I'm after, but ultimately I need to "refresh" that tab with a new URL (which I was going to use IWebBrowser2.Navigate to accomplish).
I've tried importing the type library for Chrome but I can't find anything in there to help either. So I'm happy to utilise whatever I need to, in order to refresh tab that I have the handle to.
Thanks
If you need to use Chrome Frame I have wrapped the ActiveX control here:
http://www.progdigy.com/?p=116
But I would suggest you to use Delphi Chromium Embedded Instead, you will have more possibilities.
http://code.google.com/p/delphichromiumembedded/
I want to write the following procedure / function:
procedure ShowSysPopup(aFile: string; x, y: integer);
Which will build and show (at the coordinates x and y) the right-click shell menu which one sees in the Windows Explorer for the given file. I'm not so interested in the 'showing' part but more in how one can build such a menu.
I've made a quick solution for you.
add these units to the "Uses" section:
... ShlObj, ActiveX, ComObj
and here is your procedure, I just add new parameter "HND" to carry the handle of the TWinControl that you will use to display the context Menu.
procedure ShowSysPopup(aFile: string; x, y: integer; HND: HWND);
var
Root: IShellFolder;
ShellParentFolder: IShellFolder;
chEaten,dwAttributes: ULONG;
FilePIDL,ParentFolderPIDL: PItemIDList;
CM: IContextMenu;
Menu: HMenu;
Command: LongBool;
ICM2: IContextMenu2;
ICI: TCMInvokeCommandInfo;
ICmd: integer;
P: TPoint;
Begin
OleCheck(SHGetDesktopFolder(Root));//Get the Desktop IShellFolder interface
OleCheck(Root.ParseDisplayName(HND, nil,
PWideChar(WideString(ExtractFilePath(aFile))),
chEaten, ParentFolderPIDL, dwAttributes)); // Get the PItemIDList of the parent folder
OleCheck(Root.BindToObject(ParentFolderPIDL, nil, IShellFolder,
ShellParentFolder)); // Get the IShellFolder Interface of the Parent Folder
OleCheck(ShellParentFolder.ParseDisplayName(HND, nil,
PWideChar(WideString(ExtractFileName(aFile))),
chEaten, FilePIDL, dwAttributes)); // Get the relative PItemIDList of the File
ShellParentFolder.GetUIObjectOf(HND, 1, FilePIDL, IID_IContextMenu, nil, CM); // get the IContextMenu Interace for the file
if CM = nil then Exit;
P.X := X;
P.Y := Y;
Windows.ClientToScreen(HND, P);
Menu := CreatePopupMenu;
try
CM.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE or CMF_CANRENAME);
CM.QueryInterface(IID_IContextMenu2, ICM2); //To handle submenus.
try
Command := TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_LEFTBUTTON or TPM_RIGHTBUTTON or
TPM_RETURNCMD, p.X, p.Y, 0, HND, nil);
finally
ICM2 := nil;
end;
if Command then
begin
ICmd := LongInt(Command) - 1;
FillChar(ICI, SizeOf(ICI), #0);
with ICI do
begin
cbSize := SizeOf(ICI);
hWND := 0;
lpVerb := MakeIntResourceA(ICmd);
nShow := SW_SHOWNORMAL;
end;
CM.InvokeCommand(ICI);
end;
finally
DestroyMenu(Menu)
end;
End;
modify/add the initialization, finalization section like this
initialization
OleInitialize(nil);
finalization
OleUninitialize;
and here how you can use this procedure:
procedure TForm2.Button1Click(Sender: TObject);
begin
ShowSysPopup(Edit1.Text,Edit1.Left,Edit1.Top, Handle);
end;
I hope this will work for you.
Regards,
Edit:
if you want to show context menu for more than one file check this article in my blog
Are you sure that's what you want to do? Because if you do, you are effectively going to have to reproduce all of the code in the Windows Shell and all of it's behaviour and interactions with a whole host of code.
The context menu is basically constructed by "shell extensions". These are COM DLL's registered with the system. When the context menu is invoked, the shell follows a set of rules that determine where it should look (in the registry) for extension DLL's.
I found this to be a useful guide to these rules.
But finding the extension DLL's is not even half the story. For each DLL the shell then instantiates the COM object(s) registered by that DLL and makes calls to those objects which the DLL's respond to by either configuring or invoking menu commands.
The shell itself does not build the menu, nor is the information required to build the menu available to be queried or read directly from anywhere - the menu is constructed entirely dynamically by the shell extensions.
The shell passes a handle to the menu to each extension, along with some information telling the extension what command ID's it should use for any items it adds to that menu. The extension can add pretty much whatever it likes to the menu handle it is given, including sub-menus etc, and it may well add different items depending on properties of the current file select, not just file extensions (e.g. the Tortoise SVN client adds different menu items according to what is relevant to the current SVN status of those files).
So if you want to build such a menu yourself, as I say, you will have to replicate the entire shell extension framework (or at least those parts of it that initialize the menu's, assuming for some reason you don't then want or need to invoke the menu commands themselves) in your own code.
Perhaps it might help if you explain why you wish to do this and what you are trying to achieve. There might be an easier way to go about it.
Although I agree with Deltics that it is a lot of work, the information required for most (if not all) of the items is freely available in the registry. The guide listed in Deltics answer look good and will give you most of the items. A lot can be looked up form basic entries in the registry whereas others need calls to COM objects.