동작 흐름
- MVVM Binding Panel에서 특정 Row를 클릭하면,
- 해당 Row가 가리키는 WidgetName을 계산하고,
- Widget Hierarchy에서 동일 위젯이 Selected 상태로 표시되도록 동기화된다.
MVVM Binding Panel에서 Row를 클릭하면 SBindingsList::OnSourceListSelectionChanged가 호출된다.
void SBindingsList::OnSourceListSelectionChanged(TSharedPtr<FBindingEntry> Entry, ESelectInfo::Type SelectionType) const
이 함수는 Binding Panel의 선택 상태가 변경될 때 실행되는 핵심 콜백이며
선택된 Binding 정보를 SBindingsPanel로 전달하는 역할을 한다.
내부에서는 현재 선택된 항목을 순회하면서 RowType에 따라 다른 데이터를 생성한다.
FBindingEntry::ERowType에 따라 처리 방식이 달라진다.
| RowType | 동작 |
|---|---|
| Group | 위젯 단위 그룹 Row |
| Binding | 실제 ViewBinding |
| Condition | Binding 조건 |
| Event | ViewModel 이벤트 |
예를 들어,
ERowType::Group 를 클릭하는 경우에는 case문을 타지 않고 SelectionVariants 생성 없이 바로 아래 로직으로 진행(혹은 사실상 아무것도 안 하고) 된다.ERowType::Binding 를 클릭하는 경우에는 Entry->GetBinding(View)를 통해 FMVVMBlueprintViewBinding*을 얻어 SelectionVariants에 담아 SBindingsPanel로 전달한다.기존에 있던 선택 함수 내에서 구현을 하게 되면 아래와 같은 조건에서 구현이 실행되어야 한다.
Entry->GetGroupName()SelectedBinding->DestinationPath.GetWidgetName()구현 코드는 다음과 같다.
...
if (SelectedEntries.Num() <= 1)
{
FName WidgetName = NAME_None;
if (Entry->GetRowType() == FBindingEntry::ERowType::Group)
{
WidgetName = Entry->GetGroupName();
}
else if (Entry->GetRowType() == FBindingEntry::ERowType::Binding)
{
if (FMVVMBlueprintViewBinding* SelectedBinding = Entry->GetBinding(View))
{
WidgetName = SelectedBinding->DestinationPath.GetWidgetName();
}
}
if(false == WidgetName.IsNone())
{
SelectWidgetInHierarchy(WidgetName);
}
}
...
이 로직을 통해 Binding Panel에서 선택된 Row가 어떤 위젯과 연결되어 있는지 계산할 수 있다.
SelectWidgetInHierarchy 함수는
WidgetName을 기반으로 Widget Hierarchy 선택을 변경한다.
FWidgetReference 변환한 뒤FWidgetBlueprintEditor::SelectWidgets로 Hierarchy 선택을 갱신한다.void SBindingsList::SelectWidgetInHierarchy(FName InWidgetName) const
{
TSharedPtr<FWidgetBlueprintEditor> Editor = WeakBlueprintEditor.Pin();
if (false == Editor.IsValid() || InWidgetName.IsNone())
{
return;
}
UWidgetBlueprint* BP = Editor->GetWidgetBlueprintObj();
if (nullptr == BP || nullptr == BP->WidgetTree)
{
return;
}
// 1) 이름으로 위젯 템플릿 찾기
UWidget* Template = BP->WidgetTree->FindWidget(InWidgetName);
if (nullptr == Template)
{
UE_LOG(LogTemp, Warning, TEXT("FindWidget failed: %s"), *InWidgetName.ToString());
return;
}
// 2) Template -> FWidgetReference 만들기
TSet<FWidgetReference> ToSelect;
auto MakeWidgetReferenceSet =
[](const TSharedPtr<FWidgetBlueprintEditor>& InEditor, UWidget* InTemplate, TSet<FWidgetReference>& OutSet) -> bool
{
if (false == InEditor.IsValid() || nullptr == InTemplate)
{
return false;
}
FWidgetReference ref = InEditor->GetReferenceFromTemplate(InTemplate);
if (false == ref.IsValid())
{
return false;
}
OutSet.Add(ref);
return true;
};
if (false == MakeWidgetReferenceSet(Editor, Template, ToSelect))
{
UE_LOG(LogTemp, Warning, TEXT("MakeWidgetReferenceSet failed: %s"), *InWidgetName.ToString());
return;
}
// 3) 하이라키 선택 반영
Editor->SelectWidgets(ToSelect, false);
}
FWidgetReference는 직접 생성하거나 수정할 수 없다.FWidgetReference는 FWidgetBlueprintEditor와 friend 관계로 설계되어 있기 때문에 Editor를 통해서만 유효한 Reference를 생성할 수 있다.따라서
Editor->GetReferenceFromTemplate(Template);
을 사용해 안정적으로 reference를 생성해야 한다.
이 구현을 통해 다음과 같은 양방향 동기화가 가능해졌다.
Widget Hierarchy 선택
↓
MVVM Binding Panel Row 선택
MVVM Binding Panel Row 선택
↓
Widget Hierarchy 위젯 선택
Hierarchy ↔ Binding Panel 사이의 선택 상태가 서로 동기화되는 Editor UX를 구현할 수 있었다.