OnKeyDown doesn't work with a keyboard focused Editable Text Box wrapped in a Canvas in a User Widget.

I have a MyTextBox widget with the following setup:


I overridden OnKeyUp and OnKeyDown:

Then I add this widget to the viewport from the map:

My problem is that when I type in the text box, only the OnKeyUp function is called and the OnKeyDown is never called. I tried to change the focusable property of the MyTextBox widget, but it doesn’t seem to make a difference.

Any ideas? I am using Unreal Engine 5.2.1.

My final goal is to allow the player to hold down the Up or Down key while the [Text Box] has the keyboard focus and the Text will be updated and iterated to older or newer history entries respectively. I have an object that keeps the user’s history so that the user can use the Up and Down arrow keys to navigate their history. It works well with just OnKeyUp to check both Up and Down keys and the user can go through one entry at a time.

However, I want the user to be able to navigate the history with speed by holding down either the Up or Down key. But I want the system to only begin scrolling through the history entries after the user has held down the Up or Down key for 150 ms so that the user can still go through one entry at a time.

I am open to workaround if my current approach worked around.

As workarounds, I tried using calling Is Input Key Down through the Player Controller and directly binding Up and Down keys in either the widgets or the Player Controller. They appear to work as long as the [Text Box] inside the MyTextBox widget does not have keyboard focus.

1 Like

Anything with the IsFocusable bool will eat input and decide to handle it or not. If it’s handled, nothing else can process it. If it’s not, the input will bubble up into the ancestors the widget is nested in on the widget tree.

Meaning, if you focus the EditableTextBox to type text in it, and it decides to handle some inputs like apparently the arrow keys, you won’t get any chance of implementing custom logic for arrow keys on the UserWidget.

One workaround is to implement a custom text box in c++. Another workaround is to set IsFocusable to false on the text box but true on the UserWidget. Then focus only the UserWidget and handle all input there, including passing on typed text to the text box. Due to the complexity of the text box, I would not do that.

Dived into it a bit, there is a default implementation for keys, chars, etc on the editable text level and there is also the default implementation on the widget level which uses arrow keys to navigate focus from one to another.

On that screenshot SEditableText::OnKeyDownHandler seems to be used to override default behavior. Try messing with that before creating something entirely custom.

Can’t tell if you are a c++ user but I don’t think this will go anywhere if it’s blueprint only. If so you still have the option to put the editable text box next to a button in a UserWidget, and use that button to summon a list of text histories to paste into the text box. That is not even a bad solution since it separates logic nicely.

1 Like

Appreciate the info. You’ve given me a lot to think about. I will need to take some time to digest this information. I am comfortable with C++, so I will try to see where I can with all these ideas. I’ll get back after some time. Thanks. :slight_smile:

(@GarettHunter if you are still looking for a solution)

This all seems very messy. Alone looking at the implementation of the EditableTextBox object - tons of macros for generating the Slate functions. Not to mention that even after creating such a class, figuring out how to use it as a base for creating widgets seems more difficult.

But there is a much simpler solution:

Any widget has a method called NativeOnPreviewKeyDown, which gets called before the input gets forwarded to any focused child widget element. So in my case, I created a base widget class Base, and inside it override this member function:

// header file
UCLASS(Blueprintable)
class UBase : public UUserWidget
{
    GENERATED_BODY()

public:
    virtual FReply NativeOnPreviewKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;
};

// cpp file
FReply UBase::NativeOnPreviewKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
{
	FKey key = InKeyEvent.GetKey();
	const FString keyName = key.GetFName().ToString();
	if (keyName == "Up" || keyName == "Down")
	{
		return FReply::Unhandled();
	}

	return Super::NativeOnPreviewKeyDown(InGeometry, InKeyEvent);
}

This will immediately return the input event as Unhandled when the user pressed up/down arrows, REGARDLESS of whether any text/button widget has focus, and so inside the derived Blueprint’s graph I can implement the OnKeyDown method and do whatever I want. :slight_smile:

EDIT:
On further experimentation, it might be necessary to call “BlueprintImplementableEvent” class members from within this override as well - just tried it, and it seems to work quite reliably.

The problem with this is that you almost always want to handle input on a focused widget instead of bubbling. Hijacking that process creates a cursed UI even if it works now for you as a workaround.

If you are using 1 button for multiple actions in a menu, you can assume that you want to handle any key on the focused widget and not accidentally perform an action on the menu that widget is in. Say… C for cancel current action and C for close entire menu.

If you hijack that and the menu handles “Close menu” the user thinks he is interacting with a widget but is not, + the menu disappears. A focused widget gives a natural “context” to handle input in which should ensure you don’t do things you don’t intend to.

1 Like

You can do this comparison without string, string is fragile:

if (key == EKeys::Down || key == EKeys::Up) {

}

