Delphi form without system menu but with close button - delphi

By default, a form having BorderStyle=bsSizeable has a system menu (on the left) and a close button ('X', on the right). I want to get rid of the system menu and keep the close button.
The BorderIcons property lets me remove the system menu (via biSystemmenu), but now the close button is gone too.
Is there a way to do this?
Using Delphi XE
PS: it should be possible as far as Windows is concerned: IE8's "InPrivate Filtering settings" window is sizeable, has a close button and has no system menu.

BorderStyle := bsSizeToolWin does what you want, with a slightly different appearance of the X button.

By "system menu" do you mean icon on the left of title bar? Or popup menu invoked via right click?
If it is icon that you want to remove - use this code:
const
WM_ResetIcon = WM_APP - 1;
type
TForm1 = class(TForm)
procedure FormShow(Sender: TObject);
protected
procedure WMResetIcon(var Message: TMessage); message WM_ResetIcon;
end;
implementation
procedure TForm1.FormShow(Sender: TObject);
begin
PostMessage(Handle, WM_ResetIcon, 0, 0);
end;
procedure TForm1.WMResetIcon(var Message: TMessage);
const
ICON_SMALL = 0;
ICON_BIG = 1;
begin
DestroyIcon(SendMessage(Handle, WM_SETICON, ICON_BIG, 0));
DestroyIcon(SendMessage(Handle, WM_SETICON, ICON_SMALL, 0));
end;

I don't think there is a way to do this without resorting to custom drawing your non-client area which is very difficult when glass is involved.
Consider this method.
procedure TMyForm.DeleteSystemMenu;
var
SystemMenu: HMenu;
begin
SystemMenu := GetSystemMenu(Handle, False);
DeleteMenu(SystemMenu, SC_CLOSE, MF_BYCOMMAND);
end;
Yes it succeeds in getting rid of the close item from the system menu, but it also results in the close button being disabled. So it would seem that you can't have one without the other.

Related

TEdit with clear button [duplicate]

When use TEdit control on the right side stay small icon 'x'. How after click on icon clear TEdit box.
Tnx all!
Delphi provide TClearEditButton to clear the TEdit content. It can be added by right clicking and selecting AddItem - TClearEditButton from the popup menu. It also has a Click procedure overriden in FMX.Edit unit like:
procedure TClearEditButton.Click;
var
EditTmp: TCustomEdit;
begin
inherited Click;
EditTmp := GetEdit;
if EditTmp <> nil then
begin
if EditTmp.Observers.IsObserving(TObserverMapping.EditLinkID) then
if not TLinkObservers.EditLinkEdit(EditTmp.Observers) then
Exit; // Can't change
EditTmp.Text := string.Empty;
if EditTmp.Observers.IsObserving(TObserverMapping.EditLinkID) then
TLinkObservers.EditLinkModified(EditTmp.Observers);
if EditTmp.Observers.IsObserving(TObserverMapping.ControlValueID) then
TLinkObservers.ControlValueModified(EditTmp.Observers);
end;
end;
Which make you don't need to write OnClick event handler for the TClearEditButton unless you want to do some other job along side with clearing the edit.
If you are using a TEditButton then you should write the OnClick event handler like:
procedure TForm1.EditButton1Click(Sender: TObject);
begin
Edit1.Text:= EmptyStr;
end;

Disable form restoring on title doubleclick

Create an empty Delphi VCL project
Remove all BorderIcons of main form
Set WindowState to wsMaximized
Run application. Main window appears maximized.
Double click on window title. Main window restores it's size and there is no possibility to maximize it again.
How to prevent window restoring on title double click without hiding title bar?
You can intercept the restore and additionally the move system commands to prevent restoring by dragging the caption.
type
TForm1 = class(TForm)
protected
procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;
...
procedure TForm1.WMSysCommand(var Message: TWMSysCommand);
begin
case Message.CmdType and $FFF0 of
SC_MOVE, SC_RESTORE: Exit;
end;
inherited;
end;
I tested some solutions and the one that worked was:
Set Align property to alClient;
Remove biMaximize from BorderIcons property;
Let WindowState wsNormal (default).
Answer has been edited!
If I got you right, you might want to forbid double-click by title bar in order to prevent restoring form to its original size. You can do that by intercepting WM_NCLBUTTONDBLCLK.
In the example below I've overridden WndProc method of main form and hook forementioned message.
procedure TForm1.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_NCLBUTTONDBLCLK:
begin
case TWMNCHitMessage(Message).HitTest of
HTCAPTION:
Exit
else // Another HitTest-codes are handled here
Inherited WndProc(Message);
end;
end
else
Inherited WndProc(Message);
end;
end;
Important note
Although now you cannot restore maximized form by double-clicking, it still can be restored just by moving it while mouse is captured by title bar. At least, on Windows 7 this effect is presented.
Steps to reproduce:
Run application;
Press left mouse button while it hovered over title bar;
Don't release LMB and move mouse softly - now form restores its size.
Addendum
Fixed bug with incorrect handling another non-client HitTest-codes excepting HTCAPTION (thanks to user Dsm for pointing this out!).

