Recent

Author Topic: doubled keypresses register when there is a TMemo on a form  (Read 4348 times)

robert rozee

  • Full Member
  • ***
  • Posts: 204
doubled keypresses register when there is a TMemo on a form
« on: January 03, 2025, 02:26:12 pm »
i've encountered an odd behavior with a Lazarus/FPC application that may be worthy of further investigation. and to answer the obvious question - yes - i have found a (sortof) workaround.

the application is rather large, something that i've been developing and maintaining for a number of years. a few days ago i made a change to the main form's OnKeyPress handler that involved, when a certain set of conditions were met, changing the value of the variable Key (of type char) that is passed in. changing the value of Key was done for convenience, as there were a few dozen following lines of code that i didn't wish to edit; i could have equally well just used a locally defined variable called NewKey instead.

to my surprise, i now found that on those occasions when the value of Key was changed, the OnKeyPress handler would be called TWICE in quick succession. this made absolutely no sense, and i could not for the life of me figure out the cause!

a bit of research online brought up a smattering of postings on the web (going back some number of years) about similar things happening, but with no clear/definitive explanation or solution. however, several of the postings mentioned a possible connection with the form also having a TMemo on it. my application does have a TMemo on the main form, but normally this component sits hidden (Memo1.Visible=false, Memo1.Enabled=false). only occasionally is the component enabled, displaying some content that the user can select and copy to clipboard before being hidden again.

after much experimentation, i found that the double-keypress events (there were also matching double-keydown events) could seemingly be prevented by adding the following few lines of code into my application's startup code:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.ApplicationPropertiesActivate(Sender: TObject);
  2. const startup:boolean=true;
  3. begin
  4.   if startup then                                              // startup things that can/should not be performed until the main form is 'live'
  5.   begin
  6.     startup:=false;
  7.  
  8.     Memo1.Visible:=true;                                       ///////////////////////////////////////////////////////////////
  9.     Memo1.Enabled:=true;                                       // WITHOUT these lines (in particular the SetFocus) we get   //
  10.     Memo1.SetFocus;                                            // doubling up of Form1 KeyPress events. Have no idea WHY it //
  11.     Memo1.Visible:=false;                                      // happens, but the SetFocus prevents it!                    //
  12.     Memo1.Enabled:=false
  13.   end                                      ///////////////////////////////////////////////////////////////
  14. end

note that, by design, on the main form of my application there are NO components that would normally have focus. also, while the above seems to work, i am not convinced that i have got to the bottom of the problem. the feeling i have is that it is a focus-related issue, but then when i tinker more with the code i'll sometimes find a fix that works, then when i rearrange the code it doesn't... then i'll go back to the original fix and it too doesn't work! needless to say, in my OnKeyPress handler i now do not change the value of Key - that seems to be the one way of (100% ???) avoiding the problem.

has anyone else encountered this sort of behavior? i'd be interested in hearing any theories that others may have. i must admit, few GUI applications would normally have no focused component, but then it is not unheard of. my OnKeyDown, OnKeyPress and OnKeyUp events are all triggered from the main form, while my mouse events are all triggered from a TPanel that covers the entire area of the main form (the panel then contains a TImage that the user interacts with).


cheers,
rob   :-)

Thaddy

  • Hero Member
  • *****
  • Posts: 16411
  • Censorship about opinions does not belong here.
Re: doubled keypresses register when there is a TMemo on a form
« Reply #1 on: January 03, 2025, 03:09:39 pm »
Of course it makes sense. changing key triggers onkeypress again, since it is a change.
There is nothing wrong with being blunt. At a minimum it is also honest.

robert rozee

  • Full Member
  • ***
  • Posts: 204
Re: doubled keypresses register when there is a TMemo on a form
« Reply #2 on: January 03, 2025, 05:22:49 pm »
Of course it makes sense. changing key triggers onkeypress again, since it is a change.

except:
  • the second time round it passes in the original value for Key, not the updated one,
  • if the TMemo is removed from the form the the OnKeyPress handler is not called a second time,
  • if i start out with a blank form and drop a TMemo onto it, i can not duplicate the double-up behavior.
if it were intended behavior, then it would surely be consistent?

addendum: further experimenting, and all i find is weird behavior! i think it is fair to say that if you do NOT have something like a TMemo on your form in a state that can 'receive' keypresses, then you should NOT go altering the value of Key passed into the OnKeyPress handler. also, using the feature to swap upper and lowercase characters over is not workable.


