I'm trying to respond to some windows and application messages, but I get them multiple times.
For example, I write the following code to show a message box when the date of system is changed using WM_TIMECHANGE. WMTimeChange is executed more than once, and I see multiples (most times two or three) messageboxes one behind another. Maybe, I am missing something?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
protected
procedure WMTimeChange(var Msg: TMessage) ; message WM_TIMECHANGE;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.WMTimeChange(var Msg: TMessage);
begin
showmessage('Date/Time has changed!');
end;
end.
Testing in Windows XP.
EDIT: Just to clarify, my intention is understand WHY that happen and not how to get around the multiple calls. Anyway, if an answer to that is not possible, I will probably accept one answer to the later.
EDIT2: Removed Delphi Tag as it seems not a Delphi issue.
Your code is correct. It sounds like that Windows is sending the WM_TIMECHANGE message several times.
So you can just add a small time-hysteresis comparison to let your message be triggered only once per a 1% of day, i.e. more or less 15 minutes:
type
TForm1 = class(TForm)
protected
FWMTimeChangeTimeStamp: TDateTime;
procedure WMTimeChange(var Msg: TMessage) ; message WM_TIMECHANGE;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.WMTimeChange(var Msg: TMessage);
begin
if Now-FWMTimeChangeTimeStamp>0.01 then
begin
showmessage('Date/Time has changed!');
FWMTimeChangeTimeStamp := Now;
end;
end;
This is kind of what I've used in my case to be resilient to this behavior. But as said in comments, will only work if the user take time to answer the application. So, Arnaud Bounchez is a better approach to general use. Only don't forget to initialize the FWMTimeChangeStamp to something that is different from current computer clock.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
protected
procedure WMTimeChange(var Msg: TMessage) ; message WM_TIMECHANGE;
private
isTimeChangeEventShowing: Boolean;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
isTimeChangeEventShowing := false
end;
procedure TForm1.WMTimeChange(var Msg: TMessage);
begin
if not isTimeChangeEventShowing then
begin
isTimeChangeEventShowing := true;
showmessage('Date/Time has changed!');
isTimeChangeEventShowing := false;
end;
end;
end.
Related
I have a simple Application that reads data from an .ini file into a record, and then does a ShellExecute. However, when I run it in debug mode,it tells me that the True free heap isn't what it should be:
"True heap size: 688128, True free heap: 688016, Should be: 688128"
This only happens if I actually run the ShellExecute (however even if I don't run the ShellExecute, I get a True free heap of 688016, but no complaint that it should be 688128), so I wonder if I either need to free the PChar() conversion (which shouldn't be the case from all that I've read), or the handle returned by ShellExecute (though CloseHandle(ShellHandle); doesn't change the message), or if it's intended behavior?
The IniSettings.SetupCommand in question is an .msi file that triggers a UAC prompt, but I really just want to make this a Fire & Forget thing since my Lazarus app doesn't care what it launches.
For reference, here's my whole Unit. I'm using Lazarus 1.6.2, FPC 3.0.0, i386-win32, and I'm targeting Windows only with this. My Debug Settings are at the very bottom, after the code.
unit MainUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls,
Graphics, Dialogs, StdCtrls, IniFiles, Windows;
type
{ IniSettings }
TAutorunIniSettings = record
AutorunTitle: AnsiString;
SetupCommand: AnsiString;
end;
{ TMainForm }
TMainForm = class(TForm)
btnSetup: TButton;
lblTitle: TLabel;
procedure btnSetupClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ private declarations }
IniSettings: TAutorunIniSettings;
public
{ public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.lfm}
{ TMainForm }
procedure TMainForm.FormCreate(Sender: TObject);
var
AutorunIni: TIniFile;
begin
try
AutorunIni := TIniFile.Create('Autorun.ini');
IniSettings.AutorunTitle := AutorunIni.ReadString('Autorun', 'Title', 'Autorun');
IniSettings.SetupCommand := AutorunIni.ReadString('Autorun', 'Setup', '');
self.Caption := IniSettings.AutorunTitle;
finally
if(AutorunIni <> nil) then AutorunIni.Free;
end;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
end;
procedure TMainForm.btnSetupClick(Sender: TObject);
begin
ShellExecute(0, 'open', PChar(IniSettings.SetupCommand), nil, nil, SW_SHOWNORMAL);
end;
end.
In my extended TComboBox class, I overrided ComboWndProc() procedure handler, but I was not able to detect neither CN_VSCROLL nor WM_VSCROLL messages from the scroll bar of the List (FListHandle).
I basically want to implement an infinite scroll using winapi.
I imagine that, to do what I want, I basically would need to know the track bar position of the scroll so when the track bar touch the line down button I would add more data to strings.
The idea is simple and maybe naive, but I could start from there and see what problems I would have.
Is it possible to do such a thing?
Is there a way to track scroll bar messages from TComboBox?
More importantly:
If yes, How?
If no, Why?
You can use WM_VSCROLL, to do so you have to subclass the listbox control of the combobox. CN_VSCROLL will not work because the listbox part of the combobox is not a VCL control.
Below example is essentially from this answer of Kobik, included here for the sake of completeness.
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FComboListWnd: HWND;
FComboListWndProc, FSaveComboListWndProc: Pointer;
procedure ComboListWndProc(var Message: TMessage);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
Info: TComboBoxInfo;
begin
ZeroMemory(#Info, SizeOf(Info));
Info.cbSize := SizeOf(Info);
GetComboBoxInfo(ComboBox1.Handle, Info);
FComboListWnd := Info.hwndList;
FComboListWndProc := classes.MakeObjectInstance(ComboListWndProc);
FSaveComboListWndProc := Pointer(GetWindowLong(FComboListWnd, GWL_WNDPROC));
SetWindowLong(FComboListWnd, GWL_WNDPROC, Longint(FComboListWndProc));
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SetWindowLong(FComboListWnd, GWL_WNDPROC, Longint(FSaveComboListWndProc));
classes.FreeObjectInstance(FComboListWndProc);
end;
procedure TForm1.ComboListWndProc(var Message: TMessage);
begin
case Message.Msg of
WM_VSCROLL: OutputDebugString('scrolling');
end;
Message.Result := CallWindowProc(FSaveComboListWndProc,
FComboListWnd, Message.Msg, Message.WParam, Message.LParam);
end;
I used to use Delphi 7 on my PC, but now that I bought a MacBook (Mid 2012 - OSX Mountain Lion), I wanted to continue programming in Pascal and have a similar Interface. Lazarus seems to deliver most of the things I wanted, but it seems to run into a lot of errors, when compiling even the simplest applications! To test it, I made a simple "Russian Roulette" application, just for fun, but the program just freezes, when launched or even when compiled inside Lazarus.
When launching it from command line, It shows me the following error:
TCarbonButton.SetFocus Error: SetKeyboardFocus failed with result -30585
I don't think my coding is the problem, but I guess I should include it:
unit RussianRouletteUnit;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
Buttons, ExtCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
Kugeln: TLabeledEdit;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
Number: Integer;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
if Random(StrToInt(Kugeln.Text))+1 = 1 then
begin
Button1.SetFocus;
Memo1.Color := clred;
Memo1.Text := 'BOOM';
Memo1.Lines.Add('HEADSHOT');
end;
end;
initialization
randomize;
end.
I hope you guys can help me out, any help is apprechiated :D
I'm having trouble getting to grips with DLLs in Delphi 7. I have two problems:
1) The procedure takes an integer parameter - but the dll receives a different value to the one I pass.
2) The application that called the dll crashes with an access violation after the function completes.
Here's my dll code:
library apmDLL;
uses
Classes, Messages, Windows, Dialogs, sysutils ;
const
WM_MY_MESSAGE = WM_USER + 1;
procedure sendtoACRPM (functionKey : integer); stdcall;
begin
showmessage('You sent - '+inttostr(functionKey));
showmessage('Finished Now');
end;
exports sendtoACRPM;
end.
So when I call this with the code below I get:
'Sending - 1'
'You Sent - 1636532'
'Finished Now'
Then the calling application crashes with an access violation.
The calling application looks like this:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, shlobj, shellapi;
const
WM_MY_MESSAGE = WM_USER + 1;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button2: TButton;
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure sendtoACRPM (functionKey : integer) ; external 'apmDLL.dll';
implementation
{$R *.dfm}
procedure TForm1.Button2Click(Sender: TObject);
var
myInt: integer;
begin
myInt := strtoint(edit1.text);
showmessage('Sending - ' + inttostr(myInt));
sendtoACRPM(myInt);
end;
end.
Any ideas what I'm doing wrong here?
You need stdcall both in the DLL and in the calling code declaration. You only have it in the DLL.
Calling conventions need to match on both sides. :-)
procedure sendtoACRPM (functionKey : integer); stdcall; external 'apmDLL.dll';
You should use the standard Windows MessageBox instead of ShowMessage, so that the DLL can be used from non-Delphi applications as well.
I want to minimize a Delphi application to the systray instead of the task bar.
The necessary steps seem to be the following:
Create icon which should then be displayed in the systray.
When the user clicks the [-] to minimize the application, do the following:
Hide the form.
Add the icon (step #1) to the systray.
Hide/delete the application's entry in the task bar.
When the user double-clicks the application's icon in the systray, do the following:
Show the form.
Un-minimize the application again and bring it to the front.
If "WindowState" is "WS_Minimized" set to "WS_Normal".
Hide/delete the application's icon in the systray.
When the user terminates the application, do the following:
Hide/delete the application's icon in the systray.
That's it. Right?
How could one implement this in Delphi?
I've found the following code but I don't know why it works. It doesn't follow my steps described above ...
unit uMinimizeToTray;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ShellApi;
const WM_NOTIFYICON = WM_USER+333;
type
TMinimizeToTray = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure CMClickIcon(var msg: TMessage); message WM_NOTIFYICON;
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
MinimizeToTray: TMinimizeToTray;
implementation
{$R *.dfm}
procedure TMinimizeToTray.CMClickIcon(var msg: TMessage);
begin
if msg.lparam = WM_LBUTTONDBLCLK then Show;
end;
procedure TMinimizeToTray.FormCreate(Sender: TObject);
VAR tnid: TNotifyIconData;
HMainIcon: HICON;
begin
HMainIcon := LoadIcon(MainInstance, 'MAINICON');
Shell_NotifyIcon(NIM_DELETE, #tnid);
tnid.cbSize := sizeof(TNotifyIconData);
tnid.Wnd := handle;
tnid.uID := 123;
tnid.uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP;
tnid.uCallbackMessage := WM_NOTIFYICON;
tnid.hIcon := HMainIcon;
tnid.szTip := 'Tooltip';
Shell_NotifyIcon(NIM_ADD, #tnid);
end;
procedure TMinimizeToTray.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caNone;
Hide;
end;
end.
If it still works, it's probably easiest to use JVCL's TJvTrayIcon to handle it automatically.
I would recommend using CoolTrayIcon. The author has already worked out all the issues involved with tray icons. Its free with source and examples and very debugged.
http://subsimple.com/delphi.asp
Instead of Application.BringToFront; use SetforegroundWindow(Application.Handle);
In the following text I'll be referring to the step numbers mentioned in the question:
The following solution is without any additional components. It's very easy to implement.
Step #1:
Just use the application's main icon (see following code).
Step #2:
procedure TForm1.ApplicationEvents1Minimize(Sender: TObject);
begin
Shell_NotifyIcon(NIM_ADD, #TrayIconData);
Form1.Hide;
end;
Step #3:
procedure TForm1.TrayMessage(var Msg: TMessage);
begin
if Msg.lParam = WM_LBUTTONDOWN then begin
Form1.Show;
Form1.WindowState := wsNormal;
Application.BringToFront;
Shell_NotifyIcon(NIM_DELETE, #TrayIconData);
end;
end;
Step #4:
procedure TForm1.FormDestroy(Sender: TObject);
begin
Shell_NotifyIcon(NIM_DELETE, #TrayIconData);
end;
Necessary code in interface part:
uses
[...], ShellApi;
const
WM_ICONTRAY = WM_USER + 1;
type
TForm1 = class(TForm)
[...]
procedure TrayMessage(var Msg: TMessage); message WM_ICONTRAY;
end;
The only problem: The application can be minimized to the systray only once. The next time you want to minimize it, nothing will happen. Why?
Source: delphi.about.com