本文主要是介绍Unreal UMG MVVM,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Unreal UMG MVVM
文章目录
- Unreal UMG MVVM
- 背景
- M - VM - V
- 扩展点
- === Editortime ===
- Viewmodels 编辑器界面
- View Bindings 编辑器界面
- 蓝图编译相关
- === Runtime ===
- 创建 ViewModel
- ViewModel 更新
背景
- 先阅读文档和 quabqi 的 UOD 视频分享,目前网上唯一的资料
- 看眼成熟的巨硬方案 WPF MVVM
- 剩下的就硬扒代码
M - VM - V
- Model:原始数据
- ViewModel:视图所需数据
- 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 UUserWidgetExtensionvoid UMVVMView::Construct()
{// Init ViewModel instancesfor (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 INotifyFieldValueChangedvirtual 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
这篇关于Unreal UMG MVVM的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!