How to make hovering over Minimize, Maximize, and Close buttons behave? - delphi

In a Delphi application, when you hover over a border icon, e.g.:
Minimize
Maximize
Restore
it doesn't behave correctly:
Compare to an application that does behave correctly:
Step to Reproduce
Click File, New, VCL Forms Application - Delphi
Click Run (F9)
Hover over the Minimize, Maximize, or Close buttons.
How to fix?
Windows 10, 64-bit (running natively on desktop PC)
Delphi XE6
Edit - It also fails with Delphi 7:
and in Delphi 5:
and in Delphi 4:
I assumed (i.e. was afraid) that it was caused by the ThemeServices engine; where they might have thought it was cool to not honor the user's preferences. But looks like it's something more fundamental.
Compatibility Modes
none: fails
Windows 8: fails
Windows 7: fails
Windows Vista (Service Pack 2): fails
Windows Vista (Service Pack 2): fails
Windows Vista: fails
Windows XP (Service Pack 3) (non-client area theming disabled): works
Windows XP (Service Pack 2) (non-client area theming disabled): works
Windows 98 / Windows Me (non-client area theming disabled): works
Windows 95 (non-client area theming disabled): works
Skype
Also fails in Skype; also written in Delphi:
High DPI is the trigger
I finally figured out why it fails on every Windows 10 machine i've used; but not for everyone. High dpi.
Set your dpi to 97 (101%) or higher.
Close Enough
Dalija's solutions works:
We'll ignore the problem with the tooltip and live to fight another day.
It should also be noted that Windows 10 will suggest that you might have to sign off and sign back on for some applications to work correctly after changing the DPI. This is definitely true of Delphi.
It should also be noted that Delphi doesn't tolerate the DPI changing behind its back like this. This includes adjusting the zoom slider. This would also include placing the app on any monitor besides the primary monitor.
And we never did figure out what the problem is; only kicked it down the road for users running multiple monitors.
QC Bug Report
Because Bor...Impr...CodeG...Embarca... Idera's QC site is behind a pay-wall, here's a copy of the bug report:
http://archive.is/v77rz
As you can see: nobody cares.

High DPI is the trigger and it leads to the solution.
Applications that exhibit the issue are not High DPI aware. Solution to hovering problem is to make them aware or turn on associated compatibility mode by using one of solutions under 1, 2 or 3.
Note: whether will rest of the application behave properly when High DPI awareness is turned on is another issue and will differ from application to application.
Under compatibility mode check "Disable display scaling on high DPI settings"
Call SetProcessDPIAware as first call in .dpr file - as noted by Ian Boyd, calling this function can leat to race condition and preferred way is using manifest. SetProcessDPIAware
Use custom manifest with true or true/PM setting (default Delphi manifest included with "Enable runtime themes" is not high DPI aware)
Current versions of Delphi VCL and FMX frameworks lack support for per monitor DPI awareness, so use true/PM manifest only if you are handling per monitor DPI yourself. Reported to QP as VCL and FireMonkey lack Per-Monitor DPI support for Windows 8.1 (and Windows 10)
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
or
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true/PM</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
Update:
Delphi VCL is source of buggy behavior, specifically issue is somewhere in TForm class or its ancestors. When direct Windows API is used resulting windows behave normally.
Windows API code that behaves properly:
MessageBox(0, 'Correct', 'Caption', MB_OK);
ShowMessage('Correct'); // if themes are enabled -> Windows Task dialog is used
Full Delphi sample app that creates main window without using VCL - behaves properly
program win;
{$R *.res}
uses
Windows,
Messages,
SysUtils;
var
Msg: TMSG;
LWndClass: TWndClass;
hMainHandle: HWND;
function WindowProc(HWND, Msg: Longint; wParam: wParam; lParam: lParam): Longint; stdcall;
begin
if Msg = WM_DESTROY then PostQuitMessage(0);
Result := DefWindowProc(HWND, Msg, wParam, lParam);
end;
begin
LWndClass.hInstance := hInstance;
with LWndClass do
begin
lpszClassName := 'WinApiWnd';
Style := CS_PARENTDC or CS_BYTEALIGNCLIENT;
hIcon := LoadIcon(hInstance, 'MAINICON');
lpfnWndProc := #WindowProc;
hbrBackground := COLOR_BTNFACE + 1;
hCursor := LoadCursor(0, IDC_ARROW);
end;
RegisterClass(LWndClass);
hMainHandle := CreateWindow(LWndClass.lpszClassName, 'Window Title', WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU or WS_VISIBLE, 0, 0, 360, 200, 0, 0, hInstance, nil);
while GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end.
Misbehaved VCL forms:
var
f: TForm;
f := CreateMessageDialog('Broken', mtWarning, mbOKCancel, mbOk);
f.ShowModal;
f.Free;
f := TForm.Create(nil);
f.ShowModal;
f.Free;