cheers,
rob   :-)
« Last Edit: January 04, 2025, 03:45:04 pm by robert rozee »

robert rozee

  • Full Member
  • ***
  • Posts: 204
Re: doubled keypresses register when there is a TMemo on a form
« Reply #3 on: January 05, 2025, 01:58:53 pm »
Thaddy, i think i have managed to put together a simple demonstration of at least part of what i am seeing.

i've created two identical applications, with but one key difference:
  • the first application is just a blank form, with an OnKeyPress event handler.
  • the second application has added to it a TMemo object
attached are .zip archives containing the source code for two applications.

the OnKeyPress event handlers are identical, and as follows:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormKeyPress(Sender: TObject; var Key: char);
  2. const mark:Int64=0;
  3. begin
  4.   writeln(GetTickCount64-Mark:16, ord(Key):8, #9, '<', Key, '>');
  5.   Key:='@';
  6.   mark:=GetTickCount64
  7. end;

also attached is a screenshot showing the two applications running, after pressing the 7-key sequence "abcdefg" while the main form has focus.

the first application, that excludes the TMemo, produces the output:
       856988796      97   <a>
             232      98   <b>
             248      99   <c>
             832     100   <d>
             936     101   <e>
             288     102   <f>
             192     103   <g>


the second application, that includes the TMemo, produces the output:
       856995892      97   <a>
               0      97   <a>
             940      98   <b>
               0      98   <b>
            1656      99   <c>
               0      99   <c>
             392     100   <d>
               0     100   <d>
             271     101   <e>
               1     101   <e>
             284     102   <f>
               0     102   <f>
             252     103   <g>
               0     103   <g>

  • why is there a difference between the two sets of output?
  • why is the value of Key that is passed in to the extra OnKeyPress events never '@' for the application that includes the TMemo?
  • why is there no doubling-up of OnKeyPress events for the application that excludes the TMemo?

what i have not been able to create, so far, is a simple demonstration of a form containing a TMemo and that has no doubling-up of OnKeyPress events. i do have a complex demonstration - in excess of 5000 lines of code.


cheers,
rob   :-)
« Last Edit: January 05, 2025, 02:06:10 pm by robert rozee »

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Re: doubled keypresses register when there is a TMemo on a form
« Reply #4 on: January 05, 2025, 03:21:00 pm »
@robert rozee: which versions of FPC and Lazarus and Linux are you using?

Bart

  • Hero Member
  • *****
  • Posts: 5497
    • Bart en Mariska's Webstek
Re: doubled keypresses register when there is a TMemo on a form
« Reply #5 on: January 05, 2025, 03:39:53 pm »
I assume you have Form1.KeyPreview := True, and the TMemo in the second application has focus when you type.
In that case the Key is first seen by Form1, and since you do not set it to #0, it is also seen (after that) by the TMemo.

Question is: if you type "abc" in the memo, do you see double characters in the memo as well ("aabbcc").

That is a know issue, for whic a workaround exist, see https://wiki.lazarus.freepascal.org/Lazarus_FAQ#Typing_in_edit_fields_generate_duplicate_letters.

IIRC then there is also an issue with changing keys in OnKeyPressUTF8 in GTK2.

On Windows:

Given this OnKeyPress event handler, assigned to both Form1 and Memo1 (in ObjectInspector):
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormKeyPress(Sender: TObject; var Key: char);
  2. begin
  3.   writeln(TComponent(Sender).Name,': Key = ',Key,', Changing to ',Succ(Key));
  4.   Inc(Key);
  5. end;

If I focus the Memo and then type "abc" the output is:
Code: [Select]
Form1: Key = a, Changing to b
Memo1: Key = b, Changing to c
Form1: Key = b, Changing to c
Memo1: Key = c, Changing to d
Form1: Key = c, Changing to d
Memo1: Key = d, Changing to e

And the text in the Memo is: "cde" (all without the quotes of course).

And this is how it should be.

If the Memo dos not have focus, the output is:
Code: [Select]
Form1: Key = a, Changing to b
Form1: Key = b, Changing to c
Form1: Key = c, Changing to d

As expected.

Bart

robert rozee

  • Full Member
  • ***
  • Posts: 204
Re: doubled keypresses register when there is a TMemo on a form
« Reply #6 on: January 06, 2025, 05:14:35 am »
@robert rozee: which versions of FPC and Lazarus and Linux are you using?

FPC version = 3.2.2     
LCL version = 3.6.0 

Linux Mint XFCE 22.0 x86_64

NOT windows!


cheers,
rob   :-)

Bart

  • Hero Member
  • *****
  • Posts: 5497
    • Bart en Mariska's Webstek
Re: doubled keypresses register when there is a TMemo on a form
« Reply #7 on: January 06, 2025, 11:57:28 am »
Tested your attached project with memo (from reply #3) on Windows.
Type "abc" (without quotes).
This gives:
Code: [Select]
       653612625      97        <a>
             171      98        <b>
             235      99        <c>

The result is the same wether or not Memo1 has focus.
If it has focus, as expected "@@@" appears in the memo.

From what I can gather from your screenshots you see double the amounts of the form's OnKeyPress, but at least the characters are not duplicated in the memo.

Now, what happens if you don't alter Key in TForm1.FormKeyPress?
In the from with the TMemo: do you still have the double amount of OnKeyPress happen?

Bart

robert rozee

  • Full Member
  • ***
  • Posts: 204
Re: doubled keypresses register when there is a TMemo on a form
« Reply #8 on: January 06, 2025, 12:37:40 pm »
Now, what happens if you don't alter Key in TForm1.FormKeyPress?
In the from with the TMemo: do you still have the double amount of OnKeyPress happen?

well... if i don't alter Key then everything works just fine!

i discovered the double-up behavior only because of a quick hack i made to my code (a rather large application), to test a fix for an unrelated issue with upper and lowercase letters being swapped over when capslock was toggled while another application had focus. turns out, at least for Linux+GTK2, that the capslock state is not read when an FPC application starts up, but instead just assumed to be initially off. and if you click away from an FPC application, press capslock, then click back to the FPC application, your FPC application is more-or-less none the wiser - except that the case of Key does not match the shift key and capslock states.

once i worked out what was going on with OnKeyPress, i just used a temporary variable, NewKey, to hold the case-corrected key value and altered half a dozen lines of my code to use the value from NewKey. as far as i've been able to determine, it appears that it may not even be possible for a Linux/GTK2 application to read the capslock state.

so my only interest is in understanding the behavior, and if it is a bug seeing it highlighted, or fixed, so it doesn't trip up others.


cheers,
rob   :-)

Bart

  • Hero Member
  • *****
  • Posts: 5497
    • Bart en Mariska's Webstek
Re: doubled keypresses register when there is a TMemo on a form
« Reply #9 on: January 06, 2025, 07:16:35 pm »
You could post it as an issue in the bugtracker.
Attache the sample project where it happens and describe that it disappears if the  memo is not there.

I think I remeber a simialr bugreport, but I cand find it in the bugtracker.
Worst case if you repoert it: it's a duplicate. Don't worry about that.
Feel free to mention me advising you to report it.

Bart

Bart

  • Hero Member
  • *****
  • Posts: 5497
    • Bart en Mariska's Webstek
Re: doubled keypresses register when there is a TMemo on a form
« Reply #10 on: January 06, 2025, 07:23:38 pm »
i discovered the double-up behavior only because of a quick hack i made to my code (a rather large application), to test a fix for an unrelated issue with upper and lowercase letters being swapped over when capslock was toggled while another application had focus.

Know issue: https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/33828

Bart

robert rozee

  • Full Member
  • ***
  • Posts: 204
Re: doubled keypresses register when there is a TMemo on a form
« Reply #11 on: January 07, 2025, 01:08:17 pm »
i discovered the double-up behavior only because of a quick hack i made to my code (a rather large application), to test a fix for an unrelated issue with upper and lowercase letters being swapped over when capslock was toggled while another application had focus.

Know issue: https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/33828

below is my solution to the capslock state problem, which in practice is more 'natural' than trying to correct the behavior of GetKeyState(VK_CAPITAL). my application displays the state of capslock+shift through the colour of a block cursor.


