Jump to content
gioma

Form.OnKeyUp(key:word) = Get ASCII code of Special character(è)

Recommended Posts

Hi everyone, I'm making an application that communicates between different operating systems. In this case I have to send a character when it is pressed from Windows to MAC.
In the Form.OnKeyUp function there is the parameter "key: Word" which represents the character code.
If it is not a special character (à, è, ì, ò, ù) this co corresponds to the ASCII code.
If, on the other hand, I press the "è" key, the code will be 222 which however does not correspond to its ASCII code and therefore I send wrong information to the MAC which is unable to retrieve the corresponding character.
How do I get the exact ASCII code of that character?
Thank you all.

Share this post


Link to post
2 hours ago, gioma said:

Hi everyone, I'm making an application that communicates between different operating systems.

Are you using VCL or FMX for the sending app?

2 hours ago, gioma said:

In this case I have to send a character when it is pressed from Windows to MAC.

I do not recommend sending individual characters. Send whole strings instead. And be sure to encode them in a platform-neutral encoding, such as UTF-8.  For instance, what you consider to be a single "character" may in fact be composed of multiple Unicode codepoints grouped together to create a grapheme cluster, which can't be obtained in a single UI event or transmitted as a single byte/char.

 

Rather than transmitting characters as they are being typed, I suggest having the user type into an Edit control first, and then when Enter is pressed, or a button is clicked, etc, then send the entire text in one go.  That will be much easier to manage.

2 hours ago, gioma said:

In the Form.OnKeyUp function there is the parameter "key: Word" which represents the character code.

In VCL at least, the OnKey(Down/Up) events deal in "virtual" key codes, whereas the OnKeyPress event deals in character codes.

2 hours ago, gioma said:

If it is not a special character (à, è, ì, ò, ù) this co corresponds to the ASCII code.

Only because Microsoft decided that the virtual key codes for ASCII characters would be the same numeric values as their ASCII character codes.  That is not the case for non-ASCII characters.

2 hours ago, gioma said:

If, on the other hand, I press the "è" key, the code will be 222 which however does not correspond to its ASCII code

Correct.  Virtual key code 222 (0xDE) is VK_OEM_7: "Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key"

2 hours ago, gioma said:

How do I get the exact ASCII code of that character?

You don't, because there is no ASCII code for that character.  The best way to deal with non-ASCII characters is to deal with Unicode strings.

  • Like 1

Share this post


Link to post
On 4/15/2022 at 6:21 PM, gioma said:

Hi everyone, I'm making an application that communicates between different operating systems. In this case I have to send a character when it is pressed from Windows to MAC.
In the Form.OnKeyUp function there is the parameter "key: Word" which represents the character code.
If it is not a special character (à, è, ì, ò, ù) this co corresponds to the ASCII code.
If, on the other hand, I press the "è" key, the code will be 222 which however does not correspond to its ASCII code and therefore I send wrong information to the MAC which is unable to retrieve the corresponding character.
How do I get the exact ASCII code of that character?
Thank you all.

Like Remy said this would be much easier to handle if you transmit the whole input string after the user has entered it. There are ways to figure out which character would be created from a virtual key code and the current state of the modifier keys (Shift, Alt, Ctrl), see the MapVirtualKeyEx Windows API function, but this pointless if you have to deal with accented characters or other composites that are created by a sequence of keystrokes, or Alt+numpad input. Let Windows do this work for you and use the OnKeyPress event; it gives you the character directly.

  • Like 1

Share this post


Link to post

I use VCL and the App isn't made with Delphi.

The purpose is to send the KeyUp and KeyDown events with the char from a Delphi-VCL application to a non-delphi application running on Android, iOS and Mac.

 

Share this post


Link to post
8 minutes ago, gioma said:

The purpose is to send the KeyUp and KeyDown events with the char from a Delphi-VCL application to a non-delphi application running on Android, iOS and Mac.

But, why does it have to be sent char-by-char?  That is much harder than sending properly encoded whole strings.

Share this post


Link to post
1 minute ago, Remy Lebeau said:

But, why does it have to be sent char-by-char?  That is much harder than sending properly encoded whole strings.

Because I need to emulate the Keydown and KeyUp events for every single character pressed.

Share this post


Link to post

I almost found a solution:

 

procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  buf:WideChar;
  KSta:TKeyboardState;
  s:string;
begin
  log.Lines.Add('KeyUp : '+ inttoStr(key) );
  Winapi.Windows.ToUnicodeEx( key, 0, ksta, @buf,255, 0, 0);
  s:=WideCharToString(@buf);
  log.Lines.Add('KeyUp : '+ s);
end;

Obviously I will not use it like that, but there is a problem, it goes into exception when it writes the result to the log (TMemo).
Even if I levo the last line goes into exception, however the variable s is valued.

Share this post


Link to post
24 minutes ago, gioma said:

I almost found a solution:

 


procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  buf:WideChar;
  KSta:TKeyboardState;
  s:string;
begin
  log.Lines.Add('KeyUp : '+ inttoStr(key) );
  Winapi.Windows.ToUnicodeEx( key, 0, ksta, @buf,255, 0, 0);
  s:=WideCharToString(@buf);
  log.Lines.Add('KeyUp : '+ s);
end;

Obviously I will not use it like that, but there is a problem, it goes into exception when it writes the result to the log (TMemo).
Even if I levo the last line goes into exception, however the variable s is valued.

buf is declared as a single Widechar but you tell ToUnicodeEx that it can hold 255 Widechars. A good way to ruin your call stack. :classic_cool:

  • Like 1

Share this post


Link to post
1 hour ago, gioma said:

I almost found a solution:

There are a number of problems with that code:

 

- the wScanCode parameter of ToUnicodeEx() is not optional. Unfortunately, the OnKeyUp event does not give you the original scan code that the OS provided in the WM_KEYUP message.  However, in this case, since only bit 15 of the scan code is really needed, you can fake it.  For a KeyUp event, bit 15 needs to be set to 1, not 0.

- you are not populating the TKeyboardState before passing it in to ToUnicodeEx().  You need to call GetKeyboardState() first.

- you are passing in a single WideChar for the output buffer to ToUnicodeEx(), but you are telling ToUnicodeEx() that the buffer is large enough to hold 255 WideChars, which is a lie. The output of the conversion can be more than 1 WideChar, so you need to allocate enough space to hold the entire result.  The return value will tell you how many WideChars were actually written to the buffer.

- ToUnicodeEx() alters the keyboard state while translating the key, unless you ask it not to (Windows 10+ only).

- WideCharToString() expects a null-terminated PWideChar string, not a single WideChar.

 

With that said, try something more like this instead:

procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  buf: array[0..15] of WideChar;
  KSta: TKeyboardState;
  numChars: Integer;
begin
  Winapi.Windows.GetKeyboardState(ksta);
  numChars := Winapi.Windows.ToUnicodeEx(key, $8000, ksta, buf, Length(buf)-1, 4, 0);
  if numChars > 0 then
  begin
    buf[numChars] := #0;
    log.Lines.Add('KeyUp : ' + IntToStr(Key) + ' = ' + WideCharToString(buf));
  end
  else if numChars = 0 then
    log.Lines.Add('KeyUp : ' + IntToStr(Key) + ' = (no translation)')
  end else
    log.Lines.Add('KeyUp : ' + IntToStr(Key) + ' = (dead key)');
end;

However, if you really want to handle individual key presses, you should be using the OnKeyPress event of the OnKey[Down|Up] events, as previously stated.  The OnKeyPress event gives you translated Unicode characters, not virtual key codes.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  log.Lines.Add('KeyPress : ' + IntToStr(Key) + ' = ' + Key);
end;

 

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post
14 hours ago, PeterBelow said:

buf is declared as a single Widechar but you tell ToUnicodeEx that it can hold 255 Widechars. A good way to ruin your call stack. :classic_cool:

13 hours ago, Remy Lebeau said:

There are a number of problems with that code:

 

- the wScanCode parameter of ToUnicodeEx() is not optional. Unfortunately, the OnKeyUp event does not give you the original scan code that the OS provided in the WM_KEYUP message.  However, in this case, since only bit 15 of the scan code is really needed, you can fake it.  For a KeyUp event, bit 15 needs to be set to 1, not 0.

- you are not populating the TKeyboardState before passing it in to ToUnicodeEx().  You need to call GetKeyboardState() first.

- you are passing in a single WideChar for the output buffer to ToUnicodeEx(), but you are telling ToUnicodeEx() that the buffer is large enough to hold 255 WideChars, which is a lie. The output of the conversion can be more than 1 WideChar, so you need to allocate enough space to hold the entire result.  The return value will tell you how many WideChars were actually written to the buffer.

- ToUnicodeEx() alters the keyboard state while translating the key, unless you ask it not to (Windows 10+ only).

- WideCharToString() expects a null-terminated PWideChar string, not a single WideChar.

 

With that said, try something more like this instead:


procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  buf: array[0..15] of WideChar;
  KSta: TKeyboardState;
  numChars: Integer;
begin
  Winapi.Windows.GetKeyboardState(ksta);
  numChars := Winapi.Windows.ToUnicodeEx(key, $8000, ksta, buf, Length(buf)-1, 4, 0);
  if numChars > 0 then
  begin
    buf[numChars] := #0;
    log.Lines.Add('KeyUp : ' + IntToStr(Key) + ' = ' + WideCharToString(buf));
  end
  else if numChars = 0 then
    log.Lines.Add('KeyUp : ' + IntToStr(Key) + ' = (no translation)')
  end else
    log.Lines.Add('KeyUp : ' + IntToStr(Key) + ' = (dead key)');
end;

However, if you really want to handle individual key presses, you should be using the OnKeyPress event of the OnKey[Down|Up] events, as previously stated.  The OnKeyPress event gives you translated Unicode characters, not virtual key codes.


procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  log.Lines.Add('KeyPress : ' + IntToStr(Key) + ' = ' + Key);
end;

 

You're right! Fixed the error now it works 🤩

 

procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var

  buf:WideChar;
  KSta:TKeyboardState;
begin
  //initialize TKeyboardState
  GetKeyboardState(ksta);
  //limit to 1 the capture lenght into buffer that is wide char
  ToUnicodeEx( key, 0, ksta, @buf, 1, 0, 0);
  log.Lines.Add('KeyUp : '+ inttoStr(key)+'='+buf );

end;

Now if I press "è" it wrote into TMemo: 

KeyUp : 186=è

Thank you all! :classic_smile:

 

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×