Usually I’d also call Super::Method right at the start regardless of what I’d do after on the overridden method, I see it as a good practice. It’s an odd one here because the return value is usually garbage and the handling process is hijacked.

1 Like

Thank you for that! I have been looking and looking, and couldn’t find this anywhere.
Do you know by chance if I can do the same for gamepad keys?

The InKeyEvent knows if input was a gamepad key or not, but so far my best option has been to hash the string and compare that as how I do here (feels tedious):

UENUM(BlueprintType)
enum class EMyGamepadKey : uint8
{
	invalid,

	left_shoulder,
	right_shoulder,
	left_trigger,
	right_trigger,
	face_button_top,
	face_button_bottom,
	face_button_left,
	face_button_right,
	left_thumbstick_up,
	left_thumbstick_down,
	left_thumbstick_left,
	left_thumbstick_right,
	right_thumbstick_up,
	right_thumbstick_down,
	right_thumbstick_left,
	right_thumbstick_right,
	data_pad_up,
	data_pad_down,
	data_pad_left,
	data_pad_right,
	special_left,
	special_right
};

UCLASS()
class UMyGamepadKeyConverter final : public UObject
{
	GENERATED_BODY()

private:
	// Hard topic. Some help:
	// https://stackoverflow.com/questions/2111667/compile-time-string-hashing
	static constexpr size_t MyCompileTimeHash(const std::string_view Str)
	{
		size_t Hash = sizeof(size_t) == 8 ? 0xcbf29ce484222325 : 0x811c9dc5;
		constexpr size_t Prime = sizeof(size_t) == 8 ? 0x00000100000001b3 : 0x01000193;

		const char* Input = Str.data();
		while (*Input) {
			Hash ^= static_cast<size_t>(*Input);
			Hash *= Prime;
			++Input;
		}

		return Hash;
	}

public:
	UFUNCTION(BlueprintCallable)
	static inline EMyGamepadKey GetGamepadKeyFromString(const FString& GamepadKeyString)
	{
		// String hash library
		static constexpr std::string_view String_Ls = "Gamepad Left Shoulder";
		static constexpr std::string_view String_Rs = "Gamepad Right Shoulder";
		static constexpr std::string_view String_Lt = "Gamepad Left Trigger";
		static constexpr std::string_view String_Rt = "Gamepad Right Trigger";
		static constexpr std::string_view String_Fbt = "Gamepad Face Button Top";
		static constexpr std::string_view String_Fbb = "Gamepad Face Button Bottom";
		static constexpr std::string_view String_Fbl = "Gamepad Face Button Left";
		static constexpr std::string_view String_Fbr = "Gamepad Face Button Right";
		static constexpr std::string_view String_Ltu = "Gamepad Left Thumbstick Up";
		static constexpr std::string_view String_Ltd = "Gamepad Left Thumbstick Down";
		static constexpr std::string_view String_Ltl = "Gamepad Left Thumbstick Left";
		static constexpr std::string_view String_Ltr = "Gamepad Left Thumbstick Right";
		static constexpr std::string_view String_Rtu = "Gamepad Right Thumbstick Up";
		static constexpr std::string_view String_Rtd = "Gamepad Right Thumbstick Down";
		static constexpr std::string_view String_Rtl = "Gamepad Right Thumbstick Left";
		static constexpr std::string_view String_Rtr = "Gamepad Right Thumbstick Right";
		static constexpr std::string_view String_Dpu = "Gamepad D-pad Up";
		static constexpr std::string_view String_Dpd = "Gamepad D-pad Down";
		static constexpr std::string_view String_Dpl = "Gamepad D-pad Left";
		static constexpr std::string_view String_Dpr = "Gamepad D-pad Right";
		static constexpr std::string_view String_Sl = "Gamepad Special Left";
		static constexpr std::string_view String_Sr = "Gamepad Special Right";

		static constexpr size_t Ls = MyCompileTimeHash(String_Ls);
		static constexpr size_t Rs = MyCompileTimeHash(String_Rs);
		static constexpr size_t Lt = MyCompileTimeHash(String_Lt);
		static constexpr size_t Rt = MyCompileTimeHash(String_Rt);
		static constexpr size_t Fbt = MyCompileTimeHash(String_Fbt);
		static constexpr size_t Fbb = MyCompileTimeHash(String_Fbb);
		static constexpr size_t Fbl = MyCompileTimeHash(String_Fbl);
		static constexpr size_t Fbr = MyCompileTimeHash(String_Fbr);
		static constexpr size_t Ltu = MyCompileTimeHash(String_Ltu);
		static constexpr size_t Ltd = MyCompileTimeHash(String_Ltd);
		static constexpr size_t Ltl = MyCompileTimeHash(String_Ltl);
		static constexpr size_t Ltr = MyCompileTimeHash(String_Ltr);
		static constexpr size_t Rtu = MyCompileTimeHash(String_Rtu);
		static constexpr size_t Rtd = MyCompileTimeHash(String_Rtd);
		static constexpr size_t Rtl = MyCompileTimeHash(String_Rtl);
		static constexpr size_t Rtr = MyCompileTimeHash(String_Rtr);
		static constexpr size_t Dpu = MyCompileTimeHash(String_Dpu);
		static constexpr size_t Dpd = MyCompileTimeHash(String_Dpd);
		static constexpr size_t Dpl = MyCompileTimeHash(String_Dpl);
		static constexpr size_t Dpr = MyCompileTimeHash(String_Dpr);
		static constexpr size_t Sl = MyCompileTimeHash(String_Sl);
		static constexpr size_t Sr = MyCompileTimeHash(String_Sr);

		// Hashing the argument
		const TCHAR* Ptr = GetData(GamepadKeyString);
		const std::string_view Input = StringCast<ANSICHAR>(Ptr).Get();

		switch (MyCompileTimeHash(Input))
		{
		case Ls: return EMyGamepadKey::left_shoulder;
		case Rs: return EMyGamepadKey::right_shoulder;
		case Lt: return EMyGamepadKey::left_trigger;
		case Rt: return EMyGamepadKey::right_trigger;
		case Fbt: return EMyGamepadKey::face_button_top;
		case Fbb: return EMyGamepadKey::face_button_bottom;
		case Fbl: return EMyGamepadKey::face_button_left;
		case Fbr: return EMyGamepadKey::face_button_right;
		case Ltu: return EMyGamepadKey::left_thumbstick_up;
		case Ltd: return EMyGamepadKey::left_thumbstick_down;
		case Ltl: return EMyGamepadKey::left_thumbstick_left;
		case Ltr: return EMyGamepadKey::left_thumbstick_right;
		case Rtu: return EMyGamepadKey::right_thumbstick_up;
		case Rtd: return EMyGamepadKey::right_thumbstick_down;
		case Rtl: return EMyGamepadKey::right_thumbstick_left;
		case Rtr: return EMyGamepadKey::right_thumbstick_right;
		case Dpu: return EMyGamepadKey::data_pad_up;
		case Dpd: return EMyGamepadKey::data_pad_down;
		case Dpl: return EMyGamepadKey::data_pad_left;
		case Dpr: return EMyGamepadKey::data_pad_right;
		case Sl: return EMyGamepadKey::special_left;
		case Sr: return EMyGamepadKey::special_right;

		default:
			return EMyGamepadKey::invalid;
		}
	}
};