Related

How to set FireMonkey form icon runtime in Delphi 10.2

I'm creating an application for Windows and OS X using Firemonkey Framework with Delphi 10.2 Tokyo and can't set application or form icon dynamically.
In VCL project I've used following code:
Form1.Icon.LoadFromFile()
or
Application.Icon.LoadFromFile()
But there are no such properties or methods in FMX. Is there any way to set project icon otherwise than in Project -> Options menu?
Add to your uses list {$IFDEF WIN32}WinApi.Windows,FMX.Platform.Win,{$ENDIF}
procedure setFormIcon(Form: TForm; FileName: String);
var Icon : NativeUInt;
begin
{$IFDEF WIN32}
ICON := LoadImage(0,PWideChar(Filename),IMAGE_ICON,0,0,LR_LOADFROMFILE OR LR_DEFAULTSIZE OR LR_SHARED);
SetClassLong(FmxHandleToHWND(Form.Handle), GCL_HICON, icon);
{$ENDIF}
end;
procedure TForm1.btn1Click(Sender: TObject);
begin
setFormIcon(Form1,'my icon path.ico');
end;
In the 10.2 Rio sources, I see that the function TPlatformWin.CreateWindow, which is used to create a window in the Windows, have only this line to provide a window's icon:
WindowClass.hIcon := LoadIconW(MainInstance, PChar('MAINICON'));
And function TPlatformWin.CreateAppHandle have an absolutely same code! So, we have not a direct way to set app or form icon through FireMonkey components.
At Windows You still can use WinAPI message WM_SETICON (SendMessage(Handle, WM_SETICON, ICON_BIG, MyIconHandle);), but I have not tried that and do not know any troubles in this way.
Also in Windows we may use TTaskbarBase and TPreviewClipRegion classes to the more precise and functional way of TaskBar interactions.
P.S. I do not know, what we have for same requirements at OS X.

How to use Delphi standard confirmation dialog but with checkbox "Don't ask me again"?

