How to detect when the form is being maximized? - windows

I would like to detect when the form is going to be maximized to save certain settings (not related to the size nor position) and modify the size and position a little bit. Is there an universal way to do it ?
I've tried to catch the WM_SYSCOMMAND message like in this article. It works well for maximization from menu, by maximize button, but it's not fired when I press the WIN + UP keystroke. Does anyone know an universal way how to catch the maximization event including the case with WIN + UP keystroke ?
Thanks

You can use the WM_GETMINMAXINFO message to save the state of the window and then use the WMSize message to check if the window was maximized.
in you form declare the mesage handler like so :
procedure WMSize(var Msg: TMessage); message WM_SIZE;
And handle like this :
procedure TForm57.WMSize(var Msg: TMessage);
begin
if Msg.WParam = SIZE_MAXIMIZED then
ShowMessage('Maximized');
end;

WIN+UP does not generate WM_SYSCOMMAND messages, that is why you cannot catch them. It does generate WM_GETMINMAXINFO, WM_WINDOWPOSCHANGING, WM_NCCALCSIZE, WM_MOVE, WM_SIZE, and WM_WINDOWPOSCHANGED messages, though. Like RRUZ said, use WM_GETMINMAXINFO to detect when a maximize operation is about to begin and WM_SIZE to detect when it is finished.

IMO, You cannot use WM_GETMINMAXINFO to "detect when a maximize operation is about to begin" as #Remy stated.
In-fact the only message that can is WM_SYSCOMMAND with Msg.CmdType=SC_MAXIMIZE or undocumented SC_MAXIMIZE2 = $F032 - but it's not being sent via Win+UP, or by using ShowWindow(Handle, SW_MAXIMIZE) for example.
The only way I could detect that a window is about to be maximized is via WM_WINDOWPOSCHANGING which is fired right after WM_GETMINMAXINFO:
type
TForm1 = class(TForm)
private
procedure WMWindowPosChanging(var Message: TWMWindowPosChanging); message WM_WINDOWPOSCHANGING;
end;
implementation
const
SWP_STATECHANGED = $8000;
procedure TForm1.WMWindowPosChanging(var Message: TWMWindowPosChanging);
begin
inherited;
if (Message.WindowPos^.flags and (SWP_STATECHANGED or SWP_FRAMECHANGED)) <> 0 then
begin
if (Message.WindowPos^.x < 0) and (Message.WindowPos^.y < 0) then
ShowMessage('Window state is about to change to MAXIMIZED');
end;
end;

Related

Determine size of popup hint message (THintInfo::HintStr) at runtime

