vault backup: 2024-10-12 17:19:45
@@ -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结果你又不创建。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
判断一个属性是否是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;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 315 KiB |
@@ -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变量的,这是关键不同。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
大致逻辑和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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 263 KiB |
@@ -0,0 +1,17 @@
|
||||
# BindWidgetAnimOptional
|
||||
|
||||
- **功能描述:** 指定在C++类中该UWidgetAnimation属性可以要绑定到UMG下的某个动画,也可以不绑定。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Widget Property
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** UWidget子类里UWidgetAnimation属性
|
||||
- **关联项:** [BindWidgetAnim](../BindWidgetAnim/BindWidgetAnim.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
同BindWidgetOptional作用也类似,在不绑定的时候在编译结果里会有一个提示,而不是像BindWidget一样强制的错误。
|
||||
|
||||

|
||||
|
||||
自然的也说过不能像Widget一样,不加BindWidget就自动默认绑定。
|
||||
|
||||
因此用法上要嘛加BindWidgetAnim,要嘛加BindWidgetAnimOptional。
|
After Width: | Height: | Size: 8.3 KiB |
@@ -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;
|
||||
};
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 7.1 KiB |
@@ -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;
|
||||
}
|
||||
```
|
@@ -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的时候,左侧预览界面会一下下的在跳动刷新。而在点击改变别的按钮的时候就没有该效果。
|
||||
|
||||

|
||||
|
||||
## 测试代码:
|
||||
|
||||
```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 的时候,界面左上角的数字在跳动(虽然整个界面其实并没有什么实质变化)。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在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);
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 538 KiB |
After Width: | Height: | Size: 537 KiB |
@@ -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调用。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在UCLASS上标记会导致UMG蓝图的bClassRequiresNativeTick=false。继而在UUserWidget的UpdateCanTick里判断。如果WidgetBPClass不为空(是蓝图子类)且ClassRequiresNativeTick为false,bCanTick 才一开始为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);
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 510 KiB |
@@ -0,0 +1,8 @@
|
||||
# EntryClass
|
||||
|
||||
- **功能描述:** 限定EntryWidgetClass属性上可选类必须继承自的基类,用在DynamicEntryBox和ListView这两个Widget上。
|
||||
- **使用位置:** UCLASS, UPROPERTY
|
||||
- **元数据类型:** string="abc"
|
||||
- **限制类型:** UWidget子类
|
||||
- **关联项:** [EntryInterface](EntryInterface/EntryInterface.md)
|
||||
- **常用程度:** ★★★
|
@@ -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接口继承自UserListEntry,Entry 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)。
|
||||
|
||||

|
||||
|
||||
如果如上面代码中所示,我们新创建一个接口为MyCustomListEntry,并且也新建一个新的MyCustomEntryWidget,然后在MyListView属性上指定EntryClass或EntryInterface(可以一起也可以单个),则ListView的EntryWidgetClass属性可选的类就被限制住了。
|
||||
|
||||

|
||||
|
||||
还有一种用法是当你想自定义一个ListView,可以选择继承自ListViewBase,然后在这个子类上直接限定EntryClass或EntryInterface,效果和上图是一样的。
|
||||
|
||||
```cpp
|
||||
UCLASS(meta = (EntryClass = MyCustomEntryWidget, EntryInterface = MyCustomListEntry))
|
||||
class UMyListView : public UListViewBase, public ITypedUMGListView<UObject*>
|
||||
{}
|
||||
```
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 114 KiB |
@@ -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,但其实你也知道这并没法办法编辑。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
对于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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 268 KiB |
@@ -0,0 +1,13 @@
|
||||
# OptionalWidget
|
||||
|
||||
- **功能描述:** 指定在C++类中该Widget属性可以绑定到UMG的某个同名控件,也可以不绑定。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Widget Property
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** UWidget子类里属性
|
||||
- **关联项:** [BindWidget](BindWidget/BindWidget.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
必须配合BindWidget使用。
|
||||
|
||||
BindWidget+OptionalWidget=BindWidgetOptional
|
@@ -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)
|
||||
{
|
||||
}
|
||||
```
|