vault backup: 2024-10-12 17:19:45

This commit is contained in:
2024-10-12 17:19:46 +08:00
parent ff94ddca61
commit 244c0c52f6
960 changed files with 31348 additions and 10 deletions

View File

@@ -0,0 +1,106 @@
# BindWidget
- **功能描述:** 指定在C++类中该Widget属性一定要绑定到UMG的某个同名控件。
- **使用位置:** UPROPERTY
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **限制类型:** UUserWidget子类里属性
- **关联项:** [BindWidgetOptional](../BindWidgetOptional/BindWidgetOptional.md), [OptionalWidget](../OptionalWidget.md)
- **常用程度:** ★★★★★
指定在C++类中该Widget属性一定要绑定到UMG的某个同名控件。
一种平常通用的编程范式是在C++中定义一个UUserWidget子类然后再在UMG中继承于这个C++类这样就能把一些逻辑放在C++中实现而在UMG中排布控件。这个时候常常就会有个需求需要在C++中用属性引用到UMG中定义的具体控件。
- 在C++里常见的作法是用WidgetTree->FindWidget来通过控件名字查找。但如果类里定义有几十个控件一一这么做就很繁琐。
- 因此更便利的方式是在C++里定义同名的控件属性这样就会自动的关联起来UMG蓝图对象在创建后引擎会自动的给C++中的Widget属性自动赋值到同名的控件。
- 必须要指出BindWidget只是用作UMG编辑器的编辑和编译提示让你记得要一一把名字关联上。在C++里定义的该属性要记得在UMG里也创建同名控件。在UMG中创建或更改的控件名字时知道在C++中有一个同名属性来关联接收就不会报错否则会提示和C++定义的名字冲突。
- 总结BindWidget的作用有二一是提醒UMG一定要相应的创建同名控件否则编译抱错误。二是在定义同C++里属性同名的控件的时候让UMG不会报错。
- 用法建议是为所有你想要绑定的同名属性都显式的加上BindWidget不要依赖含糊默认的自动同名绑定机制。
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_BindWidget :public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UTextBlock* MyTextBlock_NotFound;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UTextBlock* MyTextBlock_SameName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
class UTextBlock* MyTextBlock_Bind;
};
void UMyProperty_BindWidget::RunTest()
{
//C++里查找Widget的方式
UTextBlock* bindWidget= WidgetTree->FindWidget(TEXT("MyTextBlock_Bind"));
check(bindWidget==MyTextBlock_Bind);
}
```
## 测试效果:
测试操作是在C++中定义如上图的UUserWidget基类然后在UMG中创建蓝图子类。控件的列表如下图所示。
- 为了对比验证分别在C++和蓝图中定义3个控件有同名的和非同名的。然后在CreateWidet后在C++中调试查看这3个属性值。
- 可以发现MyTextBlock_Bind和MyTextBlock_SameName都自动的关联上了值发现关联属性值的逻辑其跟有没有标上BindWidget并没有关系。但是如果在MyTextBlock_SameName勾上变量也会报名字冲突的错。这是因为勾上变量会在蓝图中创建一个属性这样自然就和C++里的冲突。而没有勾上变量的时候MyTextBlock_SameName本质上只是一个在WidgetTree下的对象编辑器可以提示同名冲突C++里先定义然后UMG里再定义也可以选择不提示BindWidget的作用了其实。但如果是也要相应创建BP里的MyTextBlock_SameName变量这个冲突就是必然存在了。这个时候如果没有加上BindWidget引擎就会认为这是两个独立的不同的属性假如你在C++里明明没写BindWidget而引擎自作主张给你BindWidget了实际可能反而出现更多莫名错误。只有显式的加上BindWidget这个时候我们为MyTextBlock_Bind勾上变量引擎知道C++里已经有个C++属性了就没必要再创建一个蓝图属性了这个时候BP面板里没有
- MyTextBlock_NotFound并没有值这很符合逻辑因为我们也没有在UMG中定义该控件。但是值得注意的是假如我们尝试在UMG中定义该名字的控件会报错提示名字已经被占用。也很正常因为这就像C++类的子类里定义成员变量肯定不能出现成员变量冲突。但假如我们定义MyTextBlock_Bind就不会报这个“名字占用”的错因为引擎知道C++里有一个同名属性是要用来引用该控件。因此这才是BindWidget的精确作用含义只是作为提示。这个时候可能有人会问那我的UMG里的MyTextBlock_SameName是怎么创建上去的不是会报错吗答案是先在UMG里定义好然后再在C++里定义,这样就不会报错了。
- 假如最后MyTextBlock_Bind没有在UMG中定义那么UMG在编译的时候会报想要绑定的控件找不到提醒你自己说想要BindWidget结果你又不创建。
![Untitled](Untitled.png)
## 原理:
判断一个属性是否是BindWidget的函数是IsBindWidgetProperty这个函数。
在控件改名或编译的时候用来判断是否要生成错误提示的操作在FinishCompilingClass大致逻辑就是根据IsBindWidgetProperty判断该控件是否想要绑定然后根据当前情况生成提示。
而因为同名而自动关联值的逻辑操作在UWidgetBlueprintGeneratedClass::InitializeWidgetStatic逻辑其实是遍历WidgetTree下的控件根据其名字去C++中查找,如果找到就自动的赋值。
```cpp
void UWidgetBlueprintGeneratedClass::InitializeWidgetStatic()
{
// Find property with the same name as the template and assign the new widget to it.
if (FObjectPropertyBase** PropPtr = ObjectPropertiesMap.Find(Widget->GetFName()))
{
FObjectPropertyBase* Prop = *PropPtr;
check(Prop);
Prop->SetObjectPropertyValue_InContainer(UserWidget, Widget);
UObject* Value = Prop->GetObjectPropertyValue_InContainer(UserWidget);
check(Value == Widget);
}
}
void FWidgetBlueprintCompilerContext::FinishCompilingClass(UClass* Class)
{
// Check that all BindWidget properties are present and of the appropriate type
for (TFObjectPropertyBase<UWidget*>* WidgetProperty : TFieldRange<TFObjectPropertyBase<UWidget*>>(ParentClass))
{
bool bIsOptional = false;
if (FWidgetBlueprintEditorUtils::IsBindWidgetProperty(WidgetProperty, bIsOptional))
{}
}
}
bool FWidgetBlueprintEditorUtils::IsBindWidgetProperty(const FProperty* InProperty, bool& bIsOptional)
{
if ( InProperty )
{
bool bIsBindWidget = InProperty->HasMetaData("BindWidget") || InProperty->HasMetaData("BindWidgetOptional");
bIsOptional = InProperty->HasMetaData("BindWidgetOptional") || ( InProperty->HasMetaData("OptionalWidget") || InProperty->GetBoolMetaData("OptionalWidget") );
return bIsBindWidget;
}
return false;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

View File

@@ -0,0 +1,122 @@
# BindWidgetAnim
- **功能描述:** 指定在C++类中该UWidgetAnimation属性一定要绑定到UMG下的某个动画
- **使用位置:** UPROPERTY
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **限制类型:** UWidget子类里UWidgetAnimation属性
- **关联项:** [BindWidgetAnimOptional](../BindWidgetAnimOptional/BindWidgetAnimOptional.md)
- **常用程度:** ★★★★★
指定在C++类中该UWidgetAnimation属性一定要绑定到UMG下的某个动画。
作用同BindWidget类似都是用来把C++的属性和BP里的控件或动画赋值绑定起来。但又有一些区别
- UWidgetAnimation和Widget不同Widget的属性和控件只要同名就可以自动绑定起来而UWidgetAnimation就不允许不加BindWidgetAnim而同名否则会名字冲突报错。这是由于UMG里创建的Widget默认是不创建BP变量的子控件只是WidgetTree下的一个对象但是动画是默认会创建BP变量的。因此即使是UMG里先定义动画然后C++里再定义同名属性,也是会过不了编译的。
- UWidgetAnimation属性必须得是Transient否则也会报错。我想这是因为UWidgetAnimation自然会在BP里作为子对象序列化而不需要在C++序列的时候访问到该属性因此强制Transient以免不小心序列化它。另外UWidgetAnimation只是用作表现因此其实也会自动的加上CPF_RepSkip跳过网络复制。
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_BindWidget :public UUserWidget
{
GENERATED_BODY()
UMyProperty_BindWidget(const FObjectInitializer& ObjectInitializer);
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UWidgetAnimation* MyAnimation_NotFound;
//UPROPERTY(EditAnywhere, BlueprintReadWrite)
//class UWidgetAnimation* MyAnimation_SameName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient, meta = (BindWidgetAnim))
class UWidgetAnimation* MyAnimation_Bind;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient, meta = (BindWidgetAnimOptional))
class UWidgetAnimation* MyAnimation_BindOptional;
};
```
## 测试效果:
测试过程和BindWidget类似在C++和UMG中定义不同类型属性和动画对象。可以根据VS里实际对象的值发现
- MyAnimation_Bind和MyAnimation_BindOptional都自动的绑定了正确的动画对象。
- 没有加BindWidgetAnim的MyAnimation_SameName必须注释掉否则会和UMG里的MyAnimation_SameName名字冲突。
- 再提一下不能像Widget里一样先UMG里定义动画然后再C++定义同名属性因为WidgetAnimation是一定会创建BP变量的这是关键不同。
![Untitled](Untitled.png)
## 原理:
大致逻辑和BindWidget类似都是判断属性是否BindWidgetAnim。然后相应的在编译和改名的时候判断。
关于动画变量设置PropertyFlags的逻辑在CreateClassVariablesFromBlueprint里可以看见加上了4个属性明确了不要序列化该属性。
而为UWidgetAnimation*属性自动绑定赋值的逻辑在BindAnimationsStatic一眼就懂。
```cpp
bool FWidgetBlueprintEditorUtils::IsBindWidgetAnimProperty(const FProperty* InProperty, bool& bIsOptional)
{
if (InProperty)
{
bool bIsBindWidgetAnim = InProperty->HasMetaData("BindWidgetAnim") || InProperty->HasMetaData("BindWidgetAnimOptional");
bIsOptional = InProperty->HasMetaData("BindWidgetAnimOptional");
return bIsBindWidgetAnim;
}
return false;
}
void FWidgetBlueprintCompilerContext::CreateClassVariablesFromBlueprint()
{
for (UWidgetAnimation* Animation : WidgetBP->Animations)
{
FEdGraphPinType WidgetPinType(UEdGraphSchema_K2::PC_Object, NAME_None, Animation->GetClass(), EPinContainerType::None, true, FEdGraphTerminalType());
FProperty* AnimationProperty = CreateVariable(Animation->GetFName(), WidgetPinType);
if ( AnimationProperty != nullptr )
{
const FString DisplayName = Animation->GetDisplayName().ToString();
AnimationProperty->SetMetaData(TEXT("DisplayName"), *DisplayName);
AnimationProperty->SetMetaData(TEXT("Category"), TEXT("Animations"));
AnimationProperty->SetPropertyFlags(CPF_Transient);
AnimationProperty->SetPropertyFlags(CPF_BlueprintVisible);
AnimationProperty->SetPropertyFlags(CPF_BlueprintReadOnly);
AnimationProperty->SetPropertyFlags(CPF_RepSkip);
WidgetAnimToMemberVariableMap.Add(Animation, AnimationProperty);
}
}
}
void FWidgetBlueprintCompilerContext::FinishCompilingClass(UClass* Class)
{
if (!WidgetAnimProperty->HasAnyPropertyFlags(CPF_Transient))
{
const FText BindWidgetAnimTransientError = LOCTEXT("BindWidgetAnimTransient", "The property @@ uses BindWidgetAnim, but isn't Transient!");
MessageLog.Error(*BindWidgetAnimTransientError.ToString(), WidgetAnimProperty);
}
}
void UWidgetBlueprintGeneratedClass::BindAnimationsStatic(UUserWidget* Instance, const TArrayView<UWidgetAnimation*> InAnimations, const TMap<FName, FObjectPropertyBase*>& InPropertyMap)
{
// Note: It's not safe to assume here that the UserWidget class type is a UWidgetBlueprintGeneratedClass!
// - @see InitializeWidgetStatic()
for (UWidgetAnimation* Animation : InAnimations)
{
if (Animation->GetMovieScene())
{
// Find property with the same name as the animation and assign the animation to it.
if (FObjectPropertyBase*const* PropPtr = InPropertyMap.Find(Animation->GetMovieScene()->GetFName()))
{
check(*PropPtr);
(*PropPtr)->SetObjectPropertyValue_InContainer(Instance, Animation);
}
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

View File

@@ -0,0 +1,17 @@
# BindWidgetAnimOptional
- **功能描述:** 指定在C++类中该UWidgetAnimation属性可以要绑定到UMG下的某个动画也可以不绑定。
- **使用位置:** UPROPERTY
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **限制类型:** UWidget子类里UWidgetAnimation属性
- **关联项:** [BindWidgetAnim](../BindWidgetAnim/BindWidgetAnim.md)
- **常用程度:** ★★★
同BindWidgetOptional作用也类似在不绑定的时候在编译结果里会有一个提示而不是像BindWidget一样强制的错误。
![Untitled](Untitled.png)
自然的也说过不能像Widget一样不加BindWidget就自动默认绑定。
因此用法上要嘛加BindWidgetAnim要嘛加BindWidgetAnimOptional。

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,57 @@
# BindWidgetOptional
- **功能描述:** 指定在C++类中该Widget属性可以绑定到UMG的某个同名控件也可以不绑定。
- **使用位置:** UPROPERTY
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **限制类型:** UWidget子类里属性
- **关联项:** [BindWidget](../BindWidget/BindWidget.md)
- **常用程度:** ★★★
指定在C++类中该Widget属性可以绑定到UMG的某个同名控件也可以不绑定。
大致作用和BindWidget一样区别是
- BindWidgetOptional顾名思义是可选的意思是UMG里即使不定义该控件在编译的时候也不会报错。编译会通过但是会提示警告缺少控件。
-
- 和不加BindWidgetOptional的控件同名属性的区别是前者在UMG里定义同名控件的时候不会报错但后者是会提示同名冲突报错。
BindWidgetOptional的写法有两种
BindWidgetOptional可以看作是BindWidget和OptionalWidget的合并版。
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_BindWidget :public UUserWidget
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class UTextBlock* MyTextBlock_SameName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidgetOptional))
class UTextBlock* MyTextBlock_Optional1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget, OptionalWidget))
class UTextBlock* MyTextBlock_Optional2;
};
```
## 测试效果:
![Untitled](Untitled.png)
## 原理:
```cpp
bool FWidgetBlueprintEditorUtils::IsBindWidgetProperty(const FProperty* InProperty, bool& bIsOptional)
{
if ( InProperty )
{
bool bIsBindWidget = InProperty->HasMetaData("BindWidget") || InProperty->HasMetaData("BindWidgetOptional");
bIsOptional = InProperty->HasMetaData("BindWidgetOptional") || ( InProperty->HasMetaData("OptionalWidget") || InProperty->GetBoolMetaData("OptionalWidget") );
return bIsBindWidget;
}
return false;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,39 @@
# DefaultGraphNode
- **功能描述:** 标记引擎默认创建的蓝图节点。
- **使用位置:** UCLASS
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **常用程度:** 0
标记引擎默认创建的蓝图节点。
这样就可以在判断蓝图内是否有用户手动创建的节点时,过滤掉引擎自动创建的那些。
只在内部使用,用户不需要自己使用。
## 原理:
```cpp
static bool BlueprintEditorImpl::GraphHasUserPlacedNodes(UEdGraph const* InGraph)
{
bool bHasUserPlacedNodes = false;
for (UEdGraphNode const* Node : InGraph->Nodes)
{
if (Node == nullptr)
{
continue;
}
if (!Node->GetOutermost()->GetMetaData()->HasValue(Node, FNodeMetadata::DefaultGraphNode))
{
bHasUserPlacedNodes = true;
break;
}
}
return bHasUserPlacedNodes;
}
```

View File

@@ -0,0 +1,89 @@
# DesignerRebuild
- **功能描述:** 指定Widget里的某个属性值改变后应该重新刷新UMG的预览界面。
- **使用位置:** UPROPERTY
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **限制类型:** UWidget子类里属性
- **常用程度:** ★
指定Widget里的某个属性值改变后应该重新刷新UMG的预览界面。
首先想到的问题是哪种属性需要用到该DesignerRebuild标记
这个属性很少需要用到一般Widget里的属性在更新后也只需要更新自己的显示不需要刷新整个界面比如字号。需要用到的情况想来有二
1. 一些属性的改变会大大的改变其控件样式当然也可以做到精细化的只重绘自己但干脆整个预览界面刷新一下得了反正是编辑器环境。比如UTextBlock 的bSimpleTextMode和UListViewBase 下的EntryWidgetClass都会大大的改变自己。
2. 一些属性可能影响到整个界面别的东西的时候,这个时候也时候干脆全部刷新一下。没找到恰当的例子,但如果用户自己的控件有这个需求,就可以标上。
## 源码里的例子是:
```cpp
UCLASS(meta=(DisplayName="Text"), MinimalAPI)
class UTextBlock : public UTextLayoutWidget
{
/**
* If this is enabled, text shaping, wrapping, justification are disabled in favor of much faster text layout and measurement.
* This feature is only suitable for "simple" text (ie, text containing only numbers or basic ASCII) as it disables the complex text rendering support required for certain languages (such as Arabic and Thai).
* It is significantly faster for text that can take advantage of it (particularly if that text changes frequently), but shouldn't be used for localized user-facing text.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Performance, AdvancedDisplay, meta=(AllowPrivateAccess = "true", DesignerRebuild))
bool bSimpleTextMode;
}
UCLASS(Abstract, NotBlueprintable, hidedropdown, meta = (EntryInterface = UserListEntry), MinimalAPI)
class UListViewBase : public UWidget
{
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ListEntries, meta = (DesignerRebuild, AllowPrivateAccess = true, MustImplement = "/Script/UMG.UserListEntry"))
TSubclassOf<UUserWidget> EntryWidgetClass;
}
```
## UTextBlock的测试效果
可以发现在改变bSimpleTextMode的时候左侧预览界面会一下下的在跳动刷新。而在点击改变别的按钮的时候就没有该效果。
![DesignerRebuild1](DesignerRebuild1.gif)
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_BindWidget :public UUserWidget
{
public:
UPROPERTY(EditAnywhere, Category = Design)
int32 MyInt = 123;
UPROPERTY(EditAnywhere, Category = Design, meta = (DesignerRebuild))
int32 MyInt_DesignerRebuild = 123;
}
```
## 测试效果:
可见在改变普通的属性MyInt 的时候界面并不会刷新。而在改变MyInt_DesignerRebuild 的时候,界面左上角的数字在跳动(虽然整个界面其实并没有什么实质变化)。
![DesignerRebuild2](DesignerRebuild2.gif)
## 原理:
在Widget里的带有DesignerRebuild的某属性改变之后会通知InvalidatePreview以便更新编辑器里的预览窗口。
```cpp
void SWidgetDetailsView::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, FEditPropertyChain* PropertyThatChanged)
{
const static FName DesignerRebuildName("DesignerRebuild");
//...
// If the property that changed is marked as "DesignerRebuild" we invalidate
// the preview.
if ( PropertyChangedEvent.Property->HasMetaData(DesignerRebuildName) || PropertyThatChanged->GetActiveMemberNode()->GetValue()->HasMetaData(DesignerRebuildName) )
{
const bool bViewOnly = true;
BlueprintEditor.Pin()->InvalidatePreview(bViewOnly);
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 KiB

View File

@@ -0,0 +1,82 @@
# DisableNativeTick
- **功能描述:** 禁用该UserWidget的NativeTick。
- **使用位置:** UCLASS
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **限制类型:** UserWidget的子类
- **常用程度:** ★★★
禁用该UserWidget的NativeTick。
如果只有C++类则不起作用因为纯C++的Widget没有WidgetBPClass 。
而且BP的子类要删除Tick蓝图节点。
## 测试代码:
```cpp
UCLASS(BlueprintType, meta=())
class INSIDER_API UMyWidget_WithNativeTick :public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override
{
Super::NativeTick(MyGeometry, InDeltaTime);
UKismetSystemLibrary::PrintString(nullptr, TEXT("WithNativeTick"), true);
}
};
UCLASS(BlueprintType,meta=(DisableNativeTick))
class INSIDER_API UMyWidget_DisableNativeTick :public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override
{
Super::NativeTick(MyGeometry, InDeltaTime);
UKismetSystemLibrary::PrintString(nullptr, TEXT("DisableNativeTick"), true);
}
};
```
## 测试效果:
在蓝图中分别创建UMyWidget_WithNativeTick 和UMyWidget_DisableNativeTick 的子类UMG_WithTick和UMG_DisableTick。然后把他们都添加到一个UMG里添加到屏幕上后观察NativeTick的调用情况。
可见只有WithNativeTick调用。
![Untitled](Untitled.png)
## 原理:
在UCLASS上标记会导致UMG蓝图的bClassRequiresNativeTick=false。继而在UUserWidget的UpdateCanTick里判断。如果WidgetBPClass不为空是蓝图子类且ClassRequiresNativeTick为falsebCanTick 才一开始为false。然后判断bHasScriptImplementedTick则要求蓝图中没有EventTick默认会创建自己要手动删掉。然后后面继续判断要没有延迟蓝图节点没有动画。总之就是这个Widget要真的没有Tick的需求则可以真的最后bCanTick=false。
```cpp
void UWidgetBlueprint::UpdateTickabilityStats(bool& OutHasLatentActions, bool& OutHasAnimations, bool& OutClassRequiresNativeTick)
{
static const FName DisableNativeTickMetaTag("DisableNativeTick");
const bool bClassRequiresNativeTick = !NativeParent->HasMetaData(DisableNativeTickMetaTag);
OutClassRequiresNativeTick = bClassRequiresNativeTick;
}
void FWidgetBlueprintCompilerContext::CopyTermDefaultsToDefaultObject(UObject* DefaultObject)
{
WidgetBP->UpdateTickabilityStats(bClassOrParentsHaveLatentActions, bClassOrParentsHaveAnimations, bClassRequiresNativeTick);
WidgetClass->SetClassRequiresNativeTick(bClassRequiresNativeTick);
}
void UUserWidget::UpdateCanTick()
{
UWidgetBlueprintGeneratedClass* WidgetBPClass = Cast<UWidgetBlueprintGeneratedClass>(GetClass());
bCanTick |= !WidgetBPClass || WidgetBPClass->ClassRequiresNativeTick();
bCanTick |= bHasScriptImplementedTick;
bCanTick |= World->GetLatentActionManager().GetNumActionsForObject(this) != 0;
bCanTick |= ActiveSequencePlayers.Num() > 0;
bCanTick |= QueuedWidgetAnimationTransitions.Num() > 0;
SafeGCWidget->SetCanTick(bCanTick);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

View File

@@ -0,0 +1,8 @@
# EntryClass
- **功能描述:** 限定EntryWidgetClass属性上可选类必须继承自的基类用在DynamicEntryBox和ListView这两个Widget上。
- **使用位置:** UCLASS, UPROPERTY
- **元数据类型:** string="abc"
- **限制类型:** UWidget子类
- **关联项:** [EntryInterface](EntryInterface/EntryInterface.md)
- **常用程度:** ★★★

View File

@@ -0,0 +1,132 @@
# EntryInterface
- **功能描述:** 限定EntryWidgetClass属性上可选类必须实现的接口用在DynamicEntryBox和ListView这两个Widget上。
- **使用位置:** UCLASS, UPROPERTY
- **引擎模块:** Widget Property
- **元数据类型:** string="abc"
- **限制类型:** UWidget子类
- **关联项:** [EntryClass](../EntryClass.md)
- **常用程度:** ★★★
限定EntryWidgetClass属性上可选类必须实现的接口用在DynamicEntryBox和ListView这两个Widget上。
以ListView为例术语Entry指的是列表中显示的子控件而Item指的是列表背后的数据元素。比如列表背包有1000个元素(Item)但是同时只能呈现10个控件(Entry)在界面上。
因此EntryInterface和EntryClass顾名思义指的是EntryWidget上要实现的接口和其基类。
用法展示以下都用ListView举例DynamicBox同理。
```cpp
//1. ListView作为别的Widget的属性因此会在Property上进行Meta的提取判断。
//该属性必须是BindWidget才能自动绑定到UMG里的控件同时作为C++ property才能被枚举到。
class UMyUserWidget : public UUserWidget
{
UPROPERTY(BindWidget, meta = (EntryClass = MyListEntryWidget,EntryInterface = MyUserListEntry ))
UListViewBase* MyListView;
}
//2. 如果在Property上没有找到改Meta也会尝试在Widget Class身上直接找
UCLASS(meta = (EntryClass = MyListEntryWidget, EntryInterface = "/Script/UMG.UserObjectListEntry"))
class UMyListView : public UListViewBase, public ITypedUMGListView<UObject*>
{}
//3.之后在ClassPicker的时候EntryClass指定其父类EntryInterface指定类必须实现的接口
```
## 源码中的用法:
```cpp
UCLASS(Abstract, NotBlueprintable, hidedropdown, meta = (EntryInterface = UserListEntry), MinimalAPI)
class UListViewBase : public UWidget
{
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = ListEntries, meta = (DesignerRebuild, AllowPrivateAccess = true, MustImplement = "/Script/UMG.UserListEntry"))
TSubclassOf<UUserWidget> EntryWidgetClass;
}
UCLASS(meta = (EntryInterface = "/Script/UMG.UserObjectListEntry"), MinimalAPI)
class UListView : public UListViewBase, public ITypedUMGListView<UObject*>
{}
//其中UserObjectListEntry接口继承自UserListEntryEntry Widget都得继承自该接口。
UINTERFACE(MinimalAPI)
class UUserObjectListEntry : public UUserListEntry
{}
SNew(SClassPropertyEntryBox)
.AllowNone(false)
.IsBlueprintBaseOnly(true)
.RequiredInterface(RequiredEntryInterface)
.MetaClass(EntryBaseClass ? EntryBaseClass : UUserWidget::StaticClass())
.SelectedClass(this, &FDynamicEntryWidgetDetailsBase::GetSelectedEntryClass)
.OnSetClass(this, &FDynamicEntryWidgetDetailsBase::HandleNewEntryClassSelected)
```
在FDynamicEntryWidgetDetailsBase中判断EntryInterface和EntryClass然后在SClassPropertyEntryBox中限定属性细节面板ClassPicker的可选类。FDynamicEntryWidgetDetailsBase是FListViewBaseDetails和FDynamicEntryBoxDetails的基类因此ListView和DynamicBox的属性细节面板都由它进行定制化。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyEntryWidget :public UUserWidget, public IUserObjectListEntry
{
GENERATED_BODY()
public:
virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
public:
UPROPERTY(meta = (BindWidget))
class UTextBlock* ValueTextBlock;
};
//////////////////////////////////////////////////////////////////////////
UINTERFACE(MinimalAPI)
class UMyCustomListEntry : public UUserObjectListEntry
{
GENERATED_UINTERFACE_BODY()
};
class IMyCustomListEntry : public IUserObjectListEntry
{
GENERATED_IINTERFACE_BODY()
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyCustomEntryWidget :public UUserWidget, public IMyCustomListEntry
{
GENERATED_BODY()
public:
virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
public:
UPROPERTY(meta = (BindWidget))
class UTextBlock* ValueTextBlock;
};
//////////////////////////////////////////////////////////////////////////
UCLASS()
class INSIDER_API UMyListContainerWidget :public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (BindWidget, EntryClass = MyCustomEntryWidget, EntryInterface = MyCustomListEntry))
class UListView* MyListView;
};
```
## 蓝图中的效果:
如果MyListView上没有指定EntryClass或EntryInterface则在ListView的EntryWidgetClass属性上可以选择蓝图创建的UMG_MyEntry(继承自C++的UMyEntryWidget)。
![Untitled](Untitled.png)
如果如上面代码中所示我们新创建一个接口为MyCustomListEntry并且也新建一个新的MyCustomEntryWidget然后在MyListView属性上指定EntryClass或EntryInterface可以一起也可以单个则ListView的EntryWidgetClass属性可选的类就被限制住了。
![Untitled](Untitled%201.png)
还有一种用法是当你想自定义一个ListView可以选择继承自ListViewBase然后在这个子类上直接限定EntryClass或EntryInterface效果和上图是一样的。
```cpp
UCLASS(meta = (EntryClass = MyCustomEntryWidget, EntryInterface = MyCustomListEntry))
class UMyListView : public UListViewBase, public ITypedUMGListView<UObject*>
{}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -0,0 +1,114 @@
# IsBindableEvent
- **功能描述:** 把一个动态单播委托暴露到UMG蓝图里以绑定相应事件。
- **使用位置:** UPROPERTY
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **限制类型:** UWidget子类里动态单播属性
- **常用程度:** ★★★
把一个动态单播委托暴露到UMG蓝图里以绑定相应事件。
需要注意的点是:
- 必须是动态委托就是DYNAMIC的那些这样才可以在蓝图里序列化。
- 动态多播委托DECLARE_DYNAMIC_MULTICAST_DELEGATE默认就可以在UMG里绑定事件因此没有必要加IsBindableEvent。往往也配合加上BlueprintAssignable以便也可以在蓝图里手动绑定。
- 动态单播委托DECLARE_DYNAMIC_DELEGATE默认是不在UMG里暴露的。但可以加上IsBindableEvent以便可以在其实例的细节面板上绑定。
- UMG里的控件事件为什么要有多播和单播其实多播和单播除了数量不同以外最大的不同是多播没有返回值。这个例子可以对比UButton下的OnClicked多播事件和UImage下的OnMouseButtonDownEvent单播委托前者是点击的事件已经是个“结果”事件了点击事件可能被多个地方响应因此要设计成多播。而后者的OnMouseButtonDownEvent是鼠标按下的事件有一个重要的逻辑是会根据返回值FEventReply的不同而决定该事件是否继续路由上去因此只能用单播只能绑定一个。
## 源码例子:
```cpp
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonClickedEvent);
class UButton : public UContentWidget
{
UPROPERTY(BlueprintAssignable, Category="Button|Event")
FOnButtonClickedEvent OnClicked;
}
DECLARE_DYNAMIC_DELEGATE_RetVal_TwoParams(FEventReply, FOnPointerEvent, FGeometry, MyGeometry, const FPointerEvent&, MouseEvent);
class UImage : public UWidget
{
UPROPERTY(EditAnywhere, Category=Events, meta=( IsBindableEvent="True" ))
FOnPointerEvent OnMouseButtonDownEvent;
}
```
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_BindWidget :public UUserWidget
{
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnMyClickedMulticastDelegate);
UPROPERTY(EditAnywhere, BlueprintAssignable, Category = MyEvent)
FOnMyClickedMulticastDelegate MyClickedMulticastDelegate;
public:
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(FString,FOnMyClickedDelegate,int32,MyValue);
UPROPERTY(EditAnywhere, Category = MyEvent)
FOnMyClickedDelegate MyClickedDelegate_Default;
UPROPERTY(EditAnywhere, Category = MyEvent)
FOnMyClickedDelegate MyClickedEvent;
UPROPERTY(EditAnywhere, Category = MyEvent, meta = (IsBindableEvent = "True"))
FOnMyClickedDelegate MyClickedDelegate_Bind;
}
```
## 测试结果:
操作步骤是在UMG_BindTest外再创建一个UMG然后让UMG_BindTest成为子控件然后观察其实例上的事件绑定如下图右侧所示。
- 可以发现动态多播委托默认就会出现可以绑定的+定制化按钮如MyClickedMulticastDelegate。
- 动态多播委托加上BlueprintAssignable不能加在单播委托上了之后就可以在蓝图里绑定事件如左下侧图。
- 加了IsBindableEvent 的MyClickedDelegate_Bind可以看见出现了可以Bind的下拉按钮绑定之后可以显示函数名字也可以清除。
- 没有加IsBindableEvent 的MyClickedDelegate_Default就没有出现在可绑定的按钮你只能在C++里自己绑定了。
- 没有加IsBindableEvent 的MyClickedEvent因为名字以Event结尾也出现了可绑定的按钮这只能说是当前的一个潜规则。源码注释也说以后会去除。
- 另外这些委托我虽然都加上了EditAnywhere但其实你也知道这并没法办法编辑。
![Untitled](Untitled.png)
## 原理:
对于Widget的细节面板引擎也定义了各种Customization。其中对应的就是FBlueprintWidgetCustomization。其针对绑定的部分的代码如下。
代码也很容易理解动态多播委托默认都出现绑定动态单播委托有加IsBindableEvent或者名字以Event结尾就也创建绑定按钮。
```cpp
PropertyView->RegisterInstancedCustomPropertyLayout(UWidget::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FBlueprintWidgetCustomization::MakeInstance, BlueprintEditorRef, BlueprintEditorRef->GetBlueprintObj()));
void FBlueprintWidgetCustomization::PerformBindingCustomization(IDetailLayoutBuilder& DetailLayout, const TArrayView<UWidget*> Widgets)
{
static const FName IsBindableEventName(TEXT("IsBindableEvent"));
bCreateMulticastEventCustomizationErrorAdded = false;
if ( Widgets.Num() == 1 )
{
UWidget* Widget = Widgets[0];
UClass* PropertyClass = Widget->GetClass();
for ( TFieldIterator<FProperty> PropertyIt(PropertyClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt )
{
FProperty* Property = *PropertyIt;
if ( FDelegateProperty* DelegateProperty = CastField<FDelegateProperty>(*PropertyIt) )
{
//TODO Remove the code to use ones that end with "Event". Prefer metadata flag.
if ( DelegateProperty->HasMetaData(IsBindableEventName) || DelegateProperty->GetName().EndsWith(TEXT("Event")) )
{
CreateEventCustomization(DetailLayout, DelegateProperty, Widget);
}
}
else if ( FMulticastDelegateProperty* MulticastDelegateProperty = CastField<FMulticastDelegateProperty>(Property) )
{
CreateMulticastEventCustomization(DetailLayout, Widget->GetFName(), PropertyClass, MulticastDelegateProperty);
}
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

View File

@@ -0,0 +1,13 @@
# OptionalWidget
- **功能描述:** 指定在C++类中该Widget属性可以绑定到UMG的某个同名控件也可以不绑定。
- **使用位置:** UPROPERTY
- **引擎模块:** Widget Property
- **元数据类型:** bool
- **限制类型:** UWidget子类里属性
- **关联项:** [BindWidget](BindWidget/BindWidget.md)
- **常用程度:** ★★★
必须配合BindWidget使用。
BindWidget+OptionalWidget=BindWidgetOptional

View File

@@ -0,0 +1,26 @@
# ViewmodelBlueprintWidgetExtension
- **功能描述:** 用来验证InListItems的Object类型是否符合EntryWidgetClass的MVVM绑定的ViewModelProperty。
- **使用位置:** UFUNCTION
- **引擎模块:** Widget Property
- **元数据类型:** string="abc"
- **常用程度:** 0
用来验证InListItems的Object类型是否符合EntryWidgetClass的MVVM绑定的ViewModelProperty。
当前只在ListView里该函数使用。
## 原理:
```cpp
UCLASS(meta = (EntryInterface = "/Script/UMG.UserObjectListEntry"), MinimalAPI)
class UListView : public UListViewBase, public ITypedUMGListView<UObject*>
{
UFUNCTION(BlueprintCallable, Category = ListView, meta = (AllowPrivateAccess = true, DisplayName = "Set List Items", ViewmodelBlueprintWidgetExtension = "EntryViewModel"))
UMG_API void BP_SetListItems(const TArray<UObject*>& InListItems);
}
void UMVVMViewBlueprintListViewBaseExtension::Precompile(UE::MVVM::Compiler::IMVVMBlueprintViewPrecompile* Compiler, UWidgetBlueprintGeneratedClass* Class)
{
}
```