原创文章,转载请注明出处。
点击观看上一篇《UE4 Slate二 用UMG思想去理解Slate+Slate编码》
点击观看下一篇《UE4 Slate四 SlateUI如何做UI动画》
本篇文章主要是介绍Slate的编码,针对上一篇文章末尾的.h和.cpp做描述
点击观看上一篇《UE4 Slate二 用UMG思想去理解Slate+Slate编码》
先看下上篇文章代码的效果图
都知道UMG里面我们会继承自UUserWidget做一个自己的基类来写,那么Slate里面应该继承自谁?
在这个部分我们要了解一个插槽的概念:
Slot概念很重要, 插槽就是说我们在他(控件)下面可以添加多少个子控件,下面有三个我们写Slate会经常继承的类。
单个Slot的类,单个插槽。SUserWidget 或者 SCompoundWidget有一个插槽,
固定一个 比如 SCheckBox,SButton, SBox
SUserWidget是继承自SCompoundWidget,这两个我们都可以继承,引擎更多的是继承自SCompoundWidget。其实引擎推荐我们自己写的单个插槽类的时候是继承自SUserWidget。
Slate二讲解中我们是继承自SCompoundWidget的,也一样的。
多个Slot的类,多个插槽。SPanel: 多个插槽 比如SVerticalBox, SHorizontalBox,SScrollBox
没有插槽, 比如STextBlock
在UMG里面我们是UButton* pBtn = NewObject, 那么Slate里面我们是如何构造控件?
可以把SNew和SAssignNew就理解成我们平常调用的NewObject,只不过它在slate里面就是要这么写。因为Slate都是S类,非U类。从这点也说明了我们头文件中为什么都是智能指针包着的S类。
这个是引擎内的SUserWidget给我们抛来的Demo代码
TSharedRef<SButton> MyButton = SNew(SButton);
* or
* TSharedPtr<SButton> MyButton;
* SAssignNew( MyButton, SButton );
比如我们要新建一个Button,分别用SNew和SAssignNew写一下。
注意TSharedRef不能在.h的类内声明,
UE4智能指针->智能指针详细介绍链接
TSharedRef<SButton> MyButton = SNew(SButton);
TSharedPtr<SButton> MyButton;
SAssignNew( MyButton, SButton );
直接说比较含蓄,需要写一下才知道
SNew:
1>返回值不同:返回共享引用
SAssignNew:
1>返回值不同:返回共享指针
2>使用方式不同:链式编程中直接获取值,直接赋值,在链式编程中想获取值就用SAssignNew
优点:
1>效率比UMG要高,因为UMG封装的就是Slate
缺点:
1>不能断点调试,断点无法命中链式内部
2>编写界面制作麻烦且不易维护
因为我们是基于UE4 Plugin创建了一个Editor Standlone Window,
在这个插件代码中(test5_EditorStandlonWindow.cpp)中的OnSpawnPluginTab()方法中,
就是创建Slate的部分,所以我们在这个位置写就好了。
TSharedRef<SDockTab> Ftest5_EditorStandlonWindowModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
{
FText WidgetText = FText::Format(
LOCTEXT("WindowWidgetText", "Add code to {0} in {1} to override this window's contents"),
FText::FromString(TEXT("Ftest5_EditorStandlonWindowModule::OnSpawnPluginTab")),
FText::FromString(TEXT("test5_EditorStandlonWindow.cpp"))
);
//第一种方式
MyMainSlate = SNew(SMainSlate);
return SNew(SDockTab)
[
MyMainSlate->AsShared()
];
//第二种方式
/*SAssignNew(MyMainSlate, SMainSlate);
return SNew(SDockTab)
[
MyMainSlate->AsShared()
];*/
//第三种方式
/*return SNew(SDockTab)
[
SNew(SMainSlate)
];*/
//第四种方式
/*return SNew(SDockTab)
[
SAssignNew(MyMainSlate, SMainSlate)
];*/
//原来的代码注释掉了
//.TabRole(ETabRole::NomadTab)
//[
// // Put your tab content here!
// SNew(SBox)
// .HAlign(HAlign_Center)
// .VAlign(VAlign_Center)
// [
// SNew(STextBlock)
// .Text(WidgetText)
// ]
//];
}
//这个类的作用就是去用Slate代码实现一次这个UMG(WidgetBlueprint'/Game/Blogs_Slate/ReferUMGBP.ReferUMGBP')
class SMainSlate : public SCompoundWidget /*public SUserWidget*/
{
public:
//SLATE_BEGIN_ARGS+SLATE_END_ARGS 其实是一个结构体, 内部写的东西都相当于写在了一个结构体里面
SLATE_BEGIN_ARGS(SMainSlate)
{
}
SLATE_END_ARGS()
//外部执行SNew或者SAssignNew时候会调用Construct()
void Construct(const FArguments& InArgs);
这个呢,其实就是一个结构体, 内部写的东西都相当于写在了一个结构体里面。
为什么要有这个?
为了方便我们在外部进行SNew/SAssignNew之后能直接传参数过来,写法是如下:
下面SNew完了之后的通过 .点出来的参数其实都是在基类的上面宏内(结构体)定义的
用用就明白了,关于里面定义参数的宏,有很多,可以转到Engine里面去看。
后面会介绍Widget拾取器,可以更便捷的找到我们想看的Engine_Slate的实现位置。
SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ContentPadding(0.f)
.OnClicked(this, &SMainSlate::OnFirstSButton_OnClicked) //事件绑定的技巧, 转到定义, 看那边的代理是怎么定义的, 把参数和返回值拿过来定义一个函数即可
.OnPressed(this, &SMainSlate::OnFirstSButton_OnPressed)
.OnReleased(this, &SMainSlate::OnFirstSButton_OnReleased)
.OnHovered(this, &SMainSlate::OnFirstSButton_OnHovered)
.OnUnhovered(this, &SMainSlate::OnFirstSButton_OnUnhovered)
这里介绍一下在SConstraintCanvas里面新建一个STextBlock,不会针对每一个控件都讲一下(太多啦)。其他的在我上传的代码里面都有。
建议自己做一个UMG,然后用Slate你去实现一下试试。很多就都明白了。
void SMainSlate::Construct(const FArguments& InArgs)
{
ChildSlot
[
SNew(S类………………)
.属性设置
[
SNew(被上面S类包裹的子控件)
.子控件属性设置
[
//如果有想New的继续写
]
]
];
可以看到,我们的UMG最上层有一个Canvas Panel(UCanvasPanel类型),那么我们在代码中最开也去创建一个Canvas Panel(用它的主要目的是为了调整坐标方便)。
那么我们如何确定UCanvasPanel这个控件到底用的是哪一个S类呢?
1>首先在UMG编辑器中左侧UI列表上选中我们的Canvas Panel(UCanvasPanel类型),
2>然后再详细面板中跳转到我们这个类型的C++代码里面,
3>再Ctrl+End到头文件底部,就发现了一个被智能指针包裹的S类,没错,就是它了。
4>TIPS:通过这个方式我们确定了这个UCanvasPanel所用的S类是SConstraintCanvas,通过这种方式也可以找到其他UMG控件的实现S类。
找到了SConstraintCanvas之后,所以就有了我们的创建SConstraintCanvas代码
ChildSlot
[
SAssignNew(CanvasPanel_0, SConstraintCanvas)
//对应UMG(WidgetBlueprint'/Game/Blogs_Slate/ReferUMGBP.ReferUMGBP')中的文本
+ SConstraintCanvas::Slot()
.Anchors(0.f) //对应UMG这个文本控件上的Anchors属性,拷贝过来即可
.Offset(FMargin(20.0, 12.f, 600, 72)) //这个可能会迷惑, 第一个参数在这是PositionX, 第二个参数在这是PositionY, 第三个参数在这是SizeX, 第四个参数在这是SizeY.找不到设置坐标的同学注意看这里
.Alignment(FVector2D(0.f, 0.f)) //同样的, 对应Alignment是个FVector2D
.AutoSize(false) //对应AutoSize
.ZOrder(0)
[
SNew(STextBlock)
.Text(LOCTEXT("SMainSlate_TextBlock1", "Mesh合并界面"))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 53))
.ColorAndOpacity(FSlateColor(FLinearColor(1, 0, 0.361307, 1)))
]
CanvasPanel_0是我们在.h中定义的
//最外层的层, 就是我们默认创建UMG带的层
TSharedPtr<class SConstraintCanvas> CanvasPanel_0;
我们想在CanvasPanel_0加一个STextBlock,写法如上(+ SConstraintCanvas::Slot()),没啥原因,按着写就好了。
但是参数是什么意思呢?
一般我们会使用(下面这句代码) 的方式做为添加子控件的开始
+ SConstraintCanvas::Slot()
然后直接通过.点出来一些属性,调整我们这个控件的属性,
+ SConstraintCanvas::Slot()
.Anchors(0.f) //对应UMG这个文本控件上的Anchors属性,拷贝过来即可
.Offset(FMargin(20.0, 12.f, 600, 72)) //这个可能会迷惑, 第一个参数在这是PositionX, 第二个参数在这是PositionY, 第三个参数在这是SizeX, 第四个参数在这是SizeY.找不到设置坐标的同学注意看这里
.Alignment(FVector2D(0.f, 0.f)) //同样的, 对应Alignment是个FVector2D
.AutoSize(false) //对应AutoSize
.ZOrder(0)
比如下面的Offset就是FMargin(PositionX,PositionY,SizeX,SizeY) ,对应UMG的下图
.Alignment(FVector2D(0.f, 0.f)),对应UMG的下图
.AutoSize(false) //对应AutoSize,对应UMG的下图
然后再再[]中添加我们的控件,比如
像一些文本颜色字体的设置都是如下的写法。其他的S类比如会有其他的属性和事件绑定,也是类似的写法。
[
SNew(STextBlock)
.Text(LOCTEXT("SMainSlate_TextBlock1", "Mesh合并界面"))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 53))
.ColorAndOpacity(FSlateColor(FLinearColor(1, 0, 0.361307, 1)))
]
该部分需要了解UE4代理->代理详细介绍链接
分别对OnClicked/OnPressed/OnReleased/OnHovered/OnUnhovered都做了绑定
.h中
protected:
//SButton的事件绑定
FReply OnFirstSButton_OnClicked();
void OnFirstSButton_OnPressed();
void OnFirstSButton_OnReleased();
void OnFirstSButton_OnHovered();
void OnFirstSButton_OnUnhovered();
cpp中
SNew(SButton)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ContentPadding(0.f)
.OnClicked(this, &SMainSlate::OnFirstSButton_OnClicked) //事件绑定的技巧, 转到定义, 看那边的代理是怎么定义的, 把参数和返回值拿过来定义一个函数即可
.OnPressed(this, &SMainSlate::OnFirstSButton_OnPressed)
.OnReleased(this, &SMainSlate::OnFirstSButton_OnReleased)
.OnHovered(this, &SMainSlate::OnFirstSButton_OnHovered)
.OnUnhovered(this, &SMainSlate::OnFirstSButton_OnUnhovered)
[
SNew(STextBlock)
.Text(LOCTEXT("SMainSlate_Button_Text1", "---->"))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 24))
]
FReply SMainSlate::OnFirstSButton_OnClicked()
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnClicked"));
++nDynamicAddNum;
FString s2 = TEXT("new add") + FString::FromInt(nDynamicAddNum);
MyScrollBoxLeft->AddSlot()
[
SNew(STextBlock)
.Text(FText::FromString(s2))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 24))
.ColorAndOpacity(FSlateColor(FLinearColor(1, 0, 1, 1)))
];
MyScrollBoxRight->AddSlot()
[
SNew(STextBlock)
.Text(FText::FromString(s2))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 24))
.ColorAndOpacity(FSlateColor(FLinearColor(1, 1, 0, 1)))
];
return FReply::Handled();
}
void SMainSlate::OnFirstSButton_OnPressed()
{
//UE_LOG(LogTemp, Warning, TEXT("OnFirstSButton_OnPressed"));
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnPressed"));
}
void SMainSlate::OnFirstSButton_OnReleased()
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnReleased"));
}
void SMainSlate::OnFirstSButton_OnHovered()
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnHovered"));
}
void SMainSlate::OnFirstSButton_OnUnhovered()
{
GEngine->AddOnScreenDebugMessage(-1, 1.f, FColor::Green, TEXT("OnFirstSButton_OnUnhovered"));
}
我一共创建了三个CheckBox,并通过SAssignNew在链式编程里面将返回值放到了我们TArray数组中,
这个就是要用SAssignNew的情况。
//对应UMG(WidgetBlueprint'/Game/Blogs_Slate/ReferUMGBP.ReferUMGBP')中的
+ SConstraintCanvas::Slot()
.Anchors(0.f)
.Offset(FMargin(1260.0, 172.0, 600.0, 40.0)) //这个可能会迷惑, 第一个参数在这是PositionX, 第二个参数在这是PositionY, 第三个参数在这是SizeX, 第四个参数在这是SizeY.找不到设置坐标的同学注意看这里
.Alignment(FVector2D(0.f, 0.f)) //同样的, 对应Alignment是个FVector2D
.AutoSize(false) //对应AutoSize
.ZOrder(0)
[
SAssignNew(CheckBoxArray[0], SCheckBox)
.IsChecked(false)
.IsEnabled(true)
.OnCheckStateChanged(this, &SMainSlate::MyOnCheckStateChanged<0>)
[
SNew(STextBlock)
.Text(LOCTEXT("SMainSlate_checkbox_Text1", "按材质合并成SingleMesh"))
.Font(FCoreStyle::GetDefaultFontStyle("Roboto", 24))
]
]
我希望在三个checkbox之中同时只有一个被选中,所以有了下面的逻辑。
.h中
//SChechBox的事件绑定
template<int32 T>
void MyOnCheckStateChanged(ECheckBoxState emState)
{
if (CheckBoxArray.IsValidIndex(T))
{
for (int32 i = 0; i < CheckBoxArray.Num(); i++)
{
if (i==T)
{
if (CheckBoxArray[i].IsValid() || CheckBoxArray[i].Get())
{
CheckBoxArray[i]->SetIsChecked(emState);
}
}
else
{
if (CheckBoxArray[i].IsValid() || CheckBoxArray[i].Get())
{
CheckBoxArray[i]->SetIsChecked(ECheckBoxState::Unchecked);
}
}
}
}
}
点击观看上一篇《UE4 Slate二 用UMG思想去理解Slate+Slate编码》
点击观看下一篇《UE4 Slate四 SlateUI如何做UI动画》
谢谢,创作不易,大侠请留步… 动起可爱的双手,来个赞再走呗 <( ̄︶ ̄)>