I am working with Delphi 10.4.2 in Windows 10 (virtualized in Parallels) on a dual monitor system. To recreate the problem on a multi-monitor system, create a new Windows VCL Application and place two buttons on the form: btnPrimaryMonitor and btnSecondaryMonitor. Then insert this code by creating click handlers for the two buttons:
procedure TForm1.btnPrimaryMonitorClick(Sender: TObject);
begin
RepositionFormToMonitor(0);
EnableDisableButtons;
end;
procedure TForm1.RepositionFormToMonitor(const aMonitor: Integer);
const
offset = 2;
begin
Self.Width := Screen.Monitors[aMonitor].Width - offset;
Self.Height := Screen.Monitors[aMonitor].Height - offset;
Self.Top := Screen.Monitors[aMonitor].Top;
Self.Left := Screen.Monitors[aMonitor].Left;
end;
procedure TForm1.btnSecondaryMonitorClick(Sender: TObject);
begin
RepositionFormToMonitor(1);
EnableDisableButtons;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
EnableDisableButtons;
Self.BorderStyle := bsNone;
Self.FormStyle := fsStayOnTop;
RepositionFormToMonitor(0);
end;
procedure TForm1.EnableDisableButtons;
begin
btnPrimaryMonitor.Enabled := (Self.Monitor.MonitorNum = 1);
btnSecondMonitor.Enabled := (Self.Monitor.MonitorNum = 0);
end;
This works perfectly, but as soon as I set offset = 1 or offset = 0 the screen becomes black!
The purpose of the code is to reposition the maximized stay-on-top Form from the primary monitor to the secondary monitor by clicking on the btnSecondMonitor button and then back to the primary monitor by clicking on the btnPrimaryMonitor button.
How can this problem be avoided?
A few issues:
You should not set WindowState to wsMaximized. In fact, you shouldn't touch this property at all.
Setting BoundsRect will set Left, Top, Width, and Height, so there is no need to set Left and Top separately.
To go back to the primary monitor, just set the form's BoundsRect.
Here's an example:
Create a new VCL project. Set the main form's BorderStyle to bsNone.
Then add the following code:
procedure TForm1.FormCreate(Sender: TObject);
begin
for var i := 0 to Screen.MonitorCount - 1 do
begin
var btn := TButton.Create(Self);
btn.Parent := Self;
btn.Caption := i.ToString;
btn.Tag := i;
btn.OnClick := MonitorButtonClick;
btn.Top := 8;
btn.Left := 8 + (btn.Width + 8) * i;
end;
end;
procedure TForm1.MonitorButtonClick(Sender: TObject);
begin
BoundsRect := Screen.Monitors[(Sender as TButton).Tag].BoundsRect;
end;
If this code doesn't work properly on your system, you probably have some problem with that Windows system. This should work flawlessly.
this problem is driving me crazy, i have an edit box in which i write something. On event 'change' of edit box, a ListBox is created and filled by SQL query. It works as a hint box while writing.
When i hit enter on the item which i want to select, the listbox should 'free', but it continues to return me 'access violation'. Here the code:
procedure TFTimbra.EditCommessaKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
X, Y, W: Integer;
QSugg: TAdoQuery;
begin
if not Assigned(Suggerimento) then
begin
Suggerimento := TListBox.Create(Self);
Y := EditCommessa.Top + EditCommessa.Height;
X := EditCommessa.Left;
W := EditCommessa.Width;
with Suggerimento do
begin
Top := Y;
Left := X;
Width := W;
Height := 200;
Parent := FTimbra;
BorderStyle := bsNone;
Font.Size := 14;
Font.Style := [fsBold];
end;
end else
Suggerimento.Clear;
if Key = 40 then
Suggerimento.SetFocus;
QSugg := TAdoQuery.Create(nil);
QSugg.ConnectionString := DMMain.DBConnection.ConnectionString;
QSugg.SQL.Text := format('select Codice, Descrizione from Commesse where Descrizione like %s',
[quotedstr('%' + EditCommessa.Text + '%')]);
QSugg.Open;
while not QSugg.Eof do
begin
Suggerimento.Items.Add(QSugg.FieldByName('Descrizione').AsString);
QSugg.Next;
end;
QSugg.Close;
if Assigned(Suggerimento) then Suggerimento.OnKeyDown := SuggerimentoKeyDown;
end;
This is the first part, and this is the code that "should" free the listbox:
procedure TFTimbra.SuggerimentoKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = 13 then
begin
Commessa := Suggerimento.Items[Suggerimento.ItemIndex];
EditCommessa.Text := Commessa;
Suggerimento.Free;
end;
end;
I think the problem is in the call of the OnKeyDown function.. Thank you in advance.
You can't destroy an object from one of that object's own event handlers. When the event handler returns, the code continues executing in the context of the object, which you just freed. And that typically leads to runtime errors like this.
Rather than use a dynamic lifetime for this list box control, create it in the traditional way, using the form designer. When you want it hidden, set Visible to False. When you want it to show, set Visible to True.
I have created an array of radiobuttons, which will be created in an event. I want to create an event, saying to make the radiobutton invisible when it is clicked and the show a message. But it has to happen on click. Can you help me?
This is how I created my radiobuttons
for k := 1 to 20 do
begin
rd[k] := TRadioButton.Create(Self);
rd[k].Parent := pgcVerkiesing;
rd[k].Caption := 'rs'+IntToStr(k);
rd[k].Left := 16;
if k = 1 then
rd[k].Top := 26
else
rd[k].Top := (k*24) ;
rd[k].OnClick := OnClick;
end;
Now I want to do something like this : rs1.clicked //procedure
rs1.disabled := true;
richedit1.lines.add := 'Name';
showmessage(names);
What to do?
If I understood correctly, you want to disable the clicked radiobutton.
Define an event for your radiobuttons:
procedure TForm1.OnRadioButtonClick(Sender : TObject);
When creating your radiobuttons, tie this event handler to the radiobuttons.
rd[k].OnClick := OnRadioButtonClick;
procedure TForm1.OnRadioButtonClick(Sender : TObject);
begin
TRadioButton(Sender).Enabled := false;
RichEdit1.Lines.Add( 'Name');
ShowMessage( names); // names not defined ??
end;
I would like to know how to make my second trackbar.position mirror in the opposite direction of trackbar1.position.
eg.
Range from 1 to 100.
So When TrackBar1.Position := 2, then trackbar2.Position := 99
Regardless of which way the trackbars goes, I would like to mirror in the opposite direction.
Heres my code so far: (not interested in using keys to do this), just mouse interaction.
Direction : string;
Skip : boolean;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
if TrackBar1.Position = TrackBar2.Position then
begin
if Direction = 'up' then TrackBar2.Position := TrackBar2.Position + 1;
if Direction = 'down' then TrackBar2.Position := TrackBar2.Position - 1;
skip := true;
end;
if TrackBar1.Position < TrackBar2.Position then
begin
if skip = false then
begin
TrackBar2.Position := TrackBar2.Position - 1;
Direction := 'down';
end;
end
else
begin
if skip = false then
begin
TrackBar2.Position := TrackBar2.Position + 1;
Direction := 'up';
end;
end;
end;
Im probably overdoing this. Maybe there is a simpler way. I prefer the simpler way.
Thanks,
Ben
The 2 trackbars OnChange events are linked to this code:
procedure TForm1.TrackBarChange(Sender: TObject);
var
tbSource, tbTarget: TTrackBar;
begin
if Sender = TrackBar1 then // Check the Trackbar which triggers the event
begin
tbSource := TrackBar1;
tbTarget := TrackBar2;
end
else
begin
tbSource := TrackBar2;
tbTarget := TrackBar1;
end;
tbTarget.OnChange := nil; // disable the event on the other trackbar
tbTarget.Position := tbSource.Max + tbSource.Min - tbSource.Position; // set the position on the other trackbar
tbTarget.OnChange := TrackBarChange; // define the event back to the other trackbar
// Call a function or whatever after this line if you need to do something when it changes
// lbl1.Caption := IntToStr(TrackBar1.Position);
// lbl2.Caption := IntToStr(TrackBar2.Position);
end;
Alternative start (suggested by Ken White and comments from me ;o)):
procedure TForm1.TrackBarChange(Sender: TObject);
var
tbSource, tbTarget: TTrackBar;
begin
// if Sender is TTrackBar then // is it called 'from' a trackbar?
// begin
tbSource := TTrackBar(Sender); // Set the source
if tbSource = TrackBar1 then // Check the Trackbar which triggers the event
tbTarget := TrackBar2
else
tbTarget := TrackBar1;
tbTarget.OnChange := nil; // disable the event on the other trackbar
tbTarget.Position := tbSource.Max + tbSource.Min - tbSource.Position; // set the position on the other trackbar
tbTarget.OnChange := TrackBarChange; // define the event back to the other trackbar
// Call a function or whatever after this line if you need to do something when it changes
// lbl1.Caption := IntToStr(TrackBar1.Position);
// lbl2.Caption := IntToStr(TrackBar2.Position);
// end;
end;
Is it possible to get Delphi to close a ShowMessage or MessageDlg Dialog after a certain length of time?
I want to show a message to the user when the application is shut down, but do not want to stop the application from shutting down for more than 10 seconds or so.
Can I get the default dialog to close after a defined time, or will I need to write my own form?
Your application is actually still working while a modal dialog or system message box or similar is active (or while a menu is open), it's just that a secondary message loop is running which processes all messages - all messages sent or posted to it, and it will synthesize (and process) WM_TIMER and WM_PAINT messages when necessary as well.
So there's no need to create a thread or jump through any other hoops, you simply need to schedule the code that closes the message box to be run after those 10 seconds have elapsed. A simple way to do that is to call SetTimer() without a target HWND, but a callback function:
procedure CloseMessageBox(AWnd: HWND; AMsg: UINT; AIDEvent: UINT_PTR;
ATicks: DWORD); stdcall;
var
Wnd: HWND;
begin
KillTimer(AWnd, AIDEvent);
// active window of the calling thread should be the message box
Wnd := GetActiveWindow;
if IsWindow(Wnd) then
PostMessage(Wnd, WM_CLOSE, 0, 0);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
TimerId: UINT_PTR;
begin
TimerId := SetTimer(0, 0, 10 * 1000, #CloseMessageBox);
Application.MessageBox('Will auto-close after 10 seconds...', nil);
// prevent timer callback if user already closed the message box
KillTimer(0, TimerId);
end;
Error handling ommitted, but this should get you started.
You can try to do it with a standard Message dialog. Create the dialog with CreateMessageDialog procedure from Dialogs and after add the controls that you need.
In a form with a TButton define onClick with this:
procedure TForm1.Button1Click(Sender: TObject);
var
tim:TTimer;
begin
// create the message
AMsgDialog := CreateMessageDialog('This is a test message.',mtWarning, [mbYes, mbNo]) ;
lbl := TLabel.Create(AMsgDialog) ;
tim := TTimer.Create(AMsgDialog);
counter := 0;
// Define and adding components
with AMsgDialog do
try
Caption := 'Dialog Title' ;
Height := 169;
// Label
lbl.Parent := AMsgDialog;
lbl.Caption := 'Counting...';
lbl.Top := 121;
lbl.Left := 8;
// Timer
tim.Interval := 400;
tim.OnTimer := myOnTimer;
tim.Enabled := true;
// result of Dialog
if (ShowModal = ID_YES) then begin
Button1.Caption := 'Press YES';
end
else begin
Button1.Caption := 'Press NO';
end;
finally
Free;
end;
end;
An the OnTimer property like this:
procedure TForm1.MyOnTimer(Sender: TObject);
begin
inc(counter);
lbl.Caption := 'Counting: ' + IntToStr(counter);
if (counter >= 5) then begin
AMsgDialog.Close;
end;
end;
Define the variables and procedure:
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
AMsgDialog: TForm;
lbl:TLabel;
counter:integer;
procedure MyOnTimer(Sender: TObject);
end;
And test it.
The form close automatically when the timer final the CountDown. Similar this you can add other type of components.
Regards.
Try this:
function MessageBoxTimeOut(hWnd: HWND; lpText: PChar; lpCaption: PChar;
uType: UINT; wLanguageId: WORD; dwMilliseconds: DWORD): integer;
stdcall; external user32 name 'MessageBoxTimeoutA';
I've been using this for quite some time; it works a treat.
OK. You have 2 choices:
1 - You can create your own MessageDialog form. Then, you can use it and add a TTimer that will close the form when you want.
2 - You can keep using showmessage and create a thread that will use FindWindow (to find the messadialog window) and then close it.
I recommend you to use you own Form with a timer on it. Its cleaner and easier.
This works fine with windows 98 and newers...
I don't use the " MessageBoxTimeOut" because old windows 98, ME, doesn't have it...
this new function works like a "CHARM"..
//add this procedure
procedure DialogBoxAutoClose(const ACaption, APrompt: string; DuracaoEmSegundos: Integer);
var
Form: TForm;
Prompt: TLabel;
DialogUnits: TPoint;
ButtonTop, ButtonWidth, ButtonHeight: Integer;
nX, Lines: Integer;
function GetAveCharSize(Canvas: TCanvas): TPoint;
var
I: Integer;
Buffer: array[0..51] of Char;
begin
for I := 0 to 25 do Buffer[I] := Chr(I + Ord('A'));
for I := 0 to 25 do Buffer[I + 26] := Chr(I + Ord('a'));
GetTextExtentPoint(Canvas.Handle, Buffer, 52, TSize(Result));
Result.X := Result.X div 52;
end;
begin
Form := TForm.Create(Application);
Lines := 0;
For nX := 1 to Length(APrompt) do
if APrompt[nX]=#13 then Inc(Lines);
with Form do
try
Font.Name:='Arial'; //mcg
Font.Size:=10; //mcg
Font.Style:=[fsBold];
Canvas.Font := Font;
DialogUnits := GetAveCharSize(Canvas);
//BorderStyle := bsDialog;
BorderStyle := bsToolWindow;
FormStyle := fsStayOnTop;
BorderIcons := [];
Caption := ACaption;
ClientWidth := MulDiv(Screen.Width div 4, DialogUnits.X, 4);
ClientHeight := MulDiv(23 + (Lines*10), DialogUnits.Y, 8);
Position := poScreenCenter;
Prompt := TLabel.Create(Form);
with Prompt do
begin
Parent := Form;
AutoSize := True;
Left := MulDiv(8, DialogUnits.X, 4);
Top := MulDiv(8, DialogUnits.Y, 8);
Caption := APrompt;
end;
Form.Width:=Prompt.Width+Prompt.Left+50; //mcg fix
Show;
Application.ProcessMessages;
finally
Sleep(DuracaoEmSegundos*1000);
Form.Free;
end;
end;
////////////////////////////How Call It//////////////////
DialogBoxAutoClose('Alert'', "This message will be closed in 10 seconds',10);
/////////////////////////////////////////////////////////
MessageBox calls this function internally and pass 0xFFFFFFFF as timeout parameter, so the probability of it being removed is minimal (thanks to Maurizio for that)
I thought about using a separate thread, but it's probably going to get you into a lot of unnecessary code etc. Windows dialogs were simply not made for this thing.
You should do your own form. On the good side, you can have custom code/UI with a countdown like timed dialog boxes do.
No. ShowMessage and MessageDlg are both modal windows, which means that your application is basically suspended while they're displayed.
You can design your own replacement dialog that has a timer on it. In the FormShow event, enable the timer, and in the FormClose event disable it. In the OnTimer event, disable the timer and then close the form itself.
You can hook up the Screen.OnActiveFormChange event and use Screen.ActiveCustomForm if it is a interested form that you want to hook up the timer to close it
{code}
procedure abz.ActiveFormChange(Sender: TObject);
var
Timer: TTimer;
begin
if (Screen.ActiveCutomForm <> nil) and //valid form
(Screen.ActiveCutomForm.Tag = 0) and //not attached a timer yet
(Screen.ActiveCutomForm.ClassName = 'TMessageForm') //any interested form type check
then
begin
Timer := TTimer.Create(Screen.ActiveCutomForm); // let the form owned so it will be freed
Timer.Enabled := False;
Timer.Tag := Integer(Screen.ActiveCutomForm); // keep track to be used in timer event
.... setup any timer interval + event
Screen.ActiveCutomForm.Tag := Integer(Timer);
Timer.Enabled := True;
end;
end;
{code}
enjoy
Best way is to use a stayontop form and manage a counter to disappear using the alfpha blend property of the form, at the end of the count just close the form, but
the control will be passed to the active control needed before showing the form, this way, user will have a message which disappears automatically and wont prevent the usage of the next feature, very cool trick for me.
You can do this with WTSSendMessage.
You can find this in the JWA libraries, or call it yourself.