Unreal UMG MVVM

Unreal UMG MVVM

背景

  1. 先阅读文档和 quabqi 的 UOD 视频分享,目前网上唯一的资料
  2. 看眼成熟的巨硬方案 WPF MVVM
  3. 剩下的就硬扒代码

M - VM - V

  1. Model:原始数据
  2. ViewModel:视图所需数据
  3. View:控件蓝图 WBP

扩展点

三个 Extension,注意 MVVM 是一个插件,通过这种方式扩展 WBP

// editortime
class MODELVIEWVIEWMODELBLUEPRINT_API UMVVMWidgetBlueprintExtension_View : public UWidgetBlueprintExtension
// runtime
class MODELVIEWVIEWMODEL_API UMVVMView : public UUserWidgetExtension
class MODELVIEWVIEWMODEL_API UMVVMViewClass : public UWidgetBlueprintGeneratedClassExtension

新增一个 BP Compiler, MVVM 自动生成了一部分 BP 代码

FKismetCompilerContext::RegisterCompilerForBP(UMVVMViewModelBlueprint::StaticClass(), &UMVVMViewModelBlueprint::GetCompilerForViewModelBlueprint);

=== Editortime ===

有两个编辑器界面

在这里插入图片描述

WidgetBlueprint.UMVVMBlueprintView,这也是通过 WBP Extension UMVVMWidgetBlueprintExtension_View 挂上的

两个编辑器,一个添加 VM Class,一个添加 Binding,最终存储在 UMVVMBlueprintView

// UMVVMBlueprintView
UPROPERTY(EditAnywhere, Category = "MVVM")
TArray<FMVVMBlueprintViewBinding> Bindings;

UPROPERTY(EditAnywhere, Category = "MVVM")
TArray<FMVVMBlueprintViewModelContext> AvailableViewModels;

Viewmodels 编辑器界面

在这里插入图片描述

// Editor AddViewModel
if (UWidgetBlueprint* WidgetBlueprint = WidgetBlueprintEditor->GetWidgetBlueprintObj())
{
	UMVVMEditorSubsystem* EditorSubsystem = GEditor->GetEditorSubsystem<UMVVMEditorSubsystem>();
	check(EditorSubsystem);

	UMVVMBlueprintView* CurrentBlueprintView = WeakBlueprintView.Get();
	if (!CurrentBlueprintView)
	{
		CurrentBlueprintView = EditorSubsystem->RequestView(WidgetBlueprint);
		WeakBlueprintView = CurrentBlueprintView;
		ViewModelsUpdatedHandle = CurrentBlueprintView->OnViewModelsUpdated.AddSP(this, &SMVVMViewModelPanel::HandleViewModelsUpdated);
	}

	EditorSubsystem->AddViewModel(WidgetBlueprint, SelectedClass);
}

View Bindings 编辑器界面

在这里插入图片描述

蓝图编译相关

UMVVMBlueprintView 编辑时数据编译到 UMVVMViewClass 给运行时用

// FMVVMViewBlueprintCompiler::PreCompileBindingSources
UMVVMViewClass* ViewExtension = NewObject<UMVVMViewClass>(Class);
CurrentCompilerContext->Compile(Class, BlueprintView, ViewExtension);
CurrentCompilerContext->AddExtension(Class, ViewExtension);

// FMVVMViewBlueprintCompiler::Compile
CompileSourceCreators(CompileResult.GetValue(), Class, BlueprintView, ViewExtension);
CompileBindings(CompileResult.GetValue(), Class, BlueprintView, ViewExtension);
ViewExtension->BindingLibrary = MoveTemp(CompileResult.GetValue().Library);

=== Runtime ===

创建 ViewModel

四种模式,134 都是自动创建并关联,以默认的 Create Instance 为例

视图模型创建类型描述
自动创建小部件会自动创建自己的 Viewmodel 实例。
手动创建小部件初始化时 Viewmodel 为 null,您需要手动创建一个实例并分配它。
全局视图模型集合指的是一个全局可用的视图模型,可以被项目中的任何小部件使用。需要全局视图模型标识符。
属性路径在初始化时,执行一个函数来查找 Viewmodel。Viewmodel 属性路径使用由句点分隔的成员名称。

Widget::Init -> Init Extensions -> MVVMViewClass -> MVVMView

在这里插入图片描述

UMVVMViewClass 每个 WBP Class 一份,是编辑时存下的绑定数据
MVVMView 每个 WBP Instance 一份
UMVVMViewClass 拉起每个 WBP Instance 的 MVVMView

void UMVVMViewClass::Initialize(UUserWidget* UserWidget)
{
	ensure(UserWidget->GetExtension<UMVVMView>() == nullptr);
	UMVVMView* View = UserWidget->AddExtension<UMVVMView>();
	if (ensure(View))
	{
		if (!bLoaded)
		{
			BindingLibrary.Load();
			bLoaded = true;
		}

		View->ConstructView(this);
	}
}

CreateInstance,如前所说,会自动创建并关联 ViewModel 实例和 WBP 实例

/**
* Instance UMVVMClassExtension_View for the UUserWdiget
*/
UCLASS(Transient, DisplayName="MVVM View")
class MODELVIEWVIEWMODEL_API UMVVMView : public UUserWidgetExtension

void UMVVMView::Construct()
{
   // Init ViewModel instances
   for (const FMVVMViewClass_SourceCreator& Item : ClassExtension->GetViewModelCreators())
   {
      Item.CreateInstance(ClassExtension, this, GetUserWidget());
   }
   ...
}

UObject* FMVVMViewClass_SourceCreator::CreateInstance(const UMVVMViewClass* InViewClass, UMVVMView* InView, UUserWidget* InUserWidget) const
{
	UObject* Result = nullptr;

	FObjectPropertyBase* FoundObjectProperty = FindFProperty<FObjectPropertyBase>(InUserWidget->GetClass(), PropertyName);
	if (ensureAlwaysMsgf(FoundObjectProperty, TEXT("The compiler should have added the property")))
	{
		auto AssignProperty = [FoundObjectProperty, InUserWidget](UObject* NewObject)
		{
			check(NewObject);
			if (ensure(NewObject->GetClass()->IsChildOf(FoundObjectProperty->PropertyClass)))
			{
				FoundObjectProperty->SetObjectPropertyValue_InContainer(InUserWidget, NewObject);
			}
		};

		if (bCreateInstance)
		{
			//...
		}
		else if (GlobalViewModelInstance.IsValid())
		{
			//...
		}
		else if (FieldPath.IsValid())
		{
			//...
		}
	}

	return Result;
}

ViewModel 更新

C++ 封装 SetXXX 函数来调用 UE_MVVM_SET_PROPERTY_VALUE 宏

/** After a field value changed. Broadcast the event. */
#define UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(MemberName) \
	BroadcastFieldValueChanged(ThisClass::FFieldNotificationClassDescriptor::MemberName)

/** If the property value changed then set the new value and notify. */
#define UE_MVVM_SET_PROPERTY_VALUE(MemberName, NewValue) \
	SetPropertyValue(MemberName, NewValue, ThisClass::FFieldNotificationClassDescriptor::MemberName)

/** Set the new value and notify if the property value changed. */
template<typename T, typename U>
bool SetPropertyValue(T& Value, const U& NewValue, UE::FieldNotification::FFieldId FieldId)
{
	if (Value == NewValue)
	{
		return false;
	}

	Value = NewValue;
	BroadcastFieldValueChanged(FieldId);
	return true;
}

蓝图换掉 SetProperty 指令

DEFINE_FUNCTION(UMVVMViewModelBase::execK2_SetPropertyValue)
{
	UMVVMViewModelBase* ViewModelContext = Cast<UMVVMViewModelBase>(Context);

	bResult = TargetProperty->Identical(TargetValuePtr, SourceValuePtr);
	if (!bResult)
	{
		// Set the value then notify that the value changed.
		TargetProperty->SetValue_InContainer(ViewModelContext, SourceValuePtr);
		ViewModelContext->BroadcastFieldValueChanged(FieldId);
	}
}

总之都是调用 BroadcastFieldValueChanged,ViewModel 派生了 INotifyFieldValueChanged 来监听属性变化

但是其实就是封装了 Setter,每次变了就调用下 Delegate 派发事件

class MODELVIEWVIEWMODEL_API UMVVMViewModelBase : public UObject, public INotifyFieldValueChanged

virtual FDelegateHandle AddFieldValueChangedDelegate(UE::FieldNotification::FFieldId InFieldId, FFieldValueChangedDelegate InNewDelegate) override final;

void UMVVMViewModelBase::BroadcastFieldValueChanged(UE::FieldNotification::FFieldId InFieldId)
{
	NotificationDelegates.BroadcastFieldValueChanged(this, InFieldId);
}

蓝图编辑的 Binding 数据,会在 WBP::SetXXXViewModel 时解绑旧的,再绑新的

// 贴下入口函数,代码不贴了
UMVVMView::SetViewModel
UMVVMView::EnableLibraryBinding

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值