[언리얼 MVVM 바인딩 필드 Select 처리] 1. MVVM Panel–Widget Hierarchy 연동을 위한 사전 분석 및 R&D

KWONYEONGMIN·2026년 3월 13일

언리얼

목록 보기
12/15

개요

회사의 현재 프로젝트에서 UI 작업자와의 협업 환경 조성을 위해 MVVM 기반 UI 구조를 적극적으로 활용하고 있다. MVVM 시스템을 통해 View와 ViewModel 간의 데이터 바인딩을 관리하고 있었지만, 실제 작업 과정에서 Editor 사용성 측면에서 아쉬운 점이 몇가지 존재했다.

특히 Widget Hierarchy와 MVVM Binding Panel 사이의 연동이 부족해 다음과 같은 불편함이 발생했다.

  • Widget Hierarchy에서 특정 위젯을 확인했을 때
    → 해당 위젯의 MVVM Binding 정보를 바로 파악하기 어려움

  • MVVM Binding Panel에서 바인딩을 확인할 때
    → 어떤 위젯에 대한 바인딩인지 Hierarchy와 직관적으로 연결되지 않음

즉, Hierarchy와 Binding Panel 사이의 탐색 흐름이 끊어져 있었고,
이로 인해 특정 위젯의 바인딩을 확인하기 위해서는 패널을 직접 탐색해야 하는 비효율적인 작업 과정이 발생했다.

이러한 문제를 해결하기 위해 다음과 같은 기능을 구현하기로 했다.

  • Widget Hierarchy에서 특정 위젯의을 클릭하면 MVVM Binding Panel에서 해당 위젯의 Group Row가 자동으로 Highlight 되도록 하는 기능
  • MVVM Binding Panel의 Row를 클릭하면 Widget Hierarchy가 Highlight 되도록 하는 기능

이를 구현하기 위해서는 단순한 UI 수정이 아니라 다음과 같은 내부 구조를 이해할 필요가 있었다.

  • Widget Hierarchy View 구조
  • MVVM Binding Panel(SBindingsList / STreeView) 구조
  • Slate ListView / TreeView의 Highlight 처리 방식
  • WidgetBlueprintEditor 내부 이벤트 흐름

따라서 실제 구현에 앞서 Unreal Engine MVVM Editor 구조 분석과 R&D를 먼저 진행했다.

MVVM 필드

연관 클래스 관계도


Row 클릭 이벤트 분석

  • MVVM Row
  • Widget Hierarchy 둘다 적용됨

STableRow의 virtual FReply OnMouseButtonDown

virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override
{
	TSharedRef< ITypedTableView<ItemType> > OwnerTable = OwnerTablePtr.Pin().ToSharedRef();
	bChangedSelectionOnMouseDown = false;
	bDragWasDetected = false;

	if ( MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton )
	{
		const ESelectionMode::Type SelectionMode = GetSelectionMode();
		if (SelectionMode != ESelectionMode::None)
		{
			if (const TObjectPtrWrapTypeOf<ItemType>* MyItemPtr = GetItemForThis(OwnerTable))
			{
				...

				return FReply::Handled()
					.DetectDrag(SharedThis(this), EKeys::LeftMouseButton)
					.SetUserFocus(OwnerTable->AsWidget(), EFocusCause::Mouse)
					.CaptureMouse(SharedThis(this));
			}
		}
	}

	return FReply::Unhandled();
}

MVVM Row 분석

