Delphi XE3 WM_HOTKEY How to tell when HotKey is Released ? - delphi

I am trying to program a "cough button" in a communications program that does not always have focus. I have the code working to mute and unmute the mic (MMDevApi) and it works perfect. I set up a global hot key and this works perfect to set the mute. Now the problem. How do I tell when the Hotkey is released ? I tried a timer as shown in the code but it has an odd behavior. I Hold down my hotkey and the mic mutes immediately then after the interval of the timer it unmutes for a what appears to be half the interval of the timer and then it mutes and stays muted. Without the timer it mutes and stays muted perfectly. I really don't want ( or think it would be any good ) to have to hit a second key to unmute the mic.
//here is my register hot key code !
CoughKeyWnd := AllocateHwnd(CoughKeyWndProc);
CoughKey := GlobalAddAtom('CoughKey');
if CoughKey <> 0 then
RegisterHotKey(CoughKeyWnd, CoughKey, MOD_CONTROL, VK_OEM_3);
//the procedure
procedure TForm1.CoughKeyWndProc(var Message: TMessage);
begin
if Message.Msg = WM_HOTKEY then
begin // to prevent recalling mute
if CoughOn = FALSE then
begin
CoughOn := True;
CoughOff.SetMute(1,#GUID_NULL);
end;
Timer1.Enabled := FALSE;
Timer1.Enabled := True;
end
else
begin
Message.Result := DefWindowProc(CoughKeyWnd, Message.Msg, Message.WParam, Message.LParam);
end;
//and finally the ontimer !
procedure TForm1.JvTimer1Timer(Sender: TObject);
begin
CoughOff.SetMute(0,#GUID_NULL);
Timer1.Enabled := False;
CoughOn := False;
end;

You'll see that kind of behavior if your timer expires before the second WM_HOTKEY is retrieved but does not expire retrieving consecutive messages. The time frame between the first and second message is greater than the time frame between consecutive messages. This is because keyboard delay is greater (~250ms typical) than keyboard repeat interval.
To make your approach work, increase your timer interval, something like twice the keyboard delay for instance. You can use SystemParametersInfo to get an approximation for keyboard delay. Or employ a minimum time frame for the microphone to stay in muted state, and only after then start watching for repeated hotkey messages to re-enable your timer. Still, this method will be somewhat unreliable, hotkey messages might be delayed for whatever reason. Better use GetKeyState in your timer handler to test if the keys are still down.
You can install a keyboard hook or register for raw input when the hotkey is hit if you don't want to use the timer.

Related

Delphi, TEdit text as action trigger

I am using a TEdit to allow the user to enter a number, e.g. 10.
I convert the TEdit.Text to an integer and a calculation procedure is called.
In this calc procedure, a check was built-in to make sure no numbers below 10 are processed.
Currently I use the OnChange event. Suppose the user wants to change '10' into e.g.'50'. But as soon as the '10' is deleted or the '5' (to be followed by the 0) is typed, I trigger my warning that the minimum number is 10. I.e. the program won't wait until I have fully typed the 5 and 0.
I tried OnEnter, OnClick, OnExit, but I seem not to overcome this problem. The only solution is to add a separate button that will trigger the calculation with the new number. It works, but can I do without the button?
Use a timer for a delayed check, e.g.:
procedure TForm1.Edit1Change(Sender: TObject);
begin
// reset the timer
Timer1.Enabled := false;
Timer1.Enabled := true;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := false;
// do your check here
end;
Setting the timer to 500 ms should be fine for most users.
And as David suggested in the comments to the question: Do not show an error dialog, use something less intrusive instead, e.g. an error message in a label near the edit or change the background color. And also: Do not prevent the focus to be moved away from that control and do not play a sound, that's also very annoying.
For our in house software we set the background of a control to yellow if there is an error and display the error message of the first such error in the status bar and also as a hint of the control. If you do that, you probably don't even need to have the delay.
Thanks, for your help. I tried the timer option, but could not get that to work. I now have this code, which works (almost - see below), but requires the used to always type a CR:
procedure Calculate;
begin
// this is my calculation procedure
ShowMessage('calculation running correctly');
end;
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
var
N : integer;
begin
if Key = #13 then
begin
N := StrtoInt(Edit1.Text);
if N<10 then ShowMessage('<10!') else
if N>100 then ShowMessage('>100!') else Calculate;
end;
end;
I used the ShowMessage() here only to see if the sample code worked. In the real program I have left that out, as you all suggested.
I also included the 'turn yellow on wrong entry' (thanks David). The only issue is that if the user runs this I get a beep from my computer. I can't see what went wrong. But what is causing the beep?

Delphi MediaPlayer notification on song stopped

I need to embed a trivial MP3 player in a Delphi7 application.
I will simply scan a directory and play all the files in a random order.
I found two possible solutions: one using the Delphi MediaPlayer, and one using the PlaySound Windows API.
None are working.
The problem seems to be in the missing "stop" notification.
Using PlaySound like this:
playsound(pchar(mp3[r].name), 0, SND_ASYNC or SND_FILENAME);
I could not find a way to (politely) ask Windows to inform me when a song stopped playing.
Using the Delphi MediaPlayer, internet is full of suggestions copy/pasted one from the other, like here:
http://www.swissdelphicenter.ch/en/showcode.php?id=689
http://delphi.cjcsoft.net/viewthread.php?tid=44448
procedure TForm1.FormCreate(Sender: TObject);
begin
MediaPlayer1.Notify := True;
MediaPlayer1.OnNotify := NotifyProc;
end;
procedure TForm1.NotifyProc(Sender: TObject);
begin
with Sender as TMediaPlayer do
begin
case Mode of
mpStopped: {do something here};
end;
//must set to true to enable next-time notification
Notify := True;
end;
end;
{
NOTE that the Notify property resets back to False when a
notify event is triggered, so inorder for you to recieve
further notify events, you have to set it back to True as in the code.
for the MODES available, see the helpfile for MediaPlayer.Mode;
}
My problem is that I do get a NotifyValue == nvSuccessfull when a song is over but ALSO when I start a song, so I cannot rely on it.
Furthermore, I never, ever receive a change in state of the "mode" property, that should become mpStopped according to all the examples I found.
There is a similar question here
How can I repeat a song?
but it does not work because, as said, I receive the nvSuccessfull twice, without a way to distinguish between the start and the stop.
Last but not least, this app should work from XP to Win10, that's why I am developing with Delphi7 on WinXP.
Thank you and sorry for the length of this post, but I really tried many solutions before asking for help.
To detect when to load a new file for playing, you can use the OnNotify event and the EndPos and Position properties of the TMediaPlayer (hereafter called MP)
First setup the MP and select a TimeFormat, for example
MediaPlayer1.Wait := False;
MediaPlayer1.Notify := True;
MediaPlayer1.TimeFormat := tfFrames;
MediaPlayer1.OnNotify := NotifyProc;
When you load a file for playing, set the EndPos property
MediaPlayer1.FileName := OpenDialog1.Files[NextMedia];
MediaPlayer1.Open;
MediaPlayer1.EndPos := MediaPlayer1.Length;
MediaPlayer1.Play;
And the OnNotify() procedure
procedure TForm1.NotifyProc(Sender: TObject);
var
mp: TMediaPlayer;
begin
mp:= Sender as TMediaPlayer;
if not (mp.NotifyValue = TMPNotifyValues.nvSuccessful) then Exit;
if mp.Position >= mp.EndPos then
begin
// Select next file to play
NextMedia := (NextMedia + 1) mod OpenDialog1.Files.Count;
mp.FileName := OpenDialog1.Files[NextMedia];
mp.Open;
mp.EndPos := mp.Length;
mp.Position := 0;
mp.Play;
// Set Notify, important
mp.Notify := True;
end;
end;
Finally a comment to your attempt to use the MP.Mode = mpStopped mode to change to a new song. The mode is changed when the buttons are operated, iow mpStopped when the user presses the Stop button. Changing the song and starting to play it would likely not be what the user expected.

How do I prevent the user from clicking a button too often?

In a button-click event, I am sending a command to a server. I don't want the server to be flooded with requests, so I want to prevent the user from clicking the button too often.
I've thought about counting how many times the button is clicked, like this:
button.tag := button.tag + 1;
if button.Tag = 5 then
exit;
But this will not help because I have to set the tag to 0 each time I need to detect the fast clicks, and this will also not help because it will detect the tag number every 5 clicks, whereas I just need to know if the button was clicked repeatedly.
How do I detect fast button clicks without using the button tag?
If you want to restrict how often to send commands from your client to the server, disable the button a short while after each click. This will give the user feedback that something is processing. I'm assuming there is no way the server can give feedback when it is ready.
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := false;
Timer1.Interval := 500; // Pick a suitable interval
Timer1.Enabled := true;
SendCommand(); // Make call to server
end;
procedure TForm1.Timer1Timer(Sender: TObject);
// Timer1 is disabled at startup
begin
Button1.Enabled := true;
Timer1.Enabled := false;
end;
Note: if you are trying to do some load balancing on the server, there must be a way for the clients to know how to restrict the calls. But this is all a backwards way of doing it, the server should be capable of handling this without any restrictions on the clients.
You've correctly recognized that merely counting the clicks won't be sufficient. You need to account for the time, too. So use a timer.
When the button is clicked, disable the button and enable a timer. When the timer fires, re-enable the button.
procedure TExampleForm.SendButtonClick(Sender: TObject);
begin
Assert(SendButton = Sender);
SendButton.Enabled := False;
SendButtonTimer.Enabled := True;
SendCommandToServer(...);
end;
procedure TExampleForm.SendButtonTimerTimer(Sender: TObject);
begin
Assert(SendButtonTimer = Sender);
SendButtonTimer.Enabled := False;
SendButton.Enabled := True;
end;
Set the timer's Interval property to the number of milliseconds that you want the button to remain disabled.
There's no need to count the clicks in this scenario because the implicit limit is one click per timer interval.
You could just put some kind of timestamp into the Tag, and check whether enough time has elapsed.
Better yet, derive your own class, (TDontPressMeTooFastButton ;-), and you can do whatever you want, ie.:
track both how much time elapsed since last click and how many "fast clicks" the user attempted
you could make the button contain the timer component, not leaking this implementation detail into the form
Just about anything, in a much cleaner way than using the .Tag or global/form variables ...

Set timer to pause

How can I pause TTimer in Delphi with keeping interval? So, for instance, I have TTimer that has 10 seconds interval and when I set timer to pause after first 7 seconds of working, it will save its state, and then when I resume timer it will fire after remaining 3 seconds.
Thanks a lot guys!
Timer.interval :=1000;
ICount:integer;
In create procedure set icount to 0
Procedure timerevent(sender:tobject);
Begin
If icount=10 then
Begin
// do proccess
Icount = 0;
End;
Inc(icount);
End;
Now you can stop the timer anywhere
You cannot do that with a TTimer which is a loose wrapper around the SetTimer API.
In order to do this you would need to keep track of when the timer started and when you paused it. Then you would know how much time was left. When you need to pause, set the timer Enabled property to False and set the interval to be the amount of time remaining. Don't forget that after the timer fires for the first time you need to reset its interval to the true interval.
As you can see from the above, a TTimer is not the best fit for your problem. But, having said that it would not be terribly difficult, and quite fun, to produce a TTimer variant that supported pausing the way you desire.
That's not how the Windows timer facility works, and that's not how the Delphi wrapper works, either.
When you disable the timer, keep note of how much time was remaining before it was due to fire again. Before you re-enable it, set the Interval property to that value. The next time the timer fires, reset Interval to the original value.
TTimer does not support what you are asking for. As others have already commented, you would have to stop the timer, reset its Interval to whatever time is remaining, start it, then stop and reset its Interval back to 10 seconds when the next OnTimer event is triggered.
A simpler solution is to just let the TTimer keep running normally, and have a separate flag that tells the OnTimer event handler whether it can do its work or not whenever it is triggered, eg:
var
Boolean: Paused = False;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if not Paused then
begin
// do proccess
end;
end;
procedure TForm1.PauseButtonClick(Sender: TObject);
begin
Paused := True;
end;
procedure TForm1.ResumeButtonClick(Sender: TObject);
begin
Paused := False;
end;

how to pause while some process going in loopss(delphi)

how can i pause and resume in application while working with loops
i can put some sleep(xxx) at begininsg of my loop to pause ,but i want pause when ever i want and resume when i ever i need
any ideas ?
thanks in advance
ok here is alittle more explanation
for i:=0 to 100 do
begin
if button1.clicked then pause //stop the operation and wait for resume button
if button2.clicked then resume //resume the operations
end;
edit2 :
ok guys i will tell an example well take any checker for suppose proxy checker ,i have a bunch of proxies loaded in my tlistviwe i am checking them all ,by using lop for i:=0 to listview.items.count do ......
i want to pause my checking operation when ever i want and resume when ever i need
am i clear or still i have to explain some ? :S
regards
You need a boolean flag that will indicate whether it's safe to continue looping. If something happens that makes it so you need to pause, it should set the variable to false. This means that, unless you're working with multiple threads, whatever sets this flag will have to be checked inside the loop somewhere. Then at the top (or the bottom) of your loop, check to see if this variable is true and pause otherwise.
Here's the basic idea, in the context of your explanation:
procedure TForm1.DoLoop;
begin
FCanContinue := true;
for i:=0 to 100 do
begin
//do whatever
Application.ProcessMessages; //allow the UI to respond to button clicks
if not FCanContinue then Pause;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
FCanContinue := false;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
FCanContinue := true;
Resume;
end;
This is a very simplistic implementation. What you really should do if you have a long-running task that you need to be able to pause and resume on command is put this in a separate thread and take a look at the TEvent class in SyncObjs.
OK, I don't understand what you are trying to do, but here is some pseudo code:
for i:=0 to 100 do
begin
if button1.clicked then
begin
while not button2.clicked do
sleep(50);
end;
end;
Check if a key is pressed?

Resources