Using KeyDown to trigger edit mode and multi key typing

I have a custom control using a canvas and some other things. In it I’m using the Keydown event to trigger the start of an Edit action. In simple terms the sequence is as follows:

Function KeyDown
if is some special key then
   ' Deal with it - Such as arrows etc.
else
   EditField.Text = Char
   EditField.Show
end if
end function

the problem I’ve got is spotting multi key sequences and not starting my edit field until I have a complete character.

For example, if the user types Option-e, intending to press e afterwards. My code detects the Option-e steals it and starts the edit process, loosing the combination. For those I could spot the Option key and ignore it. However, I’ve then got none Roman languages like Chinese to worry about. Halfway through the first character I would trigger and they loose their first letter.

You probably need to the TextInputCanvas plugin to do this.

1 Like

I can’t do that as I have a whole grid control in operation. Also the EditField deals with it perfectly once it is up and running. It’s just this edge case when switching from viewing to editing a cell.

To explain a little more the custom control is a grid (that stores no data and only uses the paint event to draw). The user can navigate the grid and see the data. Everything is display only.

When the user types something then the EditField is shown and deals with data entry. KeyDown is being used to trigger that transition, and thus the problem. If they type Option-e e, I want to start the EditField with é rather than nothing.

I had thought about Initiating the DesktopTextField and then re-raising the event. However, I can’t seem to use RaiseEvent for a keyboard event, even i I put it in an Extension method of the DesktopTextField control.

As you probably know, other keyboard layouts won’t make “é” with option-e (and may have a specific key for that).
Dealing specifically with “known keys” won’t work.

Even if you could re-raise the event I don’t think it would work correctly. The Xojo canvas seems to get the keydown event immediately meaning that you wouldn’t know if it was a keydown or a long press to trigger the input method editor.

Your only solution might be to see if you can capture the low level key NSEvents and forward them to the text field.

@Arnaud_N
Yes, I presume on those keyboards it shouldn’t be a problem anyway. However, Chinese / Japanese keyboards require multiple keypresses if I remember correctly.

@keving
Yes, the idea was if I can trigger then same event on the DesktopTextField then it would work as if it was active before the key was pressed. If, for example, I activate edit mode with a double-click and then do option-e e it all works correctly.

I have other combinations that work in two times. Option-n makes ~ and option-n followed by n makes ñ. Same for ^ (key alone) and ê (^+e).
I’m not sure I’ve understood your actual problem, as I haven’t tested (only tried to understand by reading); I’m just pointing out those special cases.

It is hard to get across, I have a CustomControl which has a Canvas and a hidden EditField. When the user presses a key on the Canvas, keydown triggers and the EditField is moved to the correct location, made visible and given focus. The user can then type into the field and press return the data is saved and the EditField hidden. Think of MS Excel and its grid, as you start typing the Cell becomes active for editing and then goes away after wards.

Here are some pictures, the custom control is everything below the toolbar, it contains a canvas (which draws the grid) and the two scrollbars. It also has a hidden EditField (seen in picture 2).

Before the edit:

After keypress:

After edit complete:

The problem is the first keypress, it triggers on the canvas, it should then be passed to the EditField once it is in place and visible. The EditField then takes over. Pressing return (etc) then hides the EditField and shows the data in the Canvas (grid).

Ideally, you’d have to forward the event coming from the OS to the edit field (after showing the field, of course). That’s doable, but with plugins like the MBS plugin or using declares.

Other than that… no idea. Have a specific key the user must type to enter to edit mode (like “Return”)? Not elegant, but you’re hitting a limitation of Xojo’s built-in features, I’d say.

When you try option-e, with your layout, what do you get in KeyDown? Just a ´ symbol or the whole é?
If ´, can’t you store this fact and modify the character afterward?

I already have the ability to double click to enter edit mode. Its nice to be able to just start typing like you can in Excel. I’m fine with declares if you have a pointer as to were to start?

In terms of what happens, the option-e triggers KeyDown. Using EditField.Text = Key results in nothing being in the field. If that worked I wouldn’t be asking :slight_smile: I can’t just store it as it could be half or a third of a Chinese / Japanese character. Making life massively complex. I think triggering the keypress event on the control is the right process, I just need a way of doing that.

If you have the MBS plugins then here is a very basic example for macOS that accepts a key down event in a canvas and forwards it to a text field.

I have noticed two issues though:

  1. The Canvas KeyDown event didn’t seem to receive CJK characters so that doesn’t get forwarded on.
  2. Posting the event triggers an operating system security warning on newer versions of macOS.
Project: Redirect Key Event To Edit Field
 Date: Friday, 3 December 2021 14:58:47
 
Window1
Class Window1
Inherits Window

Private mSkipKeyDown_ As Boolean

Window1 Control Canvas1:
Function KeyDown(Key As String) As Boolean 
   Dim n As NSEventMBS
   
   'grab the current nsevent
   n = NSApplicationMBS.sharedApplication.currentEvent
   
   
   'switch the focus to the text field
   TextField1.SetFocus
   
   
   'add the key to the text field and move the selection to the end
   TextField1.Text = TextField1.Text + Key
   TextField1.SelStart = Len(TextField1.Text)

   
   'flag that we want to skip the next key down field in the text field
   'why do we need this?
   'if the user has just pressed a key then we will end up with a duplicate character in the text field which we want to avoid
   'if the user has long pressed a key then we won't get a duplicate character (we still get keydown events in th e text field so the flag gets reset)
   Self.mSkipKeyDown_ = True


   'add a delay to allow the events to take effect '(this seems a bit suspect)
   DelayMBS(0.2)


   'post the key event
   'for some reason we have to switch the option key off (this seems a bit suspect) 'we still get the correct character if the option key was held down though
   Call RemoteControlMBS.MacPressOptionKey(False)
   Call RemoteControlMBS.MacPressKey(Asc(Key), n.keyCode, True)


   'flag that we have handled the key down event
   Return True 