cheers,
rob   :-)

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormKeyPress(Sender: TObject; var Key: char);
  2. const Iflag:integer=-1;
  3. var KS1, KS2, Lflag, Uflag, invert:boolean;
  4.                             NewKey:char;
  5. begin
  6.   KS1:=(GetKeyState(VK_SHIFT)<0);              // true if SHIFT is down      
  7.   KS2:=odd(GetKeyState(VK_CAPITAL));           // true if CAPSLOCK is ON      
  8.   Lflag:=Key in ['a'..'z'];                                                  
  9.   Uflag:=Key in ['A'..'Z'];                                                  
  10.                                                                              
  11.   if Uflag then invert:=not(KS1 xor KS2) else          // check if case wrong    
  12.   if Lflag then invert:=KS1 xor KS2 else               // check if case wrong    
  13.                 invert:=false;                         // non-letter key          
  14.                                                                              
  15.   if not invert then NewKey:=Key else                                  
  16.   if Uflag then NewKey:=chr(ord(Key)+32) else          // UC -> LC                
  17.   if Lflag then NewKey:=chr(ord(Key)-32) else          // LC -> UC                
  18.                 NewKey:=Key;                           // should never land here          
  19.  
  20. //NewKey:=Key;                 // bypass test - also likely needed for win32
  21.  
  22.   if (Lflag or Uflag) and (ord(invert)<>Iflag) then            // we can only flip on receiving a letter
  23.   begin                                                        // initially Iflag = -1 (undefined)
  24.     Iflag:=ord(invert);                                        // thereafter either 0 (false) or 1 (true)
  25.     if invert then writeln('caps lock: inverted')
  26.               else writeln('caps lock: normal')                
  27.   end;                                                         // these 6 lines are just for diagnostics
  28.  
  29. //Key:='@';                    // doubleup test - seriously breaks things, so don't do it
  30.  
  31. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  32. // from here onwards ONLY work with NewKey, whose 'case' now correctly reflects the states of Shift and  //
  33. // CapsLock that the LCL holds internally. as an example, if you application was a terminal emulator you //
  34. // would add the value of NewKey to the head of your serial output queue using something like:           //
  35. //                                                                                                       //
  36. //   I:=TxBuffer.head;                                                                                   //
  37. //   TxBuffer.data[I]:=NewKey;                                                                           //
  38. //   I:=(I+1) mod sizeof(TxBuffer.data);                                                                 //
  39. //   TxBuffer.head:=I                                                                                    //
  40. //                                                                                                       //
  41. ///////////////////////////////////////////////////////////////////////////////////////////////////////////                        
  42.  
  43. end;

Bart

  • Hero Member
  • *****
  • Posts: 5497
    • Bart en Mariska's Webstek
Re: doubled keypresses register when there is a TMemo on a form
« Reply #12 on: January 07, 2025, 11:40:51 pm »
I think I remeber a simialr bugreport, but I cand find it in the bugtracker.

Found it, it was a bugreport file by myself, 6 years ago: https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/33017  :-[

I closed your report as duplicate of the first one.

Bart

robert rozee

  • Full Member
  • ***
  • Posts: 204
Re: doubled keypresses register when there is a TMemo on a form
« Reply #13 on: January 11, 2025, 02:16:21 pm »
Bart: your test code proved most helpful in nailing down some of the 'variability' i'd been seeing:

Code: Pascal  [Select][+][-]
  1.   if Assigned(ActiveControl) then
  2.     Result := ActiveControl.Name

up until now i was not aware it was possible to find out which (if any) control currently has focus. adding the following into a timer event in my code:

Code: Pascal  [Select][+][-]
  1.   if Assigned(Form1.ActiveControl) then writeln(Form1.ActiveControl.Name)
  2.                                    else writeln('no active control set');

revealed that there was a TPanel that was taking focus when the TMemo was disabled+hidden. whether or not the TPanel became focuses depended upon when exactly during the application's startup process the TMemo was disabled+hidden.

a question: once a control has focus, is there any way to tell a form to remove focus? i've tried Form1.ActiveControl:=nil but this does nothing useful - it is like the assignment to nil is not passed on to anything. the only thing that seems to work is to set focus to a control, then disable+hide that control. but even that is not 100% foolproof - i have a test application where pressing the tab key sets focus, even with TabStop:=false for all focus-able controls.


cheers,
rob   :-)


addendum: i've found that Form1.SetFocus does nothing, while Form1.SetFocusedControl(Form1) does change the result returned by Form1.ActiveControl.Name but does not stop the doubling-up of OnKeyPress events!
« Last Edit: January 11, 2025, 02:32:23 pm by robert rozee »

Bart

  • Hero Member
  • *****
  • Posts: 5497
    • Bart en Mariska's Webstek
Re: doubled keypresses register when there is a TMemo on a form
« Reply #14 on: January 11, 2025, 02:31:14 pm »
Form1.ActiveControl := nil does work on Windows, but at least on GTK this has no effect (it's a GTK thing AFAIK).

You can disable the TMemo, this will take away focus, but looks ugly, and the user cannot click on it anymore.

Bart

 

TinyPortal © 2005-2018