
⇒ GPT는 선택된 위젯의 이름을 키 값으로 사용하는 방식을 추천했다.

초기에는 GPT가 제안한 방식으로 구현을 시도했다. 그러나 Widget Hierarchy는 Engine의 UMG 모듈에, MVVM Binding Panel은 MVVM Plugin에 구현되어 있어 두 시스템을 직접 연결하는 방식은 불필요한 결합도를 만들 수 있다는 것을 확인했다.
엔진 동작을 확인하던 중, Widget Hierarchy에서 위젯을 선택하면 MVVM 패널에서 해당 위젯 이름이 표시되는 흐름이 이미 존재한다는 것을 확인했다.

해당 MVVM Panel이 구현되어 있는 클래스는 SMVVMViewModelPanel 클래스이다.
void SMVVMViewModelPanel::Construct(const FArguments& InArgs, TSharedPtr<FWidgetBlueprintEditor> WidgetBlueprintEditor)
{
...
WidgetBlueprintEditor->OnSelectedWidgetsChanging.AddSP(this, &SMVVMViewModelPanel::HandleEditorSelectionChanged);
...
}
SMVVMViewModelPanel 클래스가 생성될 때, FWidgetBlueprintEditor의 OnSelectedWidgetsChanging 델리게이트에 콜백이 바인딩된다. 즉, Widget Hierarchy에서 위젯 선택이 변경되면 해당 델리게이트가 Broadcast 된다.
⇒ 해당 델리게이트를 사용해서 선택될 때 MVVM GroupRow가 선택 표시되도록 할 것 이다.
R&D 결과, GroupRow/BindingRow는 SBindingsList::GenerateEntryRow() 에서 생성된다.
TSharedRef<ITableRow> SBindingsList::GenerateEntryRow(TSharedPtr<FBindingEntry> Entry, const TSharedRef<STableViewBase>& OwnerTable)
{
...
switch (Entry->GetRowType())
{
case FBindingEntry::ERowType::Group:
{
Row = SNew(UE::MVVM::BindingEntry::SGroupRow, this, ...);
break;
}
case FBindingEntry::ERowType::Binding:
{
Row = SNew(UE::MVVM::BindingEntry::SBindingRow, this, ...);
break;
}
...
여기서
각 위젯마다 하나의 GroupEntry(ERowType::Group) 가 생성된다.
처음에는 Row 수준에서 이벤트를 처리하는 것을 고려했다.
하지만 SBindingsList는 MVVM 패널 당 하나의 TreeView로 생성되는 구조이기 때문에, 외부 Selection 이벤트를 Row 단위에서 처리하는 것은 적절하지 않았다.
따라서 MVVM 패널의 최상위 위젯인 SBindingsPanel에서 이벤트를 수신하도록 구조를 설계했다.
void SBindingsPanel::Construct(const FArguments& InArgs, TSharedPtr<FWidgetBlueprintEditor> WidgetBlueprintEditor, bool bInIsDrawerTab)
{
...
WidgetBlueprintEditor->OnSelectedWidgetsChanged.AddSP(this, &SBindingsPanel::HandleEditorSelectionChanged);
...
}
SBindingsPanel 은 MVVM 패널의 최상위 위젯으로, 외부(UI Hierarchy)에서 발생하는 위젯 선택 이벤트를 수신하기에 가장 적합한 위치다. SBindingsPanel 생성 시 FWidgetBlueprintEditor의 OnSelectedWidgetsChanged 델리게이트에 콜백을 바인딩한다.
⇒ 이를 통해 Widget Hierarchy에서 선택이 변경될 때마다 MVVM 패널에서 해당 이벤트를 감지할 수 있다.
해당 콜백함수에서는 BindingsList::SelectGroupByWidgetName 함수를 호출하여 선택함 위젯의 이름을 전달한다.
void SBindingsPanel::HandleEditorSelectionChanged()
{
TSharedPtr<FWidgetBlueprintEditor> Editor = WeakBlueprintEditor.Pin();
if (nullptr == Editor.Get())
{
return;
}
TSet<FWidgetReference> Selected = Editor->GetSelectedWidgets();
if (Selected.Num() == 1)
{
const FWidgetReference& Ref = *Selected.CreateConstIterator();
const FName WidgetName = Ref.GetPreview()->GetFName();
BindingsList->SelectGroupByWidgetName(WidgetName);
}
}
여기서는
- 현재 선택된 위젯 목록을 가져온다.
- 단일 위젯 선택인 경우에만 처리한다.
- 선택된 위젯 이름을 SBindingsList에 전달한다.
void SBindingsList::SelectGroupByWidgetName(FName InWidgetName)
{
if (InWidgetName.IsNone())
{
return;
}
// 1. 모든 GroupEntry 순회
for (const TSharedPtr<FBindingEntry>& Entry : AllRootGroups)
{
if (false == Entry.IsValid())
{
continue;
}
if (Entry->GetRowType() != FBindingEntry::ERowType::Group)
{
continue;
}
// 2. 이 Group이 대표하는 WidgetName 얻기
FName GroupWidgetName = Entry->GetGroupName();
// 3. 이름이 같으면 선택 + 스크롤
if (GroupWidgetName == InWidgetName)
{
TreeView->SetSelection(Entry, ESelectInfo::Direct);
TreeView->RequestScrollIntoView(Entry);
TreeView->SetItemExpansion(Entry, true);
return;
}
}
}
SBindingsList는 MVVM 바인딩 목록(TreeView)을 직접 관리하는 컴포넌트다. 모든 최상위 GroupEntry(ERowType::Group)를 순회하며 각 Group이 대표하는 위젯 이름과 Hierarchy에서 선택된 위젯 이름을 비교한다.
일치하는 GroupEntry를 찾으면
SetSelection을 통해 선택 상태를 갱신하고RequestScrollIntoView로 화면에 노출하며이때 ESelectInfo::Direct를 사용하여 선택 동기화로 인한 이벤트 루프를 방지한다.
구조는 다음과 같이 구성된다.
결과적으로 다음과 같은 흐름을 구현했다.
Widget Hierarchy 선택
↓
Editor Selection Event
↓
SBindingPanel
↓
SBindingList
↓
MVVM GroupRow 자동 선택 + 스크롤
이를 통해 Widget Hierarchy에서 위젯을 선택하면 MVVM Binding Panel에서도 해당 위젯의 GroupRow가 자동으로 선택되도록 구현할 수 있었다.