Sorry for the long code - just wanted to be exact. And maybe it’s useful to others as well. :slight_smile:

And yes, the string is fragile - but it would probably be okay if I at least use the TEXT() macro?

Yea you can use existing enums for this. look at the EKeys.

EKeys::Gamepad_RightX
EKeys::Gamepad_RightY
EKeys::Gamepad_RightStick_Right
EKeys::Gamepad_RightStick_Left
EKeys::Gamepad_RightStick_Up
EKeys::Gamepad_RightStick_Down
EKeys::Gamepad_FaceButton_Right

You can directly compare “==” FKey to EKey.

I actually use FString very rarely (working with INI files, debugging).
Best advice I can give is don’t make “references” to things by string (don’t hardcode paths, don’t convert between a type to string or string to type if you can avoid it etc.). Your classes UMyGamepadKeyConverter and EMyGamepadKey can be deleted in favor of systems already present in the engine.

TEXT macro is unrelated to all this but has other uses (encoding).

String Handling | Unreal Engine 4.27 Documentation

Character Encoding | Unreal Engine 4.27 Documentation

Another thing that will help you with these cases, get Agent Ransack to search through engine source code. You’ll search on keywords like enumerator values and quickly find implementations like I showed.

1 Like

Oh my, am I probably in for a treat then? :smiley:

I just spent the last week programming an in-game console, where commands may be interpreted. A bit like this:

"interpreter.addInts 3 5"

Which then maps to a function, a bit like this:

class Interpreter {
    void AddInts(int a, int b) {}
}

Any input is entered in a TextBox widget, so the code gets an FText object, which I then convert to FString and store that by value during the interpreting.
Then I create an std::wstringstream, because my hunch is that converting from TCHAR to WCHAR seems safer than using ASCII or UTF-something (I don’t even know)…

And I only use TCHAR_TO_WCHAR() and WCHAR_TO_TCHAR() macros - I assume they are safe and platform-independent?

The string stream isn’t absolutely necessary, but very practical for std::wstringstream >> std::wstring operations.

It “IS WORKING”, but only tested on Windows. And I’m quite new to character encodings - first time I have used wchar. I have actually read the two pages you link to already, but didn’t catch on everything (probably takes some practice getting used to).