End Function

Sub Paint(g As Graphics, areas() As REALbasic.Rect) 
   g.DrawRect(0, 0, g.Width, g.Height)
End Sub

Window1 Control TextField1:
Function KeyDown(Key As String) As Boolean 
   If Self.mSkipKeyDown_ = True Then
      Self.mSkipKeyDown_ = False
      Return True 
   Else
      Return False 
   End If
End Function 
End Class

I’m afraid I don’t have MBS, and can’t afford it for this project.

With a little playing around and some help from MacOSLib I’ve put the following together:

Declare Function NSClassFromString Lib "Cocoa.framework" ( aClassName As CFStringRef ) As Ptr
Declare Function sharedApplication Lib "Cocoa.framework" Selector "sharedApplication" ( class_id As Ptr ) As Ptr
Declare Function getCurrentEvent Lib CocoaLib Selector "currentEvent" (obj_id As Ptr) As Ptr
Declare Sub sendEvent Lib CocoaLib Selector "sendEvent:" (obj_id As Ptr, anEvent As Ptr)

' Get the shared application
Var SharedApp As Ptr = sharedApplication( NSClassFromString("NSApplication") ) 
' Retrieve the current event from the system
Var oNSEvent As NSEvent = New NSEvent( getCurrentEvent( SharedApp ) )

' This is my method that positions, makes visible and sets focus on the EditField.
StartCellEdit SelectionColStart, SelectionRowStart, Key

' Now we post the event back to the application.
sendEvent SharedApp, oNSEvent

The great news is that it works perfectly. I need to try some Chinese etc. but option-e produces ’ and following it with e, produces é.

1 Like

@keving

I notice in your code you have a variable to prevent duplicates characters. Surely the reason you are getting duplicates is because of the following line:

TextField1.Text = TextField1.Text + Key

Which is putting the character in the field and then the event is also doing the same. If you remove that line you likely won’t have a problem with duplicates.

This isn’t going to be easy, I’m afraid. I know, in theory, how to do that using the MBS plugin (I’d say differently than @keving suggested, see below).
You have to intercept the event (using an event monitor), store it in a variable, prevent the original event from happening, put the focus to the field and trigger the stored event.
For “simple” declares, I could help, but creating a class like EventMonitorMBS (and its newer equivalent classes) with declares, I’m sorry I can’t help much.

This example is, IMO, still not low-level enough.

With filtering right in an event monitor (MBS has classes for that):

You’ll get all events, even ones Xojo doesn’t perceive.

I’ve not tested your example, but I strongly think this happens with that line:
Call RemoteControlMBS.MacPressOptionKey(False)
(and the one below it)

If you trigger the exact same event you received and stored (rather than, as in your example, add the corresponding key after receiving it from a KeyDown event), you shouldn’t have to do anything. The event would be the same on both controls.

@Arnaud_N
I think you are correct. Remote control would require a permission. Simply getting the current event and then sending it to the application causes no problems.

a) When the new event gets posted and the key is not being held down (simple key press) then the text field gets a keydown event which adds the character to the content. This means we have a duplicate character to deal with.

b) When the new event gets posted and the key is being held down then the text field gets a keydown event but the character doesn’t get added to the content. This means we don’t have a duplicate character to deal with.

I did create a basic example using NSEventMonitorMBS where the event would be applied to another control by first changing focus and modifying the NSEvent.locationInWindow properties. It did seem to be better than my previous example but even then I saw some weird things when the CJK input method editor was invoked.

1 Like

In my case I don’t really care about holding down keys. It’s not something that is likely to be an issue in a data grid context. My solution does not maintain the keypress repeat.

With your example you are preloading the character into the field and then sending the keystroke once more into the application, which is why you get duplicate keys. If you simply don’t put the character into the text field in the first place you won’t get a duplicate.

This will be very important if you are entering multi key languages or even just option-e followed by e. Pressing option-e should put ´ into the field. Then when you press e afterwards it should show as é. I you put the option-e into the field using code (editfield.text = key) you won’t get anything in there (this was my original issue). Equally if you are typing Japanese, for example, they you won’t get the first half of the first character. I have prevented anything from being put into the field and then allow the sendEvent to do the hard work. It works a treat. I’ve tried Japanese and simple things like option-e e.

The code I posted in the solution goes into the keydown event of the triggering control. The following code is used to position and show the editfield.

   StartCellEdit SelectionColStart, SelectionRowStart

I’ve removed the Key from it as I don’t want it posted onto the editfield. The sentEvent call will do that for me and works with CJK, option-e e and anything else I have tried.

Thank you both for your help both were required to get the solution. @Arnaud_N provided the idea of capturing the event and posting it on. @keving The MBS code pointed out how to easily obtain the event I was processing. MacOSLib provided the NSEvent class and a pointer to how to sendEvent.

2 Likes