Editable text: how to get the current caret position?

Hi,

Do you know whether threre is any reasonable way of getting the current caret position when positioned in an Editable Text? I mean, which character is it next to. It would be fine if there’s a way to do it that works for either the UEditableText (UMG) or the SEditableText (Slate), since the first one contains the second one. I’ve checked the code of both Editable Text and Editable Text Box classes and they seem to delegate the task of moving the caret position to the Layout, which on its turn delegates it depeer and depeer (the mentioned classes do not hold any variable about the current text position). That’s why it seems difficult to find a way to get the caret position.

This question has been asked a couple of times before in the recent years by other people with no response. If it’s not currently possible to do this, think of this as a feature request to make it available in future versions of the engine if you think it’s interesting enough.

Thank you.

It seems that there would be an actual way to do this in one line (with a UEditableTextBox* TextBox, and similarly for a UEditableText* Text):

ETextLocation CaretLocation = StaticCastSharedRef<SEditableTextBox>(TextBox->TakeWidget())->EditableText->EditableTextLayout->CursorInfo.GetCursorInteractionLocation();

But that won’t compile, since SEditableTextBox::EditableText, SEditableText::EditableTextLayout and SlateEditableTextLayout::CursorInfo are not public members, but private/protected without getter functions.

Epic, would you find it reasonable to add public getters for these values in a future update?

With the getter functions, it would be something like this (imagine that we are in a library C++ file, that you would expose to Blueprints if needed):

static ETextLocation GetCaretLocationForTextBox(UEditableTextBox& TextBox)
{

return StaticCastSharedRef<SEditableTextBox>(TextBox.TakeWidget())->GetEditableText()->GetEditableTextLayout()->GetCursorInfo().GetCursorInteractionLocation();
}

I think many people would find it useful, specially those working in virtual keyboards that need to work with editable text fields in code.

The workaround would be to subclass the three affected classes to add the needed getters and a UEditableTextBox subclass and manually set up the linking of the UMG text box widget with the Slate text box widget with the Slate text widget to get things working, which seems likely to lead to unexpected behavior or incompatibilities when the engine is updated and the parent classes get modified.

Hi SomeRandomDev,

Some extensive looking into the engine source uncovered an event, that fires whenever the caret is moved, providing details of the line and offset of your caret/selected text.

It’s a protected member of SEditableText

virtual void OnCursorMoved(const FTextLocation& InLocation) override;

Overriding this in your slate subclass and caching the value should do the trick.

Also with this cached value you can pass it into useful functions such as

EditableTextLayout->IsAtEndOfDocument(const FTextLocation& Location);
EditableTextLayout->IsAtBeginningOfDocument(const FTextLocation& Location);

I imagine you no longer need this information, but figured I’d put this here since there aren’t any answers to be found elsewhere, and this post came up first on my search.

Hope this helps anyone else that struggles with this :slight_smile:

2 Likes

I just implemented something similar, and I thought I would share it with anyone looking to do something like this.

This enables two functions in your widget BP, get cursor position AND insert text at the cursor.

Get cursor position in multiline editable text box

Create a new C++ class extending from UMultiLineEditableText

RM_UMultiLineEditableText.h:

#pragma once

#include "CoreMinimal.h"
#include "Components/EditableTextBox.h"
#include "Components/MultiLineEditableText.h"
#include "RM_MultiLineEditableText.generated.h"

/** Called when the cursor is moved within the text area */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnCursorMovedRM, int32, InLineIndex, int32, InOffset);

/**
 * Editable text box widget
 */
UCLASS(meta=( DisplayName="Editable Text (Multi-Line) RM" ))
class TRANSPORTATIONTABLE_API URM_MultiLineEditableText : public UMultiLineEditableText
{
	/** Called when the cursor is moved within the text area */
	DECLARE_DELEGATE_OneParam( FOnCursorMoved, const FTextLocation& );
	
	GENERATED_BODY()

	/** Called when the cursor is moved within the text area */
	UPROPERTY(BlueprintAssignable)
	FOnCursorMovedRM OnCursorMoved;
	
	virtual TSharedRef<SWidget> RebuildWidget() override;

public:
	void HandleEditableTextCursorMoved(const FTextLocation& NewCursorPosition ) const;

	UFUNCTION(BlueprintCallable)
	void InsertTextAtCursor(FString InText) const;
};

RM_MultiLineEditableText.cpp:

#include "Widgets/Text/SMultiLineEditableText.h"
#include "UI/RM_MultiLineEditableText.h"

