How can I detect how long a key has been held down? - delphi

I'm looking for a way to detect how long a key has been held down in a Delphi project and warn user.
I'm working on a chat program and need to see if the person is holding down a letter, like the W key, to spam that chat box. I'll give sample what trying to do in Delphi 7:
//Looking up if key in use and held for lets say 20 seconds
if (GetAsyncKeyState(Byte(VkKeyScan('W'))) shl 20) <> 0 then
begin
ShowMessage('W Key Held down too long!');
end;
I'm not sure whether GetAsyncKeyState will give me that information, though. If it doesn't, what will?

Windows does not report the duration of how long a key has been held down, only that the same WM_KEY... message is being repeated for a key that is held down. You have to keep track of the duration yourself manually. When you detect a WM_KEYDOWN message with its wParam bit 30 set to 1, if you are not tracking that key yet, start tracking it and store the current system/tick with it, otherwise grab the current system/tick time, calculate the duration, and act accordingly. When you receive a WM_KEYUP message, stop tracking that key if you are tracking it.

I use the StopWatch class in the StopWatch unit to check for timing. Here's how you could try using it for what you're trying to do:
uses StopWatch;
//in the Form's private declaration:
StopWatch : TStopWatch;
//in the Form's onCreate:
StopWatch := TStopWatch.Create(nil);
//in the Form's onDestroy:
StopWatch.Free();
//in the form/box onKeyDown:
StopWatch.Start();
//in the form/box onChange:
if (StopWatch.ElapsedMiliseconds > 1000)
ShowMessage('W Key Held down too long!');
//in the form/box onKeyUp:
StopWatch.Stop();
StopWatch.Start();
StopWatch.Stop();
There are likely lots of other ways to accomplish what you're trying to do though. But for a quick try this should work.
The reason I stop and start the stopwatch again onKeyUp is to clear out ElapsedMiliseconds, in case the user changes the box with some method other than the keyboard, after having been alerted -- so they won't get alerted twice.

Related

JEDI Visual Component Library : JvAlarms component (a few questions)

I decided to try the JvAlarms component. So I did :
procedure TForm1.Button1Click(Sender_TObject);
begin
jvAlarms1.Add.Name :=Edit1.Text;
jvAlarms1.Add.Time := dxDateTimeWheelPicker1.DateTime;
label1.caption:=datetimetostr(dxDateTimeWheelPicker1.DateTime);
jvAlarms1.Active:=True;
end;
Now, the strange part is that when I set the alarm and run the application,immediately I get a popup window with my alarm message.
Is this by design ?
After I close this message the application will later trigger the alarm I have set on time.I am just wondering if this immediate popup window is by default or you can turn it off and how. If you can not, is it possible to modify it so you can at least say something to the user like 'you have set the alarm : alarm name, to fire : alarmtime'.
Second question regards the alarm message.
How do you get the alarm message name when the alarm fires ?
I tried :
ShowMessage('Alarm:'+ jvAlarms1.Name);
but it does not seem to work.
I can get it with :
ShowMessage('Alarm:'+jvAlarms1.Items[0].Name;
But I do not know the indexes of the alarms added!? So I can not use that.
Any way I can retrieve the list of alarms added by my code ?
Third question regard the alarms storage.
Do you load them from *.ini or can you use a database ?
I could not find examples of such usage anywhere (over here search results turn '0') so I would be grateful if
someone could point me in the right direction.
You added two alarms because you called Add twice. Call it once instead:
var
Item: TJvAlarmItem;
....
Item := jvAlarms1.Add;
Item.Name :=Edit1.Text;
Item.Time := dxDateTimeWheelPicker1.DateTime;
When the alarm fires the component's OnAlarm event receives a reference to the specific alarm that fired. You can read the name from that reference.
It is entirely up to you where you store the alarms in your application.

how to read mouse selected word from foreground window in delphi without using clipboard [duplicate]

This question already has an answer here:
I want to read Mouse selected text from foreground window without using clipboard
(1 answer)
Closed 8 years ago.
Can anyone give me code to read mouse selected or highlighted text from foreground window in delphi using any window api or anything else.
here is my code i am sending keystroke ctrl+c to read to clipboard and then read from it
handltForForeGroundWindow := GetForegroundWindow ;
PostMessage(handltForForeGroundWindow , wm_keydown, VK_CONTROL, 0);
PostMessage(handltForForeGroundWindow , wm_keydown, VkKeyScan('c'), 0);
Sleep(40);
PostMessage(handltForForeGroundWindow , wm_keyup, VkKeyScan('c'), 0);
PostMessage(handltForForeGroundWindow , wm_keyup, VK_CONTROL, 0);
StoreWord := Clipboard.AsText; // variable to read clipboard content
A critique of the code in the question:
SendInput is the correct API to use here. It places input messages into the queue of the active window. You can create an array of the four keyboard events, and put them into the queue in an atomic operation.
The call to Sleep is in the wrong place. Why wait before posting more input? That only increases the chance that somebody else will get in the way with some interfering input. Of course, that's exactly what SendInput is all about.
You will need to give the other application a chance to process the input. You'll have to wait after sending the input long enough for it to respond. How long will it need? Who can possibly say? This is the main weakness of your strategy. There's no reliable way for you to know how long you need to wait. Too short, and the app won't be done, too long and someone else (i.e. the user, who actually owns the clipboard) may well have overwritten the text you are looking for.
Destroying the user's clipboard is likely to make the users of your program hate it. I guess you know this already.
As to how to do this properly, without using the clipboard, well you really should be using the automation API for this task. That was the subject of your previous question: I want to read Mouse selected text from foreground window without using clipboard and I'm not sure quite what is different about this one.

abort long running process of a database table : ideas ?

I must process a very long database table an think of the most common way to abort this loop. The principal code sequence goes like this
procedure TForm.ProcessmyTable(Sender : TObject);
begin
.....
myTable.first;
repeat
ReadSingleRecordfromTable ( MyTable, aRecord) ;
ProcessMyRecord(aRecord) ;
MyTable.next;
until MYTable.EOF;
end;
unit .... ;
procedure ProcessMyRecord(aRecord : TMyDataRecord) ;
begin
// do not have user interface stuff here
// Application.Processmessages will not work here !!!
.... ( long running code sequence)
end;
Could do a timer and break the loop based on the timer with var as a flag support .... but is this really the most clever way of solving this issue?
If this code runs in the main thread, then you will need to service the message queue (i.e. call Application.ProcessMessages) if you want the user to interact with your program and abort. In which case I think you already know the solution. Call Application.ProcessMessages. If the user chooses to abort, set a boolean flag and check that flag regularly in the inner-most loop.
Of course, this is all rather messy. The fundamental problem is that you are performing long-running actions on the GUI thread. That's something you should not do. Move the database processing code onto a different thread. If the user chooses to abort, signal to the thread that it is to abort. For example a call to the Terminate method of the thread would be one way to do this.
When do you want to abort? If it takes too long of if the user says 'stop'?
In both cases change your
until MYTable.EOF;
to
until MYTable.EOF or Aborted;
then set your Aborted boolean either when the timer triggers or when the user presses a key (note that you then have to use Application.ProcessMessages in the loop for the program to be able to process the keypress). This will not abort in your processing routine but after each record. If that is not fast enough you will have to show your record processing routine.
If it's such a long process that the user might want to abort surely in a windows app there should be some interaction with the GUI, a count of records done or a progress bar (if not on every record then on a periodic basis) any call to update a label or progressbar will provide the opportunity to set an abort flag (so processmessages is not required). Plus if the user can see some progress they may be less likely to abort when bored but 95% complete.
IMHO :)

Directsound with streaming buffer - Lock does not wrap! Using ported DirectX headers for Delphi

