Tab and Esc keys sometimes don't work

Bug reports will be moved here when the described bug has been fixed

Moderators: white, Hacker, petermad, Stefan2

User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48096
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

I will check that, but it's probably not easy - I would probably have to subclass the edit control for that. Maybe I have to postpone it to a later release...
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Hmmm, every TControl in Delphi 5 has public WindowProc property, which allows replacing a window proc easily at runtime. However, there is also a solution for Delphi 2:

Code: Select all

function EditWndProc(Handle : HWND; Msg : UINT; wParam : WPARAM; lParam : LPARAM) : LRESULT; stdcall;
begin
  with TEdit(FindControl(Handle)) do
  begin
    {Test}
    if Msg = WM_KEYDOWN then
    if wParam = VK_RETURN then
      Application.MessageBox('Enter pressed','Test',MB_OK);

    if Msg = WM_DESTROY then
      SetWindowLong(Handle,GWL_WNDPROC,Tag);
    Result:=CallWindowProc(Pointer(Tag),Handle,Msg,wParam,lParam);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  with Edit1 do
  begin
    Tag:=GetWindowLong(Handle,GWL_WNDPROC);
    SetWindowLong(Handle,GWL_WNDPROC,Integer(@EditWndProc));
  end;
end;
Regards!
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Now, in rc2, it's almost ideal :) There is a small problem with a "ding" sound when pressing Enter or Esc. I suppose that the reason is edit control - TEdit always plays "ding" sound when pressing Enter or Esc. The solution is to handle WM_CHAR message:

Code: Select all