TSharedRef<ITableRow> SBindingsList::GenerateEntryRow(TSharedPtr<FBindingEntry> Entry, const TSharedRef<STableViewBase>& OwnerTable)
{
	TSharedPtr<ITableRow> Row;

	if (UMVVMWidgetBlueprintExtension_View* MVVMExtensionPtr = MVVMExtension.Get())
	{
		switch (Entry->GetRowType())
		{
			case FBindingEntry::ERowType::Group:
			{
				Row = SNew(UE::MVVM::BindingEntry::SGroupRow, this, OwnerTable, WeakBlueprintEditor.Pin(), MVVMExtensionPtr->GetWidgetBlueprint(), Entry);
				break;
			}
			case FBindingEntry::ERowType::Binding:
			{
				Row = SNew(UE::MVVM::BindingEntry::SBindingRow, this, OwnerTable, WeakBlueprintEditor.Pin(), MVVMExtensionPtr->GetWidgetBlueprint(), Entry);
				break;
			}

색 변경 테스트

Group Row를 선택 시 빨간색으로 변경

void SGroupRow::Construct(const FArguments& Args, SBindingsList* InBindingsList, const TSharedRef<STableViewBase>& OwnerTableView, const TSharedPtr<FWidgetBlueprintEditor>& InBlueprintEditor, UWidgetBlueprint* InBlueprint, const TSharedPtr<FBindingEntry>& InEntry)
{
	SBaseRow::Construct(SBaseRow::FArguments(), InBindingsList, OwnerTableView, InBlueprintEditor, InBlueprint, InEntry);

	OwnerList = InBindingsList;
	WeakEntry = InEntry;

	TSharedPtr<SWidget> ChildContent = ChildSlot.DetachWidget();
	ChildSlot
		[
			SNew(SBorder)
				.BorderImage(FAppStyle::GetBrush("Brushes.Recessed"))
				.BorderBackgroundColor(this, &SGroupRow::GetContentBgColor)
				[
					ChildContent.ToSharedRef()
				]
		];
}

FSlateColor SGroupRow::GetContentBgColor() const
{
	const TSharedPtr<FBindingEntry> Entry = WeakEntry.Pin();
	if (!OwnerList || !Entry)
	{
		return FLinearColor::Transparent;
	}

	return IsSelected() ? FLinearColor::Red : FLinearColor::Transparent;
}



Binding을 선택시 빨간색으로 변경

void SBindingRow::Construct(const FArguments& Args, SBindingsList* InBindingsList, const TSharedRef<STableViewBase>& OwnerTableView, const TSharedPtr<FWidgetBlueprintEditor>& InBlueprintEditor, UWidgetBlueprint* InBlueprint, const TSharedPtr<FBindingEntry>& InEntry)
{
	static IConsoleVariable* CVarDefaultExecutionMode = IConsoleManager::Get().FindConsoleVariable(TEXT("MVVM.DefaultExecutionMode"));
	ensure(CVarDefaultExecutionMode);
	DefaultExecutionMode = CVarDefaultExecutionMode ? (EMVVMExecutionMode)CVarDefaultExecutionMode->GetInt() : EMVVMExecutionMode::DelayedWhenSharedElseImmediate;

	SBaseRow::Construct(SBaseRow::FArguments(), InBindingsList, OwnerTableView, InBlueprintEditor, InBlueprint, InEntry);

	OwnerList = InBindingsList;
	WeakEntry = InEntry;

	TSharedPtr<SWidget> ChildContent = ChildSlot.DetachWidget();
	ChildSlot
		[
			SNew(SBorder)
				.BorderImage(FAppStyle::GetBrush("WhiteBrush"))
				.BorderBackgroundColor(this, &SBindingRow::GetContentBgColor)
				[
					ChildContent.ToSharedRef()
				]
		];
}
FSlateColor SBindingRow::GetContentBgColor() const
{
	const TSharedPtr<FBindingEntry> Entry = WeakEntry.Pin();
	if (!OwnerList || !Entry)
	{
		return FLinearColor::Transparent;
	}

	return IsSelected() ? FLinearColor::Red : FLinearColor::Transparent;
}

위젯 하이라키는 어떤 클래스에서 구현되고 있는가 ?

Widget Hierarchy에서 특정 위젯을 클릭했을 때 어떤 위젯이 선택되었는지 확인하고 해당 위젯 정보를 얻어오는 과정을 먼저 확인할 필요가 있었다.
위젯 Hierarchy의 동작은 SHierarchyView 클래스에서 구현되어 있으며, 위젯 선택 이벤트는 다음 함수에서 처리된다.

void SHierarchyView::WidgetHierarchy_OnSelectionChanged(TSharedPtr<FHierarchyModel> SelectedItem, ESelectInfo::Type SelectInfo)
{
	if ( SelectInfo != ESelectInfo::Direct )
	{
		bIsUpdatingSelection = true;

		TArray< TSharedPtr<FHierarchyModel> > SelectedItems = WidgetTreeView->GetSelectedItems();

		TSet<FWidgetReference> Clear;
		BlueprintEditor.Pin()->SelectWidgets(Clear, false);

		for ( TSharedPtr<FHierarchyModel>& Item : SelectedItems )
		{
			Item->OnSelection();
			UE_LOG(LogTemp, Log, TEXT("SelectedItemName : %s"), *Item->GetUniqueName().ToString());
		}

		if ( RootWidgets.Num() > 0 )
		{
			RootWidgets[0]->RefreshSelection();
		}

		BlueprintEditor.Pin()->PasteDropLocation = FVector2D(0, 0);

		bIsUpdatingSelection = false;
	}
}

Widget Hierarchy에서 위젯을 선택하면 WidgetHierarchy_OnSelectionChanged() 함수가 호출되며, 선택된 FHierarchyModel을 기반으로 현재 선택된 위젯 정보를 처리하는 흐름이 진행된다.

여기서 중요한 점은 다음과 같다.

  • Hierarchy에서 선택된 위젯은 FHierarchyModel 객체로 관리된다.
  • 실제 위젯 정보는 FHierarchyModel을 통해 접근할 수 있다.
  • Item->OnSelection() 호출을 통해 선택된 위젯의 Editor 상태가 갱신된다.
    특히 Item->GetUniqueName()을 통해 현재 선택된 위젯의 이름을 확인할 수 있다.

cpp UE_LOG(LogTemp, Log, TEXT("SelectedItemName : %s"), *Item->GetUniqueName().ToString());

이를 통해 Widget Hierarchy에서 선택된 위젯의 식별 키(Widget Name) 를 얻을 수 있으며, 이 키를 기반으로 MVVM Binding Panel과 연동하는 기능을 구현할 수 있다.

profile
Hello World

0개의 댓글