I really don’t mind deleting code and getting better features, so thank you for the suggestions!

1 Like

I like this, I’ve been playing Bitburner and Hacknet for some time now. I’m not familiar with other platforms than Windows, or processing other languages than english input, but if you can avoid conversions then avoid it.

I don’t see what you need this for?

Post a new questions on the forums to keep the topics separated. :slight_smile: tag me there.

1 Like

Absolutely, this also went a bit off-topic. :slight_smile:

But it does come full circle, because I was browsing here in the search for how to direct proper inputs for the console I’m building. This is a screenshot of the widget:

There is a bit of background blur, but otherwise the only purpose of this widget is to create an overlay with a text box, and another text box to display the results of executing commands. But as the TextBox widget captures and consumes all input (also arrow up/down), and I ALWAYS WANT it to be focused, then I needed a “hack” to get the arrow up/down keys somewhere else and use these to browse through the command history.

Then I still have text copy/paste, hold shift to select, arrow left/right, enter to process, etc.

The string parsing questions were related to the logic behind (which seems stable enough).
And in the C++ code for the base class of the widget:

FReply UGF_ConsoleWidgetBase::NativeOnPreviewKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
{
	FKey key = InKeyEvent.GetKey();
	if (key == EKeys::Up)
	{
		const FString& previous = GetPreviousCommand();
		mEditableTextBox->SetText(FText::FromString(previous));
		return FReply::Unhandled();
	}
	if (key == EKeys::Down)
	{
		const FString& next = GetNextCommand();
		mEditableTextBox->SetText(FText::FromString(next));
		return FReply::Unhandled();
	}

	return Super::NativeOnPreviewKeyDown(InGeometry, InKeyEvent);
}

Commands are stored internally in a TDoubleLinkedList<FString> object, so I retrieve temporarily here FString& to avoid copying it (and allocating!).

Thanks to your help I was able to clear up the input processing a bit.


Fortunately, I need only to care about English input. Otherwise I would need to localize all commands. But someone else might need it. From what I gather, FText is the intended type for this, but I think that converting to widechar is still safe.

It’s a bit like this (roughly speaking):

FString inputString;
std::wstringstream ss = TCHAR_TO_WCHAR(ToCStr(intputString));
std::wstring word;
ss >> word;
while (!word.empty) {
    // process the word in some way
    ss >> word;
}

I hope it makes sense. At least, it’s easier (and probably faster) than using the FString::ParseIntoArray() member function.

I will, if something else comes up. Thank you, for all your help!

ha :slight_smile: I see all questions are solved now?

I have some thoughts I wanted to share.

FText is used for visual output. FText can be gathered for localization and linked to a key. Never tested it to run “opposite” from say chinese to english for command processing but you might be onto something. I’d just go with english, CLI is always in english afaik?

You are conflicting with the default focusing system and with Slate its navigation input here (up down, right left, tab, shift tab, enter, esc etc.). Widgets do have a delegate for when they lose focus, so you could let the HUD say “when this widget loses focus, attempt to focus it again!” which is still hacky but much less than the current approach, because you still have the opportunity to change focus to another button (close game for example). You might still run into the original problem with the original text input widget handling input differently than you want then.

I never use std, UE has built in libs. For example you can do:

const FString YourString = "SomeText";
for (const TCHAR& CharX : YourString.GetCharArray()) {
	if (FChar::IsAlpha(CharX)
        // Process a b c d e 
	}
    else if (FChar::IsDigit(CharX)) {
        // Process 1 2 3 4
    }
}

You could split up the string used as input in the textbox by whitespace using FString methods into a new string array, then parse that array as arguments for your custom console.

A list? Aren’t they mapped directly to methods or to classes? I would probably create utility classes mapped to the first word in the command string. Something like:

TMap<FString, UCommandUtility*> Utilities;
Utilities.Add(TEXT("Screenshot"), NewObject<UScreenshotUtility>(this));
const FString MyCommand = TEXT("Screenshot -x 1920 -y 1080");


// ProcessCommand(MyCommand):
  // Get first argument of string split by whitespace.
  // Find utility in utility map by using first argument as key
  // Pass other arguments to utility.

You don’t need to optimize this, the engine is a million times heavy itself for a console program.

1 Like

Yeah this is what I meant, sorry! Each command has a class member function. The linked list stores the command history, ie. what the user entered so they can go to a previous command, change parameters, and execute again…

I understand. I will think about it. Thanks!

I think so too. Doesn’t make much sense to use other languages than English. Still, I have to be careful with the UTF-16. I assume TCHAR_TO_WCHAR takes care of that for me, because the macros are generated depending on platform… I guess I will know when we start playtesting on various platforms.

And yes, all problems solved for me at least. Probably then also for anyone else who ends up in here. :slight_smile: