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;
Related
I am using the following code from this posting.
Code from Checked Answer
I need to get the Control (Label.Caption) under the mouse cursor from one of several TLabel and it worked fine when the Label was on the Main From. I put the Labels on a Panel on the Main form and now this only finds the Panel. I only want this to work on a select few of the Labels of the many that are on the Panel.
I tried changing the Z-Order for the Labels as "Bring To Front" but it made no difference, still got the Panel. How can I again find a Label under the cursor now that they are on the Panel?
Lazarus does not appear to have FindVCLWindow or ObjectAtPoint.
procedure TForm1.Button1Click(Sender: TObject);
var
ctrl : TControl;
point : TPoint;
begin
point := Mouse.CursorPos; // Mouse pos at screen
Dec(point.X, Left); // Adjust for window.
Dec(point.Y, Top);
Dec(point.Y, GetSystemMetrics(SM_CYCAPTION)); // Adjust to client area.
ctrl := ControlAtPos(point, True, True, True);
// I added the following
tStr:=ctrl.Name; // DEBUG: This now shows "Panel2"
aStr:=(ctrl as TLabel).Caption; // This used to work
end;
Try:
procedure TForm1.Button1Click(Sender: TObject);
var
ctrl: TControl;
pt: TPoint;
begin
pt := ScreenToClient(Mouse.CursorPos);
ctrl := ControlAtPos(pt, [capfRecursive, capfAllowWinControls]);
if Assigned(ctrl) then
Caption := ctrl.Name
else
Caption := Format('%d, %d', [pt.x, pt.y]);
end;
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?
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;
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.
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;