When maximizing a Form via WindowState:=wsMaximized, the title bar looks like this:
When setting the form to WindowState:=wsNormal and setting the form size manualy to a fullscreen state, the content of the frame is identical but the title bar is slightly moved.
The wsNormal form Rect on a 800*600 screen to simulate a wsMaxed form is TRect(-8,-8,808,608).
(See this question why the difference of the size is necessary)
My Question:
How can i fix the moved title bar content for the wsNormal window, so it would look correct like the following mock up?
Simple example form with one button, which reproduces both form states.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
FState: Integer;
public
{ Public-Deklarationen }
procedure WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
LBRect: TRect;
begin
LBRect := Screen.Monitors[0].BoundsRect;
case FState of
0:
begin
WindowState := wsMaximized;
end;
1:
begin
WindowState := wsNormal;
LBRect := Screen.Monitors[0].WorkareaRect;
LBRect.Inflate(8,8); //offset 8 for a form with bsSizeable
BoundsRect := LBRect;
end;
end;
Inc(FState);
FState := FState mod 2;
end;
procedure TForm1.WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
begin
//slightly smaller MaxTrackSize would prevent the wsNormal form the fully cover the screen on the right side
Message.MinMaxInfo.ptMaxTrackSize := Message.MinMaxInfo.ptMaxSize;
end;
end.
Edit1:
To clarify why this might be usefull:
We have a multi-monitor set up with identical resolution screens and want to span the form over this verly large "virtual screen. The desktop can not be unified to one large screen via e.g. AMDs Virtual Destkop settings.
The problem occurs because the title bar rect of a maximized Form has a slightly smaller height than a non-maximized form title bar.
Also, there is a need for the negative positions and enlarged size, caused by the (backwards compatible) way windows handles the border calculations and positioning. The actual offset/enlargement comes from the chosen borderstyle.
After some research, i found a way to reach the Goal for the described Scenario with minimal effort. This does not answer the question itself, it will only reach the goal.
When a form is created and also when it is maximized (by either the user or setting the forms WindowState to wsMaximized), the window message WM_GETMINMAXINFO gets send.
With sending WM_GETMINMAXINFO, the Form will be asked about its desired position and size in a maximized situation.
Here, we can override windows default positioning and e.g. span a maximized view over multiple screens by setting the values accordingly.
When analyzing the default values sent via WM_GETMINMAXINFO message, the values will have an offset of 8 in each dimension.
The offset was necessary on older Windows OS to tell the window manager to hide the borderstyle outside of the current screens view, and would also result in edge bleeding on multi-monitor setup.
The edge bleeding also occurs nowadays, when the WindowState is is set to wsNormal.
The Offset will also tell Windows Window Manager to reduce the title bars size when it is docked to the top AND to render the title bar content correctly (which is the goal we searched for).
See the following example:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
FState: Integer;
public
{ Public-Deklarationen }
procedure WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo); message WM_GETMINMAXINFO;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
case FState of
0:
begin
WindowState := wsMaximized;
end;
1:
begin
WindowState := wsNormal;
BoundsRect := TRect.Create(100,-8,300,192);
end;
2:
begin
WindowState := wsMaximized;
end;
3:
begin
WindowState := wsNormal;
BoundsRect := TRect.Create(100,0,300,200);
end;
4:
begin
WindowState := wsMaximized;
end;
5:
begin
WindowState := wsNormal;
BoundsRect := TRect.Create(100,100,300,300);
end;
end;
Inc(FState);
FState := FState mod 6;
end;
procedure TForm1.WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
begin
case FState of
0, 1:
begin
Message.MinMaxInfo.ptMaxPosition.SetLocation(100,-8);
Message.MinMaxInfo.ptMaxSize.SetLocation(200,200);
Message.MinMaxInfo.ptMaxTrackSize := Message.MinMaxInfo.ptMaxSize;
inherited;
end;
2, 3:
begin
Message.MinMaxInfo.ptMaxPosition.SetLocation(100,0);
Message.MinMaxInfo.ptMaxSize.SetLocation(200,200);
Message.MinMaxInfo.ptMaxTrackSize := Message.MinMaxInfo.ptMaxSize;
inherited;
end;
4, 5:
begin
Message.MinMaxInfo.ptMaxPosition.SetLocation(100,100);
Message.MinMaxInfo.ptMaxSize.SetLocation(200,200);
Message.MinMaxInfo.ptMaxTrackSize := Message.MinMaxInfo.ptMaxSize;
inherited;
end;
end;
end;
end.
FYI: var Message: TWMGetMinMaxInfo seems to be persistent for the forms lifetime.
On state 0, the form is slightly moved into the top, which tells the window manager to render this forms title slightly smaller. This is the default behaviour for maximized forms and the optical result i searched for.
State 1 will show the problem i had, and the other states will show you that positioning a maximized form anywhere is possible AND that the title bar will have the default size when it is not cutting a screens edge.
If you want to know what offsets to pick for your TForm, you can ask windows by using AdjustWindowRectEx with your chosen styles and main menu informations. (See: VCL.Forms.pas, TCustomForm.GetClientRect implementation)
The standard caption of an API window is fixed, you cannot do anything to alter its position or size.
Related
I'm experiencing a strange behavior with WM_NCHITTEST messages.
In summary, what happens is that as soon as I have the mouse over the target (ie: Hooked) control and leave the mouse still (or idle), I receive endlessly hundred's of WM_NCHITTEST messages per second. This happens whether I subclass the WndProc of that control with WindowProc(), or if I override the WndProc method in a descendant class (I subclass in the code below for simplicity).
As far as I could find from online Win32 API docs and other sources, I doubt that this message fires at this frequency, but I might be wrong. Or maybe there is an obvious explanation that I completely missed, or maybe something changed in the APIs that I am not aware of. In any event, I would really like to know what it is, or what is going on.
I've tested the same code (the example below) on two different systems with the same result, though both systems are in the same Delphi/OS version and configuration. I've tried running the app outside of the IDE (so no debugging hook), in both debug and release configurations (latter with no debug info), target both 32-bit and 64-bit, and I always get the same result.
I am developing with Delphi XE7 Enterprise under Win10 Pro 64-bit, version 20H2 (the latest Windows version I believe).
Here is a very simplistic program to reproduce what I am experiencing: a TForm with a TPanel, a TCheckBox, and a TLabel. The panel is the control being hooked when the checkbox is checked, and the label is displaying how many WM_NCHITTEST messages are received by the WndProc() method:
unit Unit5;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TForm5 = class(TForm)
CheckBox1: TCheckBox;
Label1: TLabel;
Panel1: TPanel;
procedure FormDestroy(Sender: TObject);
procedure CheckBox1Click(Sender: TObject);
private
FHookedCtrl: TControl;
FHookedCtrlWndProc: TWndMethod;
FMessageCount: Integer;
procedure SetHookedCtrl(const Value: TControl);
public
procedure ControlWndProc(var Message: TMessage);
property HookedCtrl: TControl read FHookedCtrl write SetHookedCtrl;
end;
var
Form5: TForm5;
implementation
{$R *.dfm}
{ TForm5 }
procedure TForm5.CheckBox1Click(Sender: TObject);
begin
//checkbox activates or deactivates the hook
if CheckBox1.Checked then
//hook the panel's WndProc by subclassing
HookedCtrl := Panel1
//release the hook on WndProc
else HookedCtrl := nil;
end;
procedure TForm5.ControlWndProc(var Message: TMessage);
begin
case Message.Msg of
WM_NCHITTEST:
begin
//show how many messages received with the label's caption
Inc(FMessageCount);
Label1.Caption := FormatFloat('##,##0 messages', FMessageCount);
end;
end;
//not really handling the messsage, just counting.
FHookedCtrlWndProc(Message);
end;
procedure TForm5.FormDestroy(Sender: TObject);
begin
//make sure to clear the hook if assigned
HookedCtrl := nil;
end;
procedure TForm5.SetHookedCtrl(const Value: TControl);
begin
if (Value <> FHookedCtrl) then
begin
if Assigned(FHookedCtrl) then
begin
//release the hook
FHookedCtrl.WindowProc := FHookedCtrlWndProc;
FHookedCtrlWndProc := nil;
FMessageCount := 0;
end;
FHookedCtrl := Value;
if Assigned(FHookedCtrl) then
begin
//hook the panel (i.e. Value)
FHookedCtrlWndProc := FHookedCtrl.WindowProc;
FHookedCtrl.WindowProc := ControlWndProc;
end;
end;
end;
end.
To reproduce: run the app, check the CheckBox, hover the mouse over the panel and leave it idle (still). In my case, I receive 100's of WM_NCHITTEST messages per second, and it never stops coming. Should this happen?
Can someone explain what's happening here?
I used Microsoft Spy++ tool to see what happens and when.
It is the following line in the WM_NCHITTEST handler
Label1.Caption := FormatFloat('##,##0 messages', FMessageCount);
which causes the issue. When you remove it, there is no more all those WM_NCHITTEST messages. To see the number of messages, use a TTimer with a 1 second interval and display the message count in the label. You'll see that you get a WM_NCHITTEST each time the timer fires (You still get a message if you have an empty OnTimer handler) and of course when the mouse is moving.
Here is the code I used:
unit Unit5;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;
type
TForm5 = class(TForm)
Label1: TLabel;
CheckBox1: TCheckBox;
Panel1: TPanel;
Timer1: TTimer;
procedure FormDestroy(Sender: TObject);
procedure CheckBox1Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
FHookedCtrl: TControl;
FHookedCtrlWndProc: TWndMethod;
FMessageCount: Integer;
procedure SetHookedCtrl(const Value: TControl);
public
procedure ControlWndProc(var Message: TMessage);
property HookedCtrl: TControl read FHookedCtrl write SetHookedCtrl;
end;
var
Form5: TForm5;
implementation
{$R *.dfm}
{ TForm5 }
procedure TForm5.CheckBox1Click(Sender: TObject);
begin
//checkbox activates or deactivates the hook
if CheckBox1.Checked then
//hook the panel's WndProc by subclassing
HookedCtrl := Panel1
else
//release the hook on WndProc
HookedCtrl := nil;
end;
procedure TForm5.ControlWndProc(var Message: TMessage);
begin
case Message.Msg of
WM_NCHITTEST:
//Count how many messages received
Inc(FMessageCount);
end;
//not really handling the messsage, just counting.
FHookedCtrlWndProc(Message);
end;
procedure TForm5.FormDestroy(Sender: TObject);
begin
//make sure to clear the hook if assigned
HookedCtrl := nil;
end;
procedure TForm5.SetHookedCtrl(const Value: TControl);
begin
if (Value <> FHookedCtrl) then begin
if Assigned(FHookedCtrl) then begin
//release the hook
FHookedCtrl.WindowProc := FHookedCtrlWndProc;
FHookedCtrlWndProc := nil;
FMessageCount := 0;
end;
FHookedCtrl := Value;
if Assigned(FHookedCtrl) then begin
//hook the panel (i.e. Value)
FHookedCtrlWndProc := FHookedCtrl.WindowProc;
FHookedCtrl.WindowProc := ControlWndProc;
end;
end;
end;
procedure TForm5.Timer1Timer(Sender: TObject);
begin
// Show how many message received
Label1.Caption := FormatFloat('##,##0 messages', FMessageCount);
end;
end.
I want to change the caption of a panel when it is out of view when scrolling in a TScrollBox. I have a scrollbox where all the all the categories are listed under each other and as the title of each category scrolls past i want the top panel to change to show which category I am currently scrolling through. How exactly can i do this?
To see if any pixel of a child control ChildCtrl currently is visible in the parent TScrollBox control named ScrollBox, check
ScrollBox.ClientRect.IntersectsWith(ChildCtrl.BoundsRect)
This is just one definition of "not out of view", however. If you instead want to check if the entire control is visible, instead check
ScrollBox.ClientRect.Contains(ChildCtrl.BoundsRect)
To detect scrolling, you would love a published OnScroll property of TScrollBox, but unfortunately there is no such property. Instead, you must intercept the scroll messages yourself, as detailed in this Q&A.
Here is a complete example (just quick and dirty to show how it is done -- in a real app, you would refactor it):
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, StdCtrls;
type
TScrollBox = class(Vcl.Forms.TScrollBox)
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
end;
TForm1 = class(TForm)
ScrollBox1: TScrollBox;
lblTitle: TLabel;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FButtons: TArray<TButton>;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
const
N = 30;
var
i, y: Integer;
btn: TButton;
begin
// First, populate the scroll box with some sample buttons
SetLength(FButtons, N);
y := 10;
for i := 0 to N - 1 do
begin
btn := TButton.Create(ScrollBox1);
btn.Parent := ScrollBox1;
btn.Left := 10;
btn.Top := y;
btn.Caption := 'Button ' + (i + 1).ToString;
Inc(y, 3*btn.Height div 2);
FButtons[i] := btn;
end;
end;
{ TScrollBox }
procedure TScrollBox.WMVScroll(var Message: TWMVScroll);
var
i: Integer;
begin
inherited;
for i := 0 to High(Form1.FButtons) do
if Form1.ScrollBox1.ClientRect.Contains(Form1.FButtons[i].BoundsRect) then
begin
Form1.lblTitle.Caption := Form1.FButtons[i].Caption;
Break;
end;
end;
end.
Don't forget to set TScrollBox.VertScrollBar.Tracking to True!
I've changed my form to a borderless form, I just changed the BorderStyle property to bsNone, but now my application loses the windows anchor and some commands like
WIN + ↑ (Align the form Client) WIN + ↓ (Minimize the form) WIN + →(Align the form Right) WIN + ←(Align the form Left)
I've tried to set BorderStyle: bsSizeable and use the below code inside of the FormCreate, but this does not worked:
procedure TfrmBase.FormCreate(Sender: TObject);
begin
SetWindowLong(Handle
,GWL_STYLE
,GetWindowLong(Handle, GWL_STYLE)
AND (NOT WS_CAPTION)
AND (NOT WS_THICKFRAME)
);
Refresh;
FormColor := oLauncher.oCor;
end;
This results:
The image above is what I want, but the Windows commands that I already have mentioned don't work
Have any way to set the BorderStyle: bsNone and don't lose these commands?
EDITED
If I use the WS_THICKFRAME my form returns a little top border and the windows commands works well, but I don't want that top border.
EDITED 2
I got very close to the expected result, but have a little problem yet...
I put this on my FormCreate
SetWindowLong(Handle
,GWL_STYLE
,GetWindowLong(Handle, GWL_STYLE)
AND (NOT WS_CAPTION)
);
And I create the method
private
procedure WmNCCalcSize(var Msg: TWMNCCalcSize); message WM_NCCALCSIZE;
and then
procedure TfrmBase.WmNCCalcSize(var Msg: TWMNCCalcSize);
begin
inherited;
if Msg.CalcValidRects then
begin
InflateRect(Msg.CalcSize_Params.rgrc[0], 0, 6);
Msg.Result := 0;
end;
end;
I got this method here
Now the border has disappeared, but when my Form loses the focus, the top / bottom border is shown again....
How can I avoid this?
SOLVED
I left the border as BorderStyle: bsSizeable, then I did it:
private
procedure WmNCCalcSize(var Msg: TWMNCCalcSize); message WM_NCCALCSIZE;
[...]
procedure TfrmBase.WmNCCalcSize(var Msg: TWMNCCalcSize);
var
R: TRect;
begin
if not Msg.CalcValidRects then
R := PRect(Msg.CalcSize_Params)^;
inherited;
if Msg.CalcValidRects then
Msg.CalcSize_Params.rgrc0 := Msg.CalcSize_Params.rgrc1
else
PRect(Msg.CalcSize_Params)^ := R;
Msg.Result := 0;
end;
procedure TfrmBase.FormCreate(Sender: TObject);
begin
BorderStyle := bsNone;
SetWindowLong(Handle
,GWL_STYLE
,WS_CLIPCHILDREN or WS_OVERLAPPEDWINDOW
);
end;
procedure TfrmBase.FormShow(Sender: TObject);
begin
Width := (Width - 1);
end;
Solution at GitHUB
I've create a repository here
Some of the commands you refer to are system commands related to sizing of the window. That requires the thick frame, without it "WIN + right" and "WIN + left" won't work. Additionally you need the minimize box and the maximize box for the WIN + up/down commands to work.
Best is to start from scratch and include the styles you need, otherwise VCL might interfere. If there's a possibility of your form to be recreated, put styling in a CreateWnd override.
procedure TForm1.FormCreate(Sender: TObject);
begin
BorderStyle := bsNone;
SetWindowLong(Handle, GWL_STYLE, WS_CLIPCHILDREN or WS_OVERLAPPEDWINDOW);
end;
Then there's the frame that you don't want. In an edit in the question you inflate the client rectangle to get rid of it. Don't guess the frame width/height, do it like the below.
procedure TForm1.WMNCCalcSize(var Message: TWMNCCalcSize);
var
R: TRect;
begin
if not Message.CalcValidRects then
R := PRect(Message.CalcSize_Params)^;
inherited;
if Message.CalcValidRects then
Message.CalcSize_Params.rgrc0 := Message.CalcSize_Params.rgrc1
else
PRect(Message.CalcSize_Params)^ := R;
Message.Result := 0;
end;
Reading the documentation for the message is mandatory at this point, the parameters have different meanings at different stages, etc..
The above leaves a window without any non-client area at all. The client rectangle is equal to the window rectangle. Although the caption is not visible, you can activate the system menu by pressing Alt+Space. The problem is, the system insists on drawing activation state. Now it draws a frame in the client area!!
Get rid of it by intercepting WM_NCACTIVATE, you also need it to draw your title according to the activation status:
procedure TForm1.WMNCActivate(var Message: TWMNCActivate);
begin
if Message.Active then
// draw active caption
else
// draw incactive caption
// don't call inherited
end;
You might have to deal with some glitches, messing up with the window has consequences. In my test, the minimized form does not have an associated icon in the alt+tab dialog for instance.
Below is my test unit in full.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
protected
procedure WMNCActivate(var Message: TWMNCActivate); message WM_NCACTIVATE;
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
BorderStyle := bsNone;
SetWindowLong(Handle, GWL_STYLE, WS_CLIPCHILDREN or WS_OVERLAPPEDWINDOW);
end;
procedure TForm1.WMNCActivate(var Message: TWMNCActivate);
begin
if Message.Active then
// draw active caption
else
// draw incactive caption
// don't call inherited
end;
procedure TForm1.WMNCCalcSize(var Message: TWMNCCalcSize);
var
R: TRect;
begin
if not Message.CalcValidRects then
R := PRect(Message.CalcSize_Params)^;
inherited;
if Message.CalcValidRects then
Message.CalcSize_Params.rgrc0 := Message.CalcSize_Params.rgrc1
else
PRect(Message.CalcSize_Params)^ := R;
Message.Result := 0;
end;
end.
I'm trying to implement the following functionality:
when mouse comes over the combobox, it opens automatically.
when mouse leaves the combobox area (not only the combo, but dropdown list too), it closes automatically.
First point was quite easy:
procedure TForm1.ComboTimeUnitsMouseEnter(Sender: TObject);
begin
ComboTimeUnits.DroppedDown := True;
end;
The second point, though, I cannot do it. I tried:
procedure TForm1.ComboTimeUnitsMouseLeave(Sender: TObject);
begin
ComboTimeUnits.DroppedDown := False;
end;
But when the mouse is over the combobox, it acts very strange, appearing and disappearing, becoming unusable.
I tried AutoCloseUp property, with no result. Now I'm out of ideas and google couldn't help.
Can someone point me in the right direction?
There is no simple solution to your Combo Box (CB) request. I recall that the drop down List of a Windows CB is child to the screen and not the CB. The reason for this is to be able to display the drop down list outside of the client window as illustrated below. Pretty good stuff if you ask me.
Suggested solution
Here's a go at trying to use the existing TComboBox. TLama's "ugly code" is more elegant than mine because he uses an interceptor class. My suggestion below does however solve an additional case, namely the listbox does not roll up when the mouse moves up and crosses the boundry between the ListBox back to the Combobox.
unit main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.AppEvnts;
type
TFormMain = class(TForm)
ComboBox1: TComboBox;
Label1: TLabel;
Label2: TLabel;
procedure ComboBox1MouseEnter(Sender: TObject);
procedure ComboBox1CloseUp(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FActiveCb : TComboBox; //Stores a reference to the currently active CB. If nil then no CB is in use
FActiveCbInfo : TComboBoxInfo; //stores relevant Handles used by the currently active CB
procedure ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
public
{ Public declarations }
end;
var
FormMain: TFormMain;
implementation
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
begin
FActiveCb := nil;
FActiveCbInfo.cbSize := sizeof(TComboBoxInfo);
Application.OnIdle := Self.ApplicationEvents1Idle;
end;
procedure TFormMain.ComboBox1CloseUp(Sender: TObject);
begin
FActiveCb := nil;
end;
procedure TFormMain.ComboBox1MouseEnter(Sender: TObject);
begin
FActiveCb := TComboBox(Sender);
FActiveCb.DroppedDown := true;
GetComboBoxInfo(FActiveCb.Handle, FActiveCbInfo); //Get CB's handles
end;
procedure TFormMain.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
var w : THandle;
begin
//Check if the mouse cursor is within the CB, it's Edit Box or it's List Box
w := WindowFromPoint(Mouse.CursorPos);
with FActiveCbInfo do
if Assigned(FActiveCb) and (w <> hwndList) and (w <> hwndCombo) and (w <> hwndItem) then
FActiveCb.DroppedDown := false;
end;
end.
How to add additional CBs
Drop a new combobox on the form.
Assign ComboBox1MouseEnter proc to the OnMouseEnter event
Assign ComboBox1CloseUp proc to the OnCloseUp event
Issues
There are however certain issues that remain to be solved:
ListBox dissapears when user clicks
Text in the CB cannot be selected using the mouse
For sure more issues...
I am willing to designed one Application in Delphi XE2 Borderlessly and Captionlessly by using the following code :
BorderIcons = []
BorderStyle = bsNone
But the problem is that there is no Menu on Right Click on the Application on Taskbar just like in the above image. Then I have tried the following codes on FormShow event, but there is also another problem. One Border is created on Left side and Left-Botton side. The codes are :
procedure TForm1.FormShow(Sender: TObject);
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);
end;
Please help me.
The simple solution is not to remove the system menu in the first place. Note that the system menu is the official name for the menu that is missing in your app.
Make your .dfm file look like this:
BorderIcons = [biSystemMenu]
BorderStyle = bsNone
Get rid of that FormShow code–it's not needed.
OK, it looks like a stray bit of code from my experimentation was confounding me. Here's what works.
Do exactly what you originally did in your .dfm form:
BorderIcons = []
BorderStyle = bsNone
Then add back the system menu using CreateParams:
TForm1 = class(TForm)
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
...
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.Style := Params.Style or WS_SYSMENU;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
BorderStyle := bsNone;
SetWindowLong(Handle, GWL_STYLE,
WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN or WS_SYSMENU);
SetWindowLong(Handle, GWL_EXSTYLE, WS_EX_CONTROLPARENT or WS_EX_APPWINDOW);
end;
You don't need the code in the OnShow handler with this solution.
The above code can be called any time (not just in OnCreate), it can be used to alter the behavior of a running form for instance (just include WS_VISIBLE to window styles if the form is already visible).
If you want the behavior to be in effect for the life time of the form, it's better to set the flags in an overriden CreateParams (where form styles are applied by VCL). This will also take possible recreation of the form into account. Don't set any form property from the OI for this solution, all of the flags are explicitly set in the code:
type
TForm1 = class(TForm)
..
protected
procedure CreateParams(var Params: TCreateParams); override;
..
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.Style := WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN or WS_SYSMENU;
Params.ExStyle := WS_EX_CONTROLPARENT or WS_EX_APPWINDOW;
end;
You can have a window that appears not to have a caption bar, or a standard caption, by simply taking over the painting of the entire window:
Create a new empty application. Use this code for your form:
unit ncUnit1;
interface
// XE2 uses clause
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
// If you're not using XE2 take out the prefixes (WinApi, Vcl, System, etc)
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
protected
procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT;
procedure SolidColorNcPaint(solidColor,frameColor:TColor);
procedure Resizing(State: TWindowState); override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.WMNCPaint(var Message: TWMNCPaint);
begin
SolidColorNcPaint(clBtnFace,clBtnFace);
end;
procedure TForm1.Resizing(State: TWindowState);
begin
inherited;
PostMessage(Self.Handle,WM_NCPAINT,0,0); {force initial paint}
end;
procedure TForm1.SolidColorNcPaint(solidColor,frameColor:TColor);
var
aBorder:Integer;
ahdc : HDC;
begin
aBorder := GetSystemMetrics(SM_CYSIZEFRAME);
canvas.Lock;
ahdc := GetWindowDC(Handle);
canvas.Handle := ahdc;
ExcludeClipRect(canvas.Handle, aBorder, 0, Width-aBorder, Height - aBorder) ;
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := frameColor;
Canvas.Pen.Color := solidColor;
Canvas.Rectangle( 0,0, Width,Height);
ReleaseDC(Self.Handle, ahdc);
canvas.Handle := 0;
canvas.Unlock;
end;
end.
What you see above is only enough code to redraw a solid color over the non-client area of the window, not to remove it completely. Depending on the style of custom window you want, you should render whatever you want on the form. If you don't want a Close button then remove the close button, and if you do not want the resizing behaviour, remove the resizing behaviour. If you set the FormStyle=fsDialog plus the above code, you would get a window that has a complete custom drawn title area (which you can put whatever you want into). If you actually don't want the title area to exist at all, you can modify the above code to achieve that too.
You could do what David says and/or also take a look at:
SetWindowRgn API.
If you use just the SetWindowRgn you don't have to remove the TForm's border, just make a rectangle that starts below it.