Right-o, I'm working on implementing DirectSound in our Delphi voip app (the app allows for radios to be used by multiple users over a network connection)
Data comes in via UDP broadcasts.
As it is now, we go down on a raw data level and do the audio mixing from multiple sources ourselves and have a centralized component that is used to play all this back.
The app itself is a Delphi 5 app and I'm tasked with porting it to Delphi 2010. Once I got to this audio playback part, we concluded that it is best if we can get rid of this old code and replace it with directsound.
So, the idea is to have one SecondaryBuffer per radio (we have one 'panel' each per radio connection, based on a set of components that we create for every specific radio) and just let these add data to their respective SecondaryBuffers whenever they get data, only pausing to fill up half a second worth of audio data in the buffer if it runs out of data.
Now, I'm stuck at the part where I'm adding data to the buffers in my test-application where I'm just trying to get this to work properly before I start writing a component to utilize it the way we want.
I'm using the ported DirectX headers for Delphi (http://www.clootie.ru/delphi/download_dx92.html)
The point of these headers is to port over the regular DirectSound interface to Delphi, so hopefully non-Delphi programmers with DirectSound may know what the cause of my problem is as well.
My SecondaryBuffer (IDirectSoundBuffer) was created as follows:
var
BufferDesc: DSBUFFERDESC;
wfx: tWAVEFORMATEX;
wfx.wFormatTag := WAVE_FORMAT_PCM;
wfx.nChannels := 1;
wfx.nSamplesPerSec := 8000;
wfx.wBitsPerSample := 16;
wfx.nBlockAlign := 2; // Channels * (BitsPerSample/2)
wfx.nAvgBytesPerSec := 8000 * 2; // SamplesPerSec * BlockAlign
BufferDesc.dwSize := SizeOf(DSBUFFERDESC);
BufferDesc.dwFlags := (DSBCAPS_GLOBALFOCUS or DSBCAPS_GETCURRENTPOSITION2 or DSBCAPS_CTRLPOSITIONNOTIFY);
BufferDesc.dwBufferBytes := wfx.nAvgBytesPerSec * 4; //Which should land at 64000
BufferDesc.lpwfxFormat := #wfx;
case DSInterface.CreateSoundBuffer(BufferDesc, DSCurrentBuffer, nil) of
DS_OK: ;
DSERR_BADFORMAT: ShowMessage('DSERR_BADFORMAT');
DSERR_INVALIDPARAM: ShowMessage('DSERR_INVALIDPARAM');
end;
I left out the parts where I defined my PrimaryBuffer (it's set to play with the looping flag and was created exactly as MSDN says it should be) and the DSInterface, but it is as you might imagine an IDirectSoundInterface.
Now, every time I get an audio message (detected, decoded and converted to the appropriate audio format by other components we have made that have been confirmed to work for over seven years), I do the following:
DSCurrentBuffer.Lock(0, 512, #FirstPart, #FirstLength, #SecondPart, #SecondLength, DSBLOCK_FROMWRITECURSOR);
Move(AudioData, FirstPart^, FirstLength);
if SecondLength > 0 then
Move(AudioData[FirstLength], SecondPart^, SecondLength);
DSCurrentBuffer.GetStatus(Status);
DSCurrentBuffer.GetCurrentPosition(#PlayCursorPosition, #WriteCursorPosition);
if (FirstPart <> nil) or (SecondPart <> nil) then
begin
Memo1.Lines.Add('FirstLength = ' + IntToStr(FirstLength));
Memo1.Lines.Add('PlayCursorPosition = ' + IntToStr(PlayCursorPosition));
Memo1.Lines.Add('WriteCursorPosition = ' + IntToStr(WriteCursorPosition));
end;
DSCurrentBuffer.Unlock(#FirstPart, FirstLength, #SecondPart, SecondLength);
AudioData contains the data in my message. Messages always contain 512 bytes of audio data.
I added the Memo1.Lines.Add lines to be able to get some debug output (since using breakpoints doesn't quite work, as directsound keeps playing the contents of the primary buffer regardless)
Now, when I'm playing my DSCurrentBuffer using the looping flag (which according to hte MSDN docs is enough to make it a Streaming Buffer) and having this code work out as it wants, my output text in the Memo show that I am being allowed to write up until the end of the buffer... But it doesn't wrap.
SecondPart is always nil. It never ever wraps around to the beginning of the buffer, which means I get the same few seconds of audio data playing over and over.
And yes, I have scoured the net for components that can do this stuff for us and have concluded that the only reliable way is to do it ourselves like this.
And yes, the audio data that this app plays is choppy. I'm holding off on writing the half-a-second buffering code until I can get the write-to-buffer code to wrap as it should :/
I have been reading that people suggest keeping track of your own write cursor, but from what I read Lock and Unlock should help me bypass that need.
I'd also rather avoid having to have two buffers that I alternate between back and forth (or a split-buffer, which would essentially be the same thing, only a bit more complex in writing)
Any help greatly appreciated!
So I figured out the problem ^^;
Pretty simple too.
DSCurrentBuffer.Unlock(#FirstPart, FirstLength, #SecondPart, SecondLength);
I thought I was supposed to just pass along the same pointers to Pointers that Lock() had required.
Changing it to
DSCurrentBuffer.Unlock(FirstPart, FirstLength, SecondPart, SecondLength);
Solved the issue and the buffer now wraps correctly.
Sorry for wasting your time, but thanks anyway ^^;
A few things that might cause this:
Memo1.Lines.Add should only be called from the main thread (the thread that initialized the VCL GUI). Use TThread.Synchronize for this (easier), or an intermediate buffer that is thread safe and preferably lock-free (faster; thanks mghie for this hint).
Unlock should be in a finally section like below, because if an exception gets raised, you never unlock the buffer, see code sample below.
You should log any exceptions taking place.
Sample code:
DSCurrentBuffer.Lock(0, 512, #FirstPart, #FirstLength, #SecondPart, #SecondLength, DSBLOCK_FROMWRITECURSOR);
try
//...
finally
DSCurrentBuffer.Unlock(#FirstPart, FirstLength, #SecondPart, SecondLength);
end;
--jeroen

How can I fix "Cannot open clipboard: Access Denied" errors?

I am using the following code to copy text to the clipboard:
Clipboard.Open;
try
Clipboard.AsText := GenerateClipboardText;
finally
Clipboard.Close;
end;
Seemingly at random I get "Cannot open clipboard: Access Denied" errors. I'm guessing that these errors are caused by other application locking the clipboard, but I never seem to be doing anything with other applications that should cause the locks.
Strangely my users seem to be reporting more of the errors with Vista and Windows 7 than with XP.
Is there a way to check if the clipboard is locked before trying to access it?
This is not a Delphi problem. Because the clipboard can be locked any moment, even if you check, if the clipboard is currently not locked, it might become locked directly after the check.
You have two possibilities here:
Don't use the Delphi clipboard class. Instead use raw API functions, where you have a little more fine-grained control over possible error situations.
Expect your code to fail by adding an exception handler. Then add some retry code, i.e. retry to set the text three times, perhaps with exponential backoff, before throwing your own error.
I'd recommend the second solution, because it'd be the more Delphi-like approach and in the end will result in cleaner code.
var
Success : boolean;
RetryCount : integer;
begin
RetryCount := 0;
Success := false;
while not Success do
try
//
// Set the clipboard here
//
Success := True;
except
on E: EClipboardException do
begin
Inc(RetryCount);
if RetryCount < 3 then
Sleep(RetryCount * 100)
else
raise Exception.Create('Cannot set clipboard after three attempts');
end else
raise; // if not a clipboard problem then re-raise
end;
end;
Strangely my users seem to be
reporting more of the errors with
Vista and Windows 7 than with XP
This may have to do with how Vista/Win7 deal with clipboard viewer notification. While they still support the XP "clipboard viewer chain", which sends one notification message that must be re-sent to each listener in turn (and if one app fails to do this, the other apps aren't notified). Starting with Vista, apps are notified directly. And there's nothing to keep them from trying to access the clipboard all at once.
Analogy: I have 3 children. I have a cake. With XP rules, I tell the oldest child to have some cake, then tell the next oldest child to have a slice. She gets her slice, tells her brother, he gets his, and tells his brother, who gets his, and everything proceeds in an orderly fashion.
Problem: The middle child takes the cake to his room, doesn't tell the youngest, and the youngest misses out.
With Vista/Windows7, that system still exists. But newer apps can request to be notified immediately, by me, as soon as the cake arrives in the kitchen. I shout "cake is ready!" and they all show up at the same time and try to grab some. But there's only one serving knife, so they have to keep reaching for the knife, failing to get it, and waiting for the next opportunity.
Try to check GetClipboardOwner, if it's not null and not your Application.Handle, you cannot Open to modify it's content.
And even it seems good to go, it might not be anymore when you actually do it.
So add a try except in a loop until you get it or give up nicely (notifying the user for instance).
There is no way to check for something and then depending on the result do something else with the expectation that it could not fail, because unless the check and the action are one atomic operation there is always the possibility that another process or thread does the same in parallel.
This holds whether you try to open the clipboard, open a file, create or delete a directory - you should simply try to do it, maybe several times in a loop, and gracefully handle errors.
First of all please notice that this probably isn't a problem in your application. Other applications locked the clipboard or messed up the notification chain and now your application fails to access it. When I do have problems like this I restart the computer and they magically go away... well... at least until I run again the application that creates the problem.
This code (not checked in Delphi) may help you. It won't fix the problem is the notification chain is broken (nothing except a PC restart will ever fix it) but it will fix the problem if an application is locking the clipboard for a while. Increase the MaxRetries if that pesky application keeps the clipboard locked for A REALLY LONG time (seconds):
procedure Str2Clipboard(CONST Str: string; iDelayMs: integer);
CONST
MaxRetries= 5;
VAR RetryCount: Integer;
begin
RetryCount:= 0;
for RetryCount:= 1 to MaxRetries DO
TRY
inc(RetryCount);
Clipboard.AsText:= Str;
Break;
EXCEPT
on Exception DO
if RetryCount = MaxRetries
then RAISE Exception.Create('Cannot set clipboard')
else Sleep(iDelayMs)
END;
end;
Also, it may be a good idea to drop the 'raise' and convert it to a function and use it like this:
if not Str2Clipboard
then Log.AddMsg('Dear user, other applications are blocking the clipboard. We have tried. We really did. But it didn''t work. Try again in a few seconds.');
I guess you are running your app on Win 8 or higher.
Just right-click on your App .exe file, go to Compatibility tab and change compatibility mode on Windows XP or lower versions. It'll work, guaranteed!

Resources