TSharedRef<SWidget> URM_MultiLineEditableText::RebuildWidget()
{
	MyMultiLineEditableText = SNew(SMultiLineEditableText)
	.TextStyle(&WidgetStyle)
	.AllowContextMenu(AllowContextMenu)
	.IsReadOnly(bIsReadOnly)
	.OnCursorMoved(BIND_UOBJECT_DELEGATE(FOnCursorMoved, HandleEditableTextCursorMoved))
	.SelectAllTextWhenFocused(SelectAllTextWhenFocused)
	.ClearTextSelectionOnFocusLoss(ClearTextSelectionOnFocusLoss)
	.RevertTextOnEscape(RevertTextOnEscape)
	.ClearKeyboardFocusOnCommit(ClearKeyboardFocusOnCommit)
	.VirtualKeyboardOptions(VirtualKeyboardOptions)
	.VirtualKeyboardDismissAction(VirtualKeyboardDismissAction)
	.OnTextChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnTextChanged))
	.OnTextCommitted(BIND_UOBJECT_DELEGATE(FOnTextCommitted, HandleOnTextCommitted))
	;

	return MyMultiLineEditableText.ToSharedRef();
}

void URM_MultiLineEditableText::HandleEditableTextCursorMoved(const FTextLocation& NewCursorPosition) const
{
	OnCursorMoved.Broadcast(NewCursorPosition.GetLineIndex(), NewCursorPosition.GetOffset());
}

void URM_MultiLineEditableText::InsertTextAtCursor(const FString InText) const
{
	MyMultiLineEditableText->InsertTextAtCursor(InText);
}

Hey, your code works perfectly, I also managed to add the GetSelectedText() function thanks to it but I also want to add the GoTo() function but everytime I build it gives me this error:

Projects\GetSelectedText\Plugins\CommonCPPFunctions\Source\CommonCPPFunctions\Public\MDMultiLineEditableText.h(50): Error: Unable to find 'class', 'delegate', 'enum', or 'struct' with name 'FTextLocation'

.h

UFUNCTION(BlueprintCallable)
	void GoTo(const FTextLocation& NewLocation);

.cpp

void UMDMultiLineEditableText::GoTo(const FTextLocation& NewLocation)
{
	MyMultiLineEditableText->GoTo(NewLocation);
}

I don’t get the error when I remove the UFUNCTION() part. Thanks for you help

An old thread (but the only one with this information I could find).
Anyway, this was how I resolved setting the carets’ position within BP using @Monsieur_Dupond’s code.

In testing the widget loses focus after changing the caret location, so using a Set Keyboard Focus node on the widget in BP resolved this.

Also, since 5.1 some of @kevinraz’s code is deprecated, so here’s the full section replace:

MyMultiLineEditableText = SNew(SMultiLineEditableText)
.TextStyle(&WidgetStyle)
.AllowContextMenu(AllowContextMenu)
.IsReadOnly(GetIsReadOnly())
.OnCursorMoved(BIND_UOBJECT_DELEGATE(FOnCursorMoved, HandleEditableTextCursorMoved))
.SelectAllTextWhenFocused(GetSelectAllTextWhenFocused())
.ClearTextSelectionOnFocusLoss(GetClearTextSelectionOnFocusLoss())
.RevertTextOnEscape(GetRevertTextOnEscape())
.ClearKeyboardFocusOnCommit(GetClearKeyboardFocusOnCommit())
.VirtualKeyboardOptions(VirtualKeyboardOptions)
.VirtualKeyboardDismissAction(VirtualKeyboardDismissAction)
.OnTextChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnTextChanged))
.OnTextCommitted(BIND_UOBJECT_DELEGATE(FOnTextCommitted, HandleOnTextCommitted))
;

.cpp

//Monsieur_Dupond
void UMDMultiLineEditableText::GoTo(const FTextLocation& NewLocation)
{
MyMultiLineEditableText->GoTo(NewLocation);
}

//Only way to use BP to then call the above function to make it switch to the new location.
void UMDMultiLineEditableText::NewCursorLocation(int32 LineIndex, int32 LineOffset)
{
MyMultiLineEditableText->GoTo(FTextLocation(LineIndex,LineOffset));
}

.h

//Monsieur_Dupond
void GoTo(const FTextLocation& NewLocation);

UFUNCTION(BlueprintCallable)
void NewCursorLocation(int32 LineIndex, int32 LineOffset);

I do not claim to be a C++ expert, but the above worked for me :slight_smile: