Move form without border style - windows

how do I move a borderless form? I tried looking on the internet, but nothing. Thanks a lot.

You can drag a form using any contained control, including itself.
Using the following example, you can move a form by clicking on its canvas and dragging. You could do the same with a panel on the form by putting the same code in the panel's MouseDown event, which would let you create your own pseudo caption bar.
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
const
SC_DRAGMOVE = $F012;
begin
if Button = mbLeft then
begin
ReleaseCapture;
Perform(WM_SYSCOMMAND, SC_DRAGMOVE, 0);
end;
end;

If you mean dragging the window by the mouse, you can override WM_NCHITTEST message handling and return HTCAPTION for the drag region. The below will drag the window within the upper 30 pixels for insance:
type
TForm1 = class(TForm)
private
protected
procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
end;
..
procedure TForm1.WMNCHitTest(var Message: TWMNCHitTest);
var
Pt: TPoint;
begin
Pt := ScreenToClient(SmallPointToPoint(Message.Pos));
if Pt.Y < 30 then
Message.Result := HTCAPTION
else
inherited;
end;

Related

Set position of InfoTip

My TListView control has ShowHints enabled and handles the OnInfoTip event. The message in the popup InfoTip box is set in the OnInfoTip handler. However, the position of the popup InfoTip box is relative to the position of the mouse when hovering over an item in the list. There doesn't appear to be a way to customise the position.
Is it possible set the position of the hint popup, for example in a specific area of the TListView or even elsewhere on the form outside the bounds of the TListView control? Ideally, I'd like to display the hint popup in such a way to minimise (or eliminate) obscuring any other item in the TListView.
First you have to expose the CMHintShow of the TListView as following:
type
TListView = class(Vcl.ComCtrls.TListView)
private
FPos: TPoint;
protected
procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW;
published
property MyPos: TPoint read FPos write FPos;
end;
TfrmMain = class(TForm)
...
ListView1: TListView;
Then at the OnInfoTip event you set the desired Position. At my example, I get the coords of the TopLeft Corner of a ScrollBox (sbxFilter - which is located under the TlistView) and pass the Coords to the TListView property MyPos.
procedure TfrmMain.ListView1InfoTip(Sender: TObject; Item: TListItem; var InfoTip: string);
var
p: TPoint;
begin
InfoTip := 'Test';
p := sbxFilter.ClientToScreen(point(0, 0));
ListView1.MyPos := p;
end;
{ TListView }
procedure TListView.CMHintShow(var Message: TCMHintShow);
begin
inherited;
Message.HintInfo.HintPos := FPos;
end;
It is possible to display the hint, that you define in the OnInfoTip() event in e.g. a StatusPanel of a StatusBar (at the bottom of the form).
For example:
procedure TForm1.ListView1InfoTip(Sender: TObject; Item: TListItem;
var InfoTip: string);
begin
InfoTip := '';
StatusBar1.Panels[0].Text := 'ListView Infotip, Item '+IntToStr(Item.Index);
end;

How can I smooth minimize a window before removing the taskbar button when minimize/restore animations are on?

I am minimizing a form to system tray (display a tray icon) while keeping its taskbar button when it is not minimized. This implies removing the taskbar button when the form is minimized and restoring it otherwise.
The simplest way to achieve this is to hide/show the form, a minimized window does not show anyway.
type
TForm1 = class(TForm)
TrayIcon1: TTrayIcon;
procedure TrayIcon1DblClick(Sender: TObject);
protected
procedure WMSize(var Message: TWMSize); message WM_SIZE;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.WMSize(var Message: TWMSize);
begin
inherited;
case Message.SizeType of
SIZE_MINIMIZED:
if not TrayIcon1.Visible then begin
TrayIcon1.Visible := True;
Hide;
end;
SIZE_RESTORED, SIZE_MAXIMIZED:
if TrayIcon1.Visible then begin
Show;
Application.BringToFront;
TrayIcon1.Visible := False;
end;
end;
end;
procedure TForm1.TrayIcon1DblClick(Sender: TObject);
begin
Show;
WindowState := wsNormal;
end;
The above application introduces a visual glitch when "Animate windows when minimizing and maximizing" setting of the OS is on (accessible through 'SystemPropertiesPerformance.exe'). The minimize window animation is skipped. It appears that the animation actually takes place after the window is minimized. In the code, the window is already hidden by then.
One solution could be to have a notification when the window manager is done with the animation and hiding the form after that. I can't find any. When, for instance, you use the taskbar button minimizing the window, the last message sent is the WM_SYSCOMMAND, which doesn't lead to any progress if I move the code (not to mention that a window can be minimized through a ShowWindow and others).
Another solution might involve to know how long the animation takes place. SystemParametersInfo doesn't have it. Similar question here tries to deal with the animation displayed when a window is first shown, although that animation seems to be related with DWM and minimize/maximize animation precedes DWM. No conclusive solution there either. Like in that question, a 250ms delay seems to work fine. But I'm not sure this is a universally sound delay. I'm not even sure a discrete delay would be definitive, perhaps a stutter would cause it to extent (not that it would matter much, but anyway...).
I also tried to actually remove the taskbar button, without hiding the form. But it's more clumsy and it doesn't change the output: the animation is skipped.
Comment about DrawAnimatedRects (which draws no animation when Aero is on) convinced me to go slightly undocumented until I have a better alternative. Methods using DrawAnimatedRects have to determine where to minimize, that's where they use undocumented system tray window class name.
The below code goes undocumented when removing the taskbar button of the form, in particular with the use of the GWLP_HWNDPARENT index of SetWindowLongPtr. In any case, removing the taskbar button is not clumsy as in transforming the window to a tool window and the animation goes smooth.
The code falls back to a timer which hides the form in case removing the taskbar button fails.
type
TForm1 = class(TForm)
TrayIcon1: TTrayIcon;
Timer1: TTimer;
procedure TrayIcon1DblClick(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
protected
procedure WMSize(var Message: TWMSize); message WM_SIZE;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function ShowTaskbarButton(Wnd: HWND; Show: Boolean = True;
OwnerWnd: HWND = 0): Boolean;
var
ExStyle, HWndParent: LONG_PTR;
IsToolWindow: Boolean;
begin
HwndParent := GetWindowLongPtr(Wnd, GWLP_HWNDPARENT);
ExStyle := GetWindowLongPtr(Wnd, GWL_EXSTYLE);
Result := Show = (HWndParent = 0) and (ExStyle and WS_EX_APPWINDOW <> 0);
if not Result then begin
IsToolWindow := ExStyle and WS_EX_TOOLWINDOW <> 0;
if IsToolWindow then begin
ShowWindow(Wnd, SW_HIDE);
ShowWindowAsync(Wnd, SW_SHOW);
end;
SetLastError(0);
if Show then
SetWindowLongPtr(Wnd, GWL_EXSTYLE, ExStyle or WS_EX_APPWINDOW)
else
SetWindowLongPtr(Wnd, GWL_EXSTYLE, ExStyle and not WS_EX_APPWINDOW);
if not IsToolWindow and (GetLastError = 0) then
SetWindowLongPtr(Wnd, GWLP_HWNDPARENT, OwnerWnd);
Result := GetLastError = 0;
end;
end;
procedure TForm1.WMSize(var Message: TWMSize);
begin
inherited;
case Message.SizeType of
SIZE_MINIMIZED:
if not TrayIcon1.Visible then begin
if not ShowTaskbarButton(Handle, False, Application.Handle) then
Timer1.Enabled := True; // fall back
TrayIcon1.Visible := True
end;
SIZE_RESTORED, SIZE_MAXIMIZED:
if TrayIcon1.Visible then begin
ShowTaskbarButton(Handle);
Application.BringToFront;
TrayIcon1.Visible := False;
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Timer1.Interval := 250;
Timer1.Enabled := False;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Hide;
Timer1.Enabled := False;
end;
procedure TForm1.TrayIcon1DblClick(Sender: TObject);
begin
ShowTaskbarButton(Handle);
if not Showing then // used timer to hide
Show;
WindowState := wsNormal;
end;

How to create a Canvas object inside a Paintbox on Lazarus?

On a Form I've put a Paintbox with the property Align = alClient and a Button.
I need to draw a object of Canvas type inside the PaintBox in the OnClick event of the Button.
This is the Canvas object that will be created:
const IconSize:Integer = 10
type
Icon = Class
public
posX, posY:Integer;
constructor Create(X,Y:Integer);
destructor Destroy;
procedure SetX(AValue: Integer);
procedure SetY(AValue: Integer);
published
property LocationX : Integer read posX write SetX;
property LocationY : Integer read posY write SetY;
end;
var CanvasIcon: Icon;
This is constructor method of the object:
constructor Icon.Create(X, Y: Integer);
var Bitmap:TBitmap;
begin
Bitmap:=TBitmap.Create;
try
Bitmap.Height := IconSize;
Bitmap.Width := IconSize;
Bitmap.Canvas.Pen.Color := clBlack;
Bitmap.Canvas.Rectangle(Round(X-(IconSize/2)), Round(Y-(IconSize/2)),
Round(X+(IconSize/2)), Round(Y+(IconSize/2)));
PaintBox.Canvas.Draw(0, 0, Bitmap);
finally
Bitmap.Free;
end;
end;
This is the OnClick event of the Button:
procedure TFormPaintBox.Button1Click(Sender: TObject);
begin
CanvasIcon:=Icon.Create(10,10);
end;
However, the Lazarus show the follow message:
src/unitpaintbox.pas(121,33) Error: Wrong number of parameters specified for call to "Create"
But the constructor receive two parameters, exactly like was specified on onClick event of the Button. How to solve this problem?

Delphi XE and Trapping Arrow Key with OnKeyDown

I want my form to handle the arrow keys, and I can do it -- as long as there is no button on the form. Why is this?
Key messages are processed by the controls themselves who receives these messages, that's why when you're on a button the form is not receiving the message. So normally you would have to subclass these controls, but the VCL is kind enough to ask the parenting form what to do if the form is interested:
type
TForm1 = class(TForm)
..
private
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
..
procedure TForm1.DialogKey(var Msg: TWMKey);
begin
if not (Msg.CharCode in [VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT]) then
inherited;
end;
François editing: to answer the OP original question, you need to call onKeyDown somehow so that his event code would work (feel free to edit; was too long for a comment).
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private declarations }
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.DialogKey(var Msg: TWMKey);
begin
case Msg.CharCode of
VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT:
if Assigned(onKeyDown) then
onKeyDown(Self, Msg.CharCode, KeyDataToShiftState(Msg.KeyData));
else
inherited
end;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of
VK_DOWN: Top := Top + 5;
VK_UP: Top := Top - 5;
VK_LEFT: Left := Left - 5;
VK_RIGHT: Left := Left + 5;
end;
end;
Arrow keys are used to navigate between buttons on a form. This is standard Windows behaviour. Although you can disable this standard behaviour you should think twice before going against the platform standard. Arrow keys are meant for navigation.
If you want to get the full low down on how a key press finds its way through the message loop I recommend reading A Key's Odyssey. If you want to intercept the key press before it becomes a navigation key, you need to do so in IsKeyMsg or earlier. For example, Sertac's answer gives one such possibility.
Only the object that has the focus can receive a keyboard event.
To let the form have access to the arrow keys event,
declare a MsgHandler in the public part of the form.
In the form create constructor, assign the Application.OnMessage to this MsgHandler.
The code below intercepts the arrow keys only if they are coming from a TButton descendant. More controls can be added as needed.
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage := Self.MsgHandler;
end;
procedure TForm1.MsgHandler(var Msg: TMsg; var Handled: Boolean);
var
ActiveControl: TWinControl;
key : word;
begin
if (Msg.message = WM_KEYDOWN) then
begin
ActiveControl := Screen.ActiveControl;
// if the active control inherits from TButton, intercept the key.
// add other controls as fit your needs
if not ActiveControl.InheritsFrom(TButton)
then Exit;
key := Msg.wParam;
Handled := true;
case Key of // intercept the wanted keys
VK_DOWN : ; // doStuff
VK_UP : ; // doStuff
VK_LEFT : ; // doStuff
VK_RIGHT : ; // doStuff
else Handled := false;
end;
end;
end;
Because they are preempted to deal with setting the focus on the next available WinControl.
(I'm pretty sure that if you put an Edit instead of a Button you see the same thing).
If you want to handle them yourself, you can provide the Application with an OnMessage event that will filter those before they are processed and handle them yourself there.
var
KBHook: HHook; {this intercepts keyboard input}
implementation
{$R *.dfm}
function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt): LongInt; stdcall;
begin
case WordParam of
vk_Space: ShowMessage ('space') ;
vk_Right:ShowMessage ('rgt') ;
vk_Left:ShowMessage ('lft') ;
vk_Up: ShowMessage ('up') ;
vk_Down: ShowMessage ('down') ;
end; {case}
end;
procedure TForm4.FormCreate(Sender: TObject);
begin
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc,HInstance,GetCurrentThreadId());
end;
This code will work even when a control is focused (buttons , listboxes), so be careful some controls may loose their keyboard events (Read David haffernans answer) .
keyboard events with Focused controls
eg: If you are having textbox in your app and want to recive text(if focused) also , then
add an applicationevent1
procedure TForm4.ApplicationEvents1Message(var Msg: tagMSG;var Handled: Boolean);
begin
if Msg.message = WM_KEYFIRST then
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc,HInstance,GetCurrentThreadId());
end;
add the following code at the bottom of the function KeyboardHookProc
UnhookWindowsHookEx(KBHook);
and remove
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc, HInstance,
GetCurrentThreadId());
from oncreate event.

How do I create a window without any frame regardless of user settings?

I need to write an application that displays two different pictures in two instances of the application. These pictures must look as if they were put side by side on the canvas of the same window but for internal reasons it must be two different applications not a single one. Is there any way to turn off the window frame regardless of what the user's Windows settings are? I still want to retain the title bar and the close/minimize/maximize buttons.
Bonus points if the two (or multiple) windows look and react like a single one to the user.
A Delphi example would be nice but I can probably do with a hint on which flags or whatever to set using Win32 API (no dotNET please).
Since windows with title bars always have borders, your next option is to make a borderless window and then paint a title bar at the top of the window yourself. That means handling mouse messages, too. Start with wm_NCHitTest. To make a borderless window, override your form's CreateParams method and set the Style field so there's no border.
This creates a Form without side or bottom borders:
type
TForm1 = class(TForm)
private
FBorderWidth: Integer;
FTitleHeight: Integer;
procedure AppRestored(Sender: TObject);
procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT;
protected
procedure Resize; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.AppRestored(Sender: TObject);
begin
Repaint;
end;
procedure TForm1.Resize;
begin
inherited Resize;
if FBorderWidth = 0 then
begin
FBorderWidth := (Width - ClientWidth) div 2;
FTitleHeight := Height - ClientHeight - FBorderWidth;
Application.OnRestore := AppRestored;
end;
Invalidate;
end;
procedure TForm1.WMNCCalcSize(var Message: TWMNCCalcSize);
begin
inherited;
with Message.CalcSize_Params^ do
begin
Dec(rgrc[0].Left, FBorderWidth);
Inc(rgrc[0].Right, FBorderWidth);
Inc(rgrc[0].Bottom, FBorderWidth);
end;
end;
procedure TForm1.WMNCPaint(var Message: TWMNCPaint);
begin
DeleteObject(Message.RGN);
Message.RGN := CreateRectRgn(Left, Top, Left + Width, Top + FTitleHeight);
inherited;
end;

Resources