Main Menu in toolbar not working as expected

I create a VCL forms application, add a main menu and insert the "MDI Frame Menu" from the menu templates. I run the programme and use the acceleration keys. Everything works as expected.
I now add a toolbar, disconnect the main menu from the form and link it to the toolbar. I run the programme . Now the menu items activate by just pressing the appropriate key without pressing Alt (e.g. pressing "W" opens the Windows menu item.
How can I get the menu on the toolbar to behave like the main menu without it?
I created one answer to my question (there might be simpler and more elegant ones). Steps to make a TMainMenu in a toolbar behave the same as a TMainMenu on a form are:
Put a TMainMenu on MainForm
Populate the MainMenu as needed
Put an ActionList on the form
Make sure all the menu items act via the ActionList
Clear the menu property of the MainForm
Put a TToolbar on the form
Assign the MainMenu to the Toolbar
Write the procedure DeleteHotKeysOfToolbarMenu(Sender: TObject); (see code snippet below)
Write code for the ActionList.OnExecute event as shown below
Set the KeyPreview property of the MainForm to true
Write the MainForm.OnKeyPress event as shown below
In the Create procedure of the MainForm call DeleteHotKeysOfToolbarMenu to start the programme without any visible hot keys
That is it
Below are the code snippets:
procedure TMainForm.DeleteHotKeysOfToolbarMenu(Sender: TObject);
var
m: integer;
begin
for m := 0 to Toolbar.ButtonCount-1 do
Toolbar.Buttons[m].Caption := StripHotKey(Toolbar.Buttons[m].Caption);
end;
procedure TMainForm.ActionListExecute(Action: TBasicAction; var Handled: Boolean);
begin
if Action.ActionComponent.ClassType = TMenuItem then
DeleteHotKeysOfToolbarMenu(Self);
end;
TMainForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (ssAlt in Shift) or (Key = VK_F10) then
begin
Toolbar.Menu := nil;
Toolbar.Menu := MainMenu;
end;
if Key = VK_Escape then DeleteHotKeysOfToolbarMenu(Self);
end;

How to put controls into a Design State Mode just like the Form Designer does?

This one has been puzzling me for some time now and maybe the answer is an easy one, or perhaps it involves much more VCL hacking or magic to accomplish what I am looking for but either way I am at a loss as to how to solve my problem.
If you look at the Delphi Form Designer you will see that none of the controls animate when the mouse moves over them, they also cannot receive focus or input (eg you cannot type into a TEdit, click a TCheckBox or move a TScrollBar etc), only at runtime do the controls behave normally and respond to user interaction.
I want to know how can I implement this type of behavior to any control at runtime, eg set controls into like a Designer State Mode? However, controls should also still respond to Mouse Events such as OnMouseDown, OnMouseMove, OnMouseUp etc so they can be moved and sized if needed for example.
This is the closest that I managed:
procedure SetControlState(Control: TWinControl; Active: Boolean);
begin
SendMessage(Control.Handle, WM_SETREDRAW, Ord(Active), 0);
InvalidateRect(Control.Handle, nil, True);
end;
Which could be called simply like so:
procedure TForm1.chkActiveClick(Sender: TObject);
begin
SetControlState(Button1, chkActive.Checked);
SetControlState(Button2, chkActive.Checked);
SetControlState(Edit1, chkActive.Checked);
end;
Or for example, all controls on the form:
procedure TForm1.chkActiveClick(Sender: TObject);
var
I: Integer;
Ctrl: TWinControl;
begin
for I := 0 to Form1.ControlCount -1 do
begin
if Form1.Controls[I] is TWinControl then
begin
Ctrl := TWinControl(Form1.Controls[I]);
if (Ctrl <> nil) and not (Ctrl = chkActive) then
begin
SetControlState(Ctrl, chkActive.Checked);
end;
end;
end;
end;
Two problems I have noticed with the above is that whilst the controls do appear to become Design State like, some controls such as TButton still have the animation effect painted on them. The other issue is when pressing the left Alt key when the controls are Design State like causes them to disappear.
So my question is, how do I put controls into a Design State mode at runtime just like the Delphi Form Designer does, where those controls do not animate (based on Windows Theme) and cannot receive focus or input?
To make that bit clearer, look at this sample image based off the above code sample where the controls are no longer active, but the TButton's animation paint is still active:
But should actually be:
From the two images above, only the TCheckBox control can be interacted with.
Is there a procedure hidden away somewhere that can change the state of a control? Or perhaps a more suitable approach to achieving this? The code I managed to get so far just presents more problems.
Setting the controls to Enabled := False is not an answer I am looking for either, yes the behavior is kind of the same but of course the controls paint differently to show they are disabled which is not what I am looking for.
What you are looking for is not a feature of the controls themselves, but rather is an implementation of the Form Designer itself. At design-time, user input is intercepted before it can be processed by any given control. The VCL defines a CM_DESIGNHITTEST message to allow each control to specify whether it wants to receive user input at design-time (for example to allow visual resizing of list/grid column headers). It is an opt-in feature.
What you can do, though, is put the desired controls onto a borderless TPanel, and then simply enable/disable the TPanel itself as needed. That will effectively enable/disable all user input and animations for its child controls. Also, when the TPanel is disabled, the child controls will not render themselves as looking disabled.
Remy Lebeau's answer on putting controls into a container such as a TPanel, and then setting the panel to Enabled := False does put the controls into the state I was looking for. I also discovered that overriding the controls WM_HITTEST put the controls into the same state, eg they don't receive focus and cannot be interacted with. The problem with those two is that the controls still need to be able to respond to MouseDown, MouseMove and MouseUp events etc but they no longer cannot.
Remy also suggested writing a class and implement Vcl.Forms.IDesignerHook, something I have not attempted yet as maybe it requires too much work for what I need.
Anyway, after lots of playing around I found another alternative way, it involves the use of PaintTo to draw the control onto a canvas. The steps I did are as follows:
Create a custom TPanel with an exposed Canvas
At FormCreate create and align the custom panel to client
Add controls to the form at runtime (bringing the custom panel to the front)
Call the controls PaintTo method onto the custom panels Canvas
What this is essentially doing is creating the components and using the Form as the parent with our custom panel sitting on top. The controls are then painted onto the panels canvas which makes it appear as if the control is on the panel, when actually it sits underneath on the form undisturbed.
Because the controls are underneath the panel, in order for them to respond to events such as MouseDown, MouseMove and MouseUp etc I overrided the WM_NCHitTest in the panel and set the result to HTTRANSPARENT.
In code it would look something like this:
Custom panel:
type
TMyPanel = class(TPanel)
protected
procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHitTest;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Canvas;
end;
{ TMyPanel }
constructor TMyPanel.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Align := alClient;
BorderStyle := bsNone;
Caption := '';
end;
destructor TMyPanel.Destroy;
begin
inherited Destroy;
end;
procedure TMyPanel.WMNCHitTest(var Message: TWMNCHitTest);
begin
Message.Result := HTTRANSPARENT;
end;
Form:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FMyPanel: TMyPanel;
procedure ControlMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
public
{ Public declarations }
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
FMyPanel := TMyPanel.Create(nil);
FMyPanel.Parent := Form1;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FMyPanel.Free;
end;
procedure TForm1.ControlMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Sender is TWinControl then
begin
ShowMessage('You clicked: ' + TWinControl(Sender).Name);
end;
end;
Example of adding a TButton to the form:
procedure TForm1.Button1Click(Sender: TObject);
var
Button: TButton;
begin
Button := TButton.Create(Form1);
Button.Parent := Form1;
FMyPanel.BringToFront;
with Button do
begin
Caption := 'Button';
Left := 25;
Name := 'Button';
Top := 15;
OnMouseDown := ControlMouseDown;
PaintTo(FMyPanel.Canvas, Left, Top);
Invalidate;
end;
end;
If you try running the above, you will see that the TButton we created does not animate or receive focus, but it can respond to MouseDown events we attached in the code above, that is because we are not actually looking at the control, instead we are viewing a graphical copy of the control.
I'm not sure if this is what you're after or not, but Greatis has a Form Designer component. See: http://www.greatis.com/delphicb/formdes/

TPageControl tab area OnMouseEnter OnMouseLeave events

I need to catch the "OnMouseEnter" and "0nMouseLeave" for a certain area of the TPageControl component. With that specific area I mean the whole "tab header" rectangle.
The problem is, that the page control doesn't catch the messages (I'm catching internal control messages CM_MOUSEENTER and CM_MOUSELEAVE) in the "empty" space.
The aim for me is to draw a small arrow in the right empty side when user hovers in the red framed area (and drawing is just piece of cake) and erase it when leaves this area. And I'm don't care about the overflow of the tabs (which causes to draw scrolling double button) - that will never happen.
Here is the working piece of code, but it's not the clear solution and I don't like it. There must be another (more clean) way to do it.
type
TPageControl = class(ComCtrls.TPageControl)
protected
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
end;
procedure TPageControl.CMMouseLeave(var Message: TMessage);
begin
inherited;
Canvas.TextOut(Width - 130, 5, 'CMMouseLeave'); // display result
end;
procedure TPageControl.WMNCHitTest(var Message: TWMNCHitTest);
var TabHeaderRect: TRect;
begin
if Message.Result = 0 then // if Message.Result = HTNOWHERE ...
begin
TabHeaderRect := ClientRect;
TabHeaderRect.Bottom := Top + 21;
if PtInRect(TabHeaderRect, ScreenToClient(Point(Message.XPos, Message.YPos))) then
Canvas.TextOut(Width - 130, 5, 'WMNCHitTest '); // display result
Message.Result := HTCLIENT;
end
else
inherited;
end;
Obviously, the empty space does not belong to the control's client area and so the control doesn't get any mouse-related Windows messages for that area. You will have to use the form's mouse events. Or put the page control inside a panel (using alClient) and use the panel's mouse events.
If you need this more than once, you could create a new component that does exactly that (combine a panel and a page control to achieve the desired behaviour).
Are you sure you're handling OnMouseEnter/OnMouseLeave for the page control itself, and not for the TTabSheet instance that it contains?

Resources