In many confirmation dialogs it is usefull to have such option (quick wayt to disable confirmation).
But i can't find how to do that. I don't want to design it myself because i need this dialog to be standard-like and don't wont to redesign with every update of Delphi. Is there simple way to use Delphi standard confirmation dialog with such checkbox ?
UPDATE2. Suggested SynTaskDialog library from Synopse project does great job (all i need and even more), i will use it in my projects. Thanks!
UPDATE. So, thank you guys for ideas. System function MessageBoxCheck is nice solution but seem to be not so stable as it should be. In general i agree that it is good idea to use latest API functions to provide users with best UI experience of modern os and use old-fashioned design for older systems. At moment i stay on simple solution (code is following), but if someone share the code with support of UI for modern OS, it will be nice.
function MsgDlgWithCB(const Msg,Title,CBMsg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; DefaultButton: TMsgDlgBtn;
var cbDontAskAnymore: TCheckBox): TForm;
var
i: integer;
b: TButton;
y: integer;
begin
Result := CreateMessageDialog(Msg, DlgType, Buttons, DefaultButton) ;
Result.Position := poScreenCenter;
cbDontAskAnymore := TCheckBox.Create(Result);
cbDontAskAnymore.Caption := CBMsg;
cbDontAskAnymore.Width := 130;
y := -1;
for i := 0 to result.ComponentCount-1 do
if result.Components[i] is TButton then
begin
b := TButton(result.Components[i]);
b.Left := b.Left + cbDontAskAnymore.Width + 16;
Result.ClientWidth := Max(Result.ClientWidth, b.Left+b.Width+16);
y := b.Top+b.Height-cbDontAskAnymore.Height;
end;
if y<0 then
y := Result.ClientHeight - cbDontAskAnymore.height - 16;
Result.Caption := Title;
cbDontAskAnymore.Parent := Result;
cbDontAskAnymore.Top := y;
cbDontAskAnymore.Left := 8;
end;
function MessageDlgCheckbox(const Msg: string; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; DefaultButton: TMsgDlgBtn;
var cbDontAskAnymore: Boolean;
const Title: string ='Confirmation';
const CBMsg: string = 'Don''t ask anymore'): integer;
var
f: TForm;
c: TCheckbox;
begin
f := MsgDlgWithCB(Msg,Title,CBMsg,DlgType,Buttons,DefaultButton,c);
try
result := f.ShowModal;
cbDontAskAnymore := c.Checked;
finally
f.free;
end;
end;
You can use our Open Source SynTaskDialog unit.
Windows provides a generic task dialog available since Vista/Seven. But there is none available with previous versions of Windows, i.e. Windows XP or 2K.
This unit (licensed under a MPL/GPL/LGPL tri-license) will use the new TaskDialog API under Vista/Seven, and emulate it with pure Delphi code and standard themed VCL components under XP or 2K. It supports Delphi 6 up to XE4, and is Win32/Win64 Unicode ready.
Here is the result under a Windows Seven 64 bit computer:
And here is the same dialog created from our emulated pure Delphi code:
Since this screenshot was made on a Win 7 machine, the styling is native for that OS. When the emulated version of the dialog runs on XP it displays in a style native to that OS.
You have your "Do not ask for this setting next time" checkbox... and potentially much more!
The system native functionality that offers such facilities is the task dialog API introduced in Vista. This provides means for you to show much more capable dialogs than the older MessageBox API.
Should you need to support XP then you will have to create your own dialog. For example by deriving from TForm and calling ShowModal. If you do this, make the form capable of building itself dynamically. Don't make one form per message that you show!
In my codebase, I have my own wrapper of the task dialog API. This detects at runtime versions of Windows that do not support task dialog and falls back on a custom built Delphi dialog.
Regarding SHMessageBoxCheck I'd be a little wary of taking a dependency on that. According to its documentation it's not supported beyond XP, and you have to import it by ordinal. I'd personally be worried that it might be dropped from a future version of Windows. That said, MS has a strong track record of doing whatever it takes to keep legacy apps working with new OS releases.

How to send virtual keys to other application using delphi 2010?