We have a TListView with ShowHint enabled. In the OnInfoTip handler, a hint message is constructed that is specific to the item over which the mouse is hovering. The message may include newline (#13#10) characters.
An override has been created to process CM_HINTSHOW messages and the hint message about to be displayed can be seen in msg.HintInfo.HintStr. It may be possible to calculate the size at runtime but this seems risky because the implementation details may be complex or platform-dependent.
Can the THintInfo be queried for its' bounding rectangle or is there another way to determine exactly how big the popup hint message will be when it is displayed?
This is required so an exact position of the hint (msg.HintInfo.HintPos) can be set.
THintWindow has the function CalcHintRect that can be used for this case. The VCL will use this function when showing a HintWindow:
with HintInfo do
HintWinRect := FHintWindow.CalcHintRect(HintMaxWidth, HintStr, HintData);
As FHintWindow is inaccessible outside of TApplication a temporary instance would need to be created.
procedure TMyListView.CMHintShow(var Message: TCMHintShow);
var
AHintWindow: THintWindow;
AHintWinRect: TRect;
...
begin
AHintWindow := Message.HintInfo.HintWindowClass.Create(nil);
try
AHintWinRect := AHintWindow.CalcHintRect(...);
...
finally
AHintWindow.Free;
end;
end;
How correct this is depends on the THintWindowClass's implementation. But the HintWindow would show incorrectly if one could not rely on it.
A potential pitfall could be in middle-eastern locale when BidiMode is right-to-left. Then following is done additionally:
if FHintWindow.UseRightToLeftAlignment then
with HintWinRect do
begin
Delta := MultiLineWidth(HintInfo.HintStr) + 5;
Dec(Left, Delta);
Dec(Right, Delta);
end;

How to get focused child elements of windows pane control?

I use windows UI Automation framework to access controls in other processes. I catch the system SETFOCUS messages and check the type of the focused control if it is an edit control or not. This sometimes works perfectly, but sometimes I won't get the focused control from setfocus message but only a handle to an upper control in the tree, for example a handle to a pane. What am I doing wrong?
I tried to find out which child element of the pane currently got keyboard focus by checking the UIA_HasKeyboardFocusPropertyId of the enabled child elements, but all of them will return false.
Below is the code which checks the keyboard focus property.
Additionally, for some reason I am not able to use the Automation Element Property Identifiers. They should be listed here: https://msdn.microsoft.com/en-us/library/windows/desktop/ee684017(v=vs.85).aspx
I got the values from archive.org, because the content is no longer available.
In the code, "i" is the type of the currenty focused control which got the windows setfocus message.
if (i = 50033) then // 50033 (pane)
begin
uiAuto.CreatePropertyCondition(30010, true, cond); // 30010 (enabled property)
if cond <> nil then
begin
focusedElement.FindAll(TreeScope_Children, cond, children);
if children <> nil then
begin
children.Get_Length(length);
if length > 0 then
begin
Memo1.Lines.Add('length: ' + IntToStr(length));
for j := 0 to length-1 do
begin
children.GetElement(j, tempChildElement);
tempChildElement.Get_CurrentControlType(k);
Memo1.Lines.Add('child element type: ' + IntToStr(k));
tempChildElement.GetCurrentPropertyValue(30008, keybFocusBool); // 30008 (UIA_HasKeyboardFocusPropertyId)
if keybFocusBool then
Memo1.Lines.Add('child has keyboard focus: TRUE');
keybFocusbool := false;
end;
end;
end;
end;
end;

Forwarding keyboard events from one Windows control to another

In Delphi XE, I'm trying to implement an "instant search" feature - one that resembles Firefox's "search as you type" somewhat, but is better illustrated by a similar feature in an open source clipboard extender, Ditto:
There is a list of items that handles typical navigation events. However, any alphanumeric keys as well as navigation and editing commands (right/left arrows, shift+arrows, backspace, delete etc.) should be rerouted to the edit box below the list. An OnChange event of the edit box will trigger a refresh of the list.
The point of the UI is that user does not have to tab or shift-tab between the controls. The two controls (the list and the edit box) should 'feel" as if they were a single control. The behavior of the search UI should not be contingent on which control has focus.
It seems my best option is to forward certain keyboard events from the list control (I'm using TcxTreeList) to the edit box, and forward a handful of navigation keys from the edit box to the list. How can I achieve that?
Notes:
TcxTreeList supports incremental search of course, but this is not what I'm after. The search goes to an SQLite database and looks for substring matches. The list displays only the matching items from the db.
There is some overlap, e.g. both controls would normally handle VK_HOME and VK_END, but that's OK - in this case the keys would go to the list. I'll need to decide whether to forward each individual keypress, or handle it in the control that received it.
On Edit:
One obvious way seemed to be to invoke the respective KeyDown, KeyUp and KeyPress methods of the edit control, like so:
type
THackEdit = class( TEdit );
procedure TMainForm.cxTreeList1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
THackEdit( edit1 ).KeyDown( Key, Shift );
end;
Unfortunately, this has no effect. My guess is TEdit won't process key events unless it is focused. Using SendMessage( THackEdit( edit1 ).Handle, WM_KEYDOWN, Key, 0 ) has no effect, either.
You can use the message handling capability of a VCL control and send the relevant messages to one another. I don't know about a 'TcxTreeList', but the following demonstrates the idea on an edit control and a memo control responding to keyboard events synchronously (whereever possible of course).
type
TEdit = class(stdctrls.TEdit)
private
FMsgCtrl: TWinControl;
FRecursing: Boolean;
procedure WmChar(var Msg: TWMChar); message WM_CHAR;
procedure WmKeyDown(var Msg: TWMKeyDown); message WM_KEYDOWN;
procedure WmKeyUp(var Msg: TWMKeyUp); message WM_KEYUP;
end;
TMemo = class(stdctrls.TMemo)
private
FMsgCtrl: TWinControl;
FRecursing: Boolean;
procedure WmChar(var Msg: TWMChar); message WM_CHAR;
procedure WmKeyDown(var Msg: TWMKeyDown); message WM_KEYDOWN;
procedure WmKeyUp(var Msg: TWMKeyUp); message WM_KEYUP;
end;
TForm1 = class(TForm)
Edit1: TEdit;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TEdit }
procedure TEdit.WmChar(var Msg: TWMChar);
begin
if not FRecursing then begin
inherited;
// Insert test here to see if the message will be forwarded
// exit/modify accordingly.
if Assigned(FMsgCtrl) then begin
FRecursing := True;
try
FMsgCtrl.Perform(Msg.Msg,
MakeWParam(Msg.CharCode, Msg.Unused), Msg.KeyData);
finally
FRecursing := False;
end;
end;
end;
end;
procedure TEdit.WmKeyDown(var Msg: TWMKeyDown);
begin
// exact same contents as in the above procedure
end;
procedure TEdit.WmKeyUp(var Msg: TWMKeyUp);
begin
// same here
end;
{ TMemo }
procedure TMemo.WmChar(var Msg: TWMChar);
begin
// same here
end;
procedure TMemo.WmKeyDown(var Msg: TWMKeyDown);
begin
// same here
end;
procedure TMemo.WmKeyUp(var Msg: TWMKeyUp);
begin
// same here
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.FMsgCtrl := Memo1;
Memo1.FMsgCtrl := Edit1;
end;
You might need to intervene additional messages but you get the idea.
If for one reason or another you cannot derive a new control or override message handling, you can consider sub-classing the controls. Answer to this question would show you how to do that.
Not exactly what you are asking for, but for similar results, I use the following trick.
Assume you have one TEdit Edit1 and one TListbox Listbox1.
In the OnEnter event of Listbox1, simply yield focus to Edit1
procedure TForm1.ListBox1Enter(Sender: TObject);
begin
edit1.SetFocus;
end;
And in the OnKeyDown event of Edit1, use the up and down arrows to navigate the items of the listbox and use the enter key to move the selected item to the edit box.
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var k:word;
begin
if (Shift=[]) and (key=VK_DOWN) then
begin
listbox1.ItemIndex:=listbox1.ItemIndex+1;
key:=0;
end
else if (Shift=[]) and (key=VK_UP) then
begin
listbox1.ItemIndex:=listbox1.ItemIndex-1;
key:=0;
end
else if (Shift=[]) and (key=VK_RETURN) then
begin
edit1.text:=listbox1.items[listbox1.itemindex];
end;
end;

Add a border icon to the form

Using Delphi I would like to add another button to the border icon buttons; close, maximize, minimize. Any ideas on how to do this?
This was easy to do prior to Windows Aero. You simply had to listen to the WM_NCPAINT and WM_NCACTIVATE messages to draw on top of the caption bar, and similarly you could use the other WM_NC* messages to respond to mouse clicks etc, in particular WM_NCHITTEST, WM_NCLBUTTONDOWN, and WM_NCLBUTTONUP.
For instance, to draw a string on the title bar, you only had to do
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
protected
procedure WMNCPaint(var Msg: TWMNCPaint); message WM_NCPAINT;
procedure WMNCActivate(var Msg: TWMNCActivate); message WM_NCACTIVATE;
private
procedure DrawOnCaption;
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.WMNCActivate(var Msg: TWMNCActivate);
begin
inherited;
DrawOnCaption;
end;
procedure TForm1.WMNCPaint(var Msg: TWMNCPaint);
begin
inherited;
DrawOnCaption;
end;
procedure TForm1.DrawOnCaption;
var
dc: HDC;
begin
dc := GetWindowDC(Handle);
try
TextOut(dc, 20, 2, 'test', 4);
finally
ReleaseDC(Handle, dc);
end;
end;
end.
Now, this doesn't work with Aero enabled. Still, there is a way to draw on the caption bar; I have done that, but it is far more complicated. See this article for a working example.
Chris Rolliston wrote a detailed blog about creating a custom title bar on Vista and Windows 7.
He also wrote a follow up article and posted example code on CodeCentral.
Yes, set the form's border style property to bsNone and implement your own title bar with all the buttons and custom behaviour you like.

Background changes by itself and procedure repeats many times until I release the mouse button

I am a student, and I'm working on a little slots game (if the same random number comes up 3 timed, you win). I use Borland Pascal 7. I use graph to make this a bit more visual, but when I start the game my background turns from black to grey, and the other problem is that if I click the game start button, the game runs many times until I release the mouse button. How can I solve this?
Here is my full program:
program slots;
uses mymouse,graph,crt;
var gdriver,gmode,coin:integer;
m:mouserec;
a,b,c,coins:string;
procedure gomb(x1,y1,x2,y2:integer;szoveg:string);
var j,n:integer;
begin
setcolor(blue);
rectangle(x1,y1,x2,y2);
setfillstyle(1,blue);
floodfill(x1+2,y1+2,blue);
setcolor(0);
outtextxy((x1+x2)div 2 -textwidth(szoveg) div 2 ,(y1+y2) div 2-textheight(szoveg) div 2,szoveg);
end;
procedure randomal(var a,b,c:string);
begin
randomize;
STR(random(2)+1,a);
STR(random(2)+1,b);
STR(random(2)+1,c);
end;
procedure menu;
begin;
settextstyle(0,0,1);
outtextxy(20,10,'Meno menu');
gomb(20,20,90,50,'Teglalap');
gomb(20,60,90,90,'Inditas');
gomb(20,100,90,130,'Harmadik');
gomb(20,140,90,170,'Negyedik');
end;
procedure teglalap(x1,x2,y1,y2,tinta:integer);
begin
setcolor(tinta);
rectangle(x1,x2,y1,y2);
end;
procedure jatek(var a,b,c:string;var coin:integer;coins:string);
begin;
clrscr;
menu;
randomal(a,b,c);
if ((a=b) AND (b=c)) then coin:=coin+1 else coin:=coin-1;
settextstyle(0,0,3);
setbkcolor(black);
outtextxy(200,20,a);
outtextxy(240,20,b);
outtextxy(280,20,c);
STR(coin,coins);
outtextxy(400,400,coins);
end;
procedure eger;
begin;
mouseinit;
mouseon;
menu;
repeat
getmouse(m);
if (m.left) and (m.x>20) ANd (m.x<90) and (m.y>20) and (m.y<50) then teglalap(90,90,300,300,blue);
if (m.left) and (m.x>20) AND (m.x<90) and (m.y>60) and (m.y<90) then jatek(a,b,c,coin,coins);
until ((m.left) and (m.x>20) ANd (m.x<140) and (m.y>140) and (m.y<170));
end;
begin
coin:=50;
gdriver:=detect;
initgraph(gdriver, gmode, '');
eger;
end.
I have many years to use Turbo Pascal :)
I used this snippet to init BGI (graphic) mode:
Gd := Detect;
InitGraph(Gd, Gm, 'bgi');
if GraphResult <> grOk then
Halt(1);
SetBkColor(black);
Cleardevice;
If I recall correctly, ClearDevice is proper for clearing the screen, ClrScr is for text mode.
Now, GetMouse(m); probably returns immediately the mouse data thus the code
in the repeat loop runs again and again with no delay, even if you don't use the mouse.
One solution is to check if the mouse button is up before you execute that code or
add some kind of delay before calling the GetMouse.

Resources