function EditWndProc(Handle : HWND; Msg : UINT; wParam : WPARAM; lParam : LPARAM) : LRESULT; stdcall;
begin
  with TEdit(FindControl(Handle)) do
  begin
    {Test}
    if Msg = WM_KEYDOWN then
    if wParam = VK_RETURN then
      Text:='Enter pressed';

    {Disable "ding" sound}
    if Msg = WM_CHAR then
    if AnsiChar(wParam) in [#9,#13,#27] then
      wParam:=0;

    if Msg = WM_DESTROY then
      SetWindowLong(Handle,GWL_WNDPROC,Tag);
    Result:=CallWindowProc(Pointer(Tag),Handle,Msg,wParam,lParam);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  with Edit1 do
  begin
    Tag:=GetWindowLong(Handle,GWL_WNDPROC);
    SetWindowLong(Handle,GWL_WNDPROC,Integer(@EditWndProc));
  end;
end;
P.S. Maybe you could also add a support for Tab key - when no message box is existing, Tab key exits editing mode and jumps to second panel immediately.

Regards!
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48096
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

That's why I wrote in the history "Unfortunately the beep cannot be avoided". I will try your workaround.
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Still "dinging" and Tab key still doesn't work in 8.01 rc 1.
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48096
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

I tried your workaround, but it didn't work.

Moving thread to "will not be changed".
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

I thought that your TInEdit control is derived from TEdit (which omits calling the original window procedure when char is #0), but it is probably derived directly from Windows EDIT class and it calls original window procedure even for #0 char.

So the working solution is:

Code: Select all

function EditWndProc(Handle : HWND; Msg : UINT; wParam : WPARAM; lParam : LPARAM) : LRESULT; stdcall;
begin
  with TEdit(FindControl(Handle)) do
  begin
    {Disable "ding" sound}
    if Msg = WM_CHAR then
    {3 = Ctrl+C (Copy), 8 = VK_BACK, 22 = Ctrl+V (Paste), 24 = Ctrl+X (Cut),
     26 = Ctrl+Z (Undo), 30 = unicode separator, 31 = unicode separator}
    {24 = Ctrl+X dings when no text is selected and this behavior should
     be preserved, so we don't exit for Ctrl+X}
    if Char(wParam) in [#0..#2,#4..#7,#9..#21,#23,#25,#27..#29] then
    begin
      Result:=0;
      Exit;
    end;

    if Msg = WM_DESTROY then
      SetWindowLong(Handle,GWL_WNDPROC,Tag);
    Result:=CallWindowProc(Pointer(Tag),Handle,Msg,wParam,lParam);
  end;
end;
We are not changing wParam to #0, but we set Result to 0 (= WM_CHAR message handled) and omit calling original window procedure.

I checked (with a debugger) the code of the original EDIT window procedure. It beeps for chars with code <= 32 (space), except for some chars like Ctrl+C or backspace - these chars must be passed to the original window procedure.

Regards!
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48096
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Thanks, I will try your code. Hopefully this will not break input of Chinese or other double byte charsets...
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Important remark. I used Char, but it's mapped to AnsiChar (not WideChar) in Delphi 2. So another small fix - I added checking for > 255 characters and changed Char to AnsiChar to be sure to check for 1-byte value in all Delphi versions:

Code: Select all

function EditWndProc(Handle : HWND; Msg : UINT; wParam : WPARAM; lParam : LPARAM) : LRESULT; stdcall; 
begin 
  with TEdit(FindControl(Handle)) do 
  begin 
    {Disable "ding" sound} 
    if Msg = WM_CHAR then 
    {3 = Ctrl+C (Copy), 8 = VK_BACK, 22 = Ctrl+V (Paste), 24 = Ctrl+X (Cut), 
     26 = Ctrl+Z (Undo), 30 = unicode separator, 31 = unicode separator} 
    {24 = Ctrl+X dings when no text is selected and this behavior should 
     be preserved, so we don't exit for Ctrl+X}
    if (Word(wParam) < 256) and (AnsiChar(wParam) in [#0..#2,#4..#7,#9..#21,#23,#25,#27..#29]) then
    begin 
      Result:=0; 
      Exit; 
    end; 

    if Msg = WM_DESTROY then 
      SetWindowLong(Handle,GWL_WNDPROC,Tag); 
    Result:=CallWindowProc(Pointer(Tag),Handle,Msg,wParam,lParam); 
  end; 
end;
The code above is based on the EDIT message handling in user32.dll (checked with a debugger - there is even 'EditWndProc' export in user32.dll), so I should work properly - DefWindowProc only dings and exits in these cases (for single-line edit controls and when edit control is not a part of the combobox).
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48096
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

The problem is only with the 64-bit version, which is using Lazarus, not Delphi 2. I'm just using

Code: Select all

if WideChar(wParam) in [#0..#2,#4..#7,#9..#21,#23,#25,#27..#29] then
which seems to work just fine.
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Checking if WideChar belongs to set of chars is not safe, at least in Delphi (I didn't make any tests with Lazarus). With Delphi < 2009 this construction can't even be compiled. With newer Delphi versions it can be compiled, but with warning. I created the following small test and tested it with Delphi XE2:

Code: Select all

procedure TForm1.Button1Click(Sender: TObject);
var
  C : AnsiChar;
begin
  C:=#161;
  if C in [#161] then
    Caption:='ok'
  else
    Caption:='error';
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  C : WideChar;
begin
  C:=#161;
  if C in [#161] then
    Caption:='ok'
  else
    Caption:='error';
end;
Test1 sets caption to 'ok', but Test2 sets it to 'error'. We get the following warning message for Test2: "WideChar reduced to byte char in set expressions. Consider using 'CharInSet' function in 'SysUtils' unit". And - as it can be seen in assembler - for some reason Test2 compares C not with #161, but with #711.

And the suggested CharInSet function is:

Code: Select all

function CharInSet(C: WideChar; const CharSet: TSysCharSet): Boolean;
begin
  Result := (C < #$0100) and (AnsiChar(C) in CharSet);
end;
So "if (Word(wParam) < 256) and (AnsiChar(wParam) in [...]) then" construction seems to be more safe and more universal than "if WideChar(wParam) in [...] then".


Some references:

W1050: WideChar reduced to byte char in set expressions (Delphi)

Delphi, how to avoid unicode warning message in D2009, D2010
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48096
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

I didn't encounter any problems with Lazarus, but I will still chage it to

Code: Select all

    if (wparam<255) and (char(wParam) in [#0..#2,#4..#7,#9..#21,#23,#25,#27..#29]) then
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

It's safer to use AnsiChar instead of Char in this context. Char may be mapped to AnsiChar of WideChar, depending on the compiler. For example, for Delphi >= 2009, Char will be mapped to WideChar and will introduce warning W1050 and these problems that I described above.

Regards!
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48096
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Not in Lazarus...

Please try with RC4 whether it works as intended or not.
Author of Total Commander
https://www.ghisler.com
User avatar
MarcinW
Power Member
Power Member
Posts: 852
Joined: 2012-01-23, 15:58 UTC
Location: Poland

Post by *MarcinW »

Unfortunately I have no possibility to test 64-bit version...
Post Reply