I need to send several virtual keys (VK_RETURN) from my delphi application (myapp.exe) into another application (target.exe).
Eg : Send VK_RETURN twice , from myapp.exe , into target.exe
The OS that I use are Windows 7 64 bit and Windows XP.
I read : How to send an "ENTER" key press to another application? , Send Ctrl+Key to a 3rd Party Application (did not work for me) and other previous asked question.
But still I'm getting confused.
How to set the focus to the target application ?
How to send the virtual keys to the targeted application ?
Simple example : I want to send VK_RETURN twice into notepad.exe or calc.exe (already loaded) or any other program from my delphi application. How to do that ?
The simplest way to do this in Delphi 2010, please...
PS :
I tried SndKey32.pass from http://delphi.about.com/od/adptips2004/a/bltip1104_3.htm
And got error : [DCC Error] SndKey32.pas(420): E2010 Incompatible types: 'Char' and 'AnsiChar'
If (Length(KeyString)=1) then MKey:=vkKeyScan(KeyString[1])
If your target application isn't the foreground window, you need to use PostMessage to send keystrokes to its window handle. You can get that window handle using FindWindow. The code below sends the Enter key to a the text area in a running instance of Notepad (note it uses an additional FindWindowEx to locate the memo area first). It was tested using both Delphi 2007 and Delphi XE4 (32-bit target) on Windows 7 64.
uses Windows;
procedure TForm1.Button1Click(Sender: TObject);
var
NpWnd, NpEdit: HWnd;
begin
NpWnd := FindWindow('Notepad', nil);
if NpWnd <> 0 then
begin
NpEdit := FindWindowEx(NpWnd, 0, 'Edit', nil);
if NpEdit <> 0 then
begin
PostMessage(NpEdit, WM_KEYDOWN, VK_RETURN, 0);
PostMessage(NpEdit, WM_KEYUP, VK_RETURN, 0);
end;
end;
end;
To find the window by title (caption) instead, you can just use the second parameter to FindWindow. This finds a new instance of Notepad with the default 'Untitled' file open:
NpWnd := FindWindow(nil, 'Untitled - Notepad');
Note that this requires as exact match on the window title. An extra space before or after the -, for instance, will cause the match to fail and the window handle to not be retrieved.
You can use both the window class and title if you have multiple instances running. To find the copy of Notepad running with Readme.txt loaded, you would use
NpWnd := FindWindow('Notepad', 'Readme.txt - Notepad');
To find other applications, you'll need to use something like WinSpy or WinSight to find the window class names. (There are others also, such as Winspector or WinDowse (both of which are written in Delphi).)
Your comment mentions Calculator; according to Winspector, the Calculator main window is in a window class called CalcFrame on Windows 7, and the area the numbers are displayed in is a Static window (meaning it doesn't seem to receive keystrokes directly). The buttons are simply called Button, so you'd have to loop through them using EnumChildWindows looking for the individual buttons to identify them in order to obtain their handles.
(How to enumerate child windows is a separate question; you can probably find an example by searching here or via Google. If you can't, post a new, separate question about that and we can try to get you an answer.)
Here's a quick example of sending keys to Calculator after finding it by window class. It doesn't do anything useful, because it needs some time spent to identify different buttons and the keys that each responds to (and the proper combination of messages). This code simply sends 11Numpad+22 to the calculator window (a quick test showed that they were properly received and displayed, and that's about all the time I wanted to spend on the process).
uses Windows;
procedure TForm1.Button1Click(Sender: TObject);
var
NpWnd: HWnd;
begin
NpWnd := FindWindow('CalcFrame', nil);
if NpWnd <> 0 then
begin
PostMessage(NpWnd, WM_KEYDOWN, VK_NUMPAD1, 0);
PostMessage(NpWnd, WM_KEYDOWN, VK_ADD, 0);
PostMessage(NpWnd, WM_KEYDOWN, VK_NUMPAD2, 0);
end;
end;

Can you make a Borderless Application Main Window in Windows, without WS_POPUP style?

I want to create a window that will be the main window and that Windows itself recognizes as a main application window. However, when I make my window borderless, and without any non-client area at all, Windows no longer recognizes that this window is an application main window. This has several side effects:
WindowsKey+M minimizes all windows except my application's main window.
Clicking once on the taskbar (in win7) and then again, should toggle the application main window's state/visibility between normal and minimized state. This does not work for such a window.
In bare Win32 programming terms, I'm asking about parameter values for dwStyle as when calling CreateWindow (WS_... constants), or CreateWindowEx (WS_EX_... constants). For delphi users, these values would be set in the CreateParams method, which you would override, and then set Params.Style := WS_xxx; For MFC/C++ users and C users, something in your framework would eventually be calling CreateWindow, with this dwStyle value.
In delphi terms, setting your form.BorderStyle=bsNone, results in dwStyle=WS_POPUP. However I want a borderless window without using dwStyle=WS_POPUP.
Note: All the answers below are good, but using each in production scenarios is problematic, and my attempts to do so, have resulted in encountering many glitches, which for a professional quality application, I still find I can not work around. Davids answer is a great pure Win32 API answer though, and fits the bill. It seems that an industrial strength solution should combine multiple qualities, including all those I have in my question above. In short, borderless forms using BorderStyle=bsNone (dwStyle=WS_POPUP) block all Windows functionality that usually applies to main windows of applications, and all the solutions below solve part of it.
Based on David's suggestions, I wrote the following, which doesn't work:
I want a window without a border, that behaves in all ways, like a windows application window, to the system, that is, it can be minimized/restored by clicking on the window in the taskbar, and will be minimized by WindowsKey+M. I am beginning to think that the only way to do this is to add non-client paint code and to resize the top nonclient area bounds to zero. This is of course not a trivial idea.
It turns out that I made a simple mistake in my coding (hence the two paragraphs above) and in fact the code below does now work as I desire. This one is in pascal, but it should be easy to convert it to C++ or anything else.
program NoBorderProject;
uses
Windows, Messages;
{the Messages unit contains the windows
Message constants like WM_COMMAND}
{$R *.RES}
var
wClass: TWndClass;
Msg: TMsg;
win:HWND;
function WindowProc(hWnd,Msg,wParam,lParam:Integer):Integer; stdcall;
begin
if Msg = WM_DESTROY then PostQuitMessage(0);
Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;
begin
wClass.lpszClassName:= 'CN';
wClass.lpfnWndProc := #WindowProc;
wClass.hInstance := hInstance;
wClass.hbrBackground:= 1;
RegisterClass(wClass);
win := CreateWindow(wClass.lpszClassName,'Title Bar',
WS_POPUP,//WS_OVERLAPPEDWINDOW or WS_VISIBLE,
10,10,340,220,0,0,hInstance,nil);
SetWindowLong(win, GWL_STYLE, WS_POPUP or WS_MINIMIZEBOX);
SetWindowLong(win, GWL_EXSTYLE, 0 );
ShowWindow(win,SW_SHOW);
while GetMessage(Msg,0,0,0) do
DispatchMessage(Msg);
end.
The following gets the job done:
hWnd = CreateWindow(...);
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP | WS_MINIMIZEBOX);
SetWindowLong(hWnd, GWL_EXSTYLE, 0);
ShowWindow(hWnd, ...);
You were probably missing WS_MINIMIZEBOX.
A bit icky, but you can set the window region by putting this in YourForm.OnShow event:
var
r: TRect;
begin
r := ClientRect;
OffsetRect(r, 0, GetSystemMetrics(SM_CYCAPTION));
OffsetRect(r, GetSystemMetrics(SM_CXFRAME), GetSystemMetrics(SM_CYFRAME));
SetWindowRgn(Handle,
CreateRectRgn(
r.Left, r.Top,
ClientWidth + r.Left, ClientHeight + r.Top), True);
You need to override TForm.CreateParams and set or remove any style that you are interest in
procedure TYourForm.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style := Params.Style and
Params.ExStyle := Params.ExStyle or ;
end;

Issues with nVidia nView desktop manager and Delphi applications?

I've recently had a customer report an issue with my applications and NVidia nView desktop manager. Basically nView seems to 'unhide' hidden secondary forms when moving an application between monitors. I'm testing with Delphi 2010. The problem can also be seen using just the IDE (show a non-docked window, close it and then choose to send the Delphi IDE to a different monitor.) The previously hidden forms are in an unresponsive state and can't be closed.
To reproduce:
. Use a multi-monitor system with NVidia nView desktop manager.
. Start any Delphi application with secondary forms that will be hidden when they are closed.
. Show or ShowModal the form, then close it (form needs to have been shown at least once).
. Choose to send the application to a different monitor. (via hotkeys or the caption icons)
. The application will move to the other monitor and any hidden forms will be visible.
Has anyone else seen this issue? I've verified it with an older version of nView, but a customer with the latest version has the issue whenever he tries to move the main form or resize it. Unfortunately my laptop can't be updated to the latest version so I can't easily test with it.
Thanks for any comments or suggestions!
-Mark
I had the same problem. The Delphi application stopped responding without any obvious reason. Looking at the stack trace from madExcept I could see that the application froze in nview.dll. The only "solution" that I found was to look for nView and advise the user to turn it off using the code below:
function CheckForNview: Boolean;
function IsNviewModuleRunning(AHandle: THandle; AProcessId: DWord): Boolean;
var
AModuleEntry: TModuleEntry32;
begin
AModuleEntry.dwSize := SizeOf(AModuleEntry);
AModuleEntry.th32ProcessID := AProcessId;
Result := False;
if Module32First(AHandle, AModuleEntry) then
begin
if SameStr(AModuleEntry.szModule, 'nview.dll') then
Result := True;
while Module32Next(AHandle, AModuleEntry) do
begin
if SameStr(AModuleEntry.szModule, 'nview.dll') then
Result := True;
end;
end;
end;
var
AHandle: THandle;
AProcessEntry: TProcessEntry32;
begin
Result := False;
AHandle := CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
if Process32First(AHandle, AProcessEntry) then
begin
if SameStr(AProcessEntry.szExeFile, ExtractFileName(ParamStr(0))) then
Result := Result or IsNviewModuleRunning(AHandle, AProcessEntry.th32ProcessID);
while Process32Next(AHandle, AProcessEntry) do
begin
if SameStr(AProcessEntry.szExeFile, ExtractFileName(ParamStr(0))) then
Result := Result or IsNviewModuleRunning(AHandle, AProcessEntry.th32ProcessID);
end;
end;

Resources