使用Slate UI C++的SComboButton创建下拉菜单时出现UI堆叠异常
Slate UI自定义SCompoundWidget下拉菜单堆叠问题解决
问题描述
在C++中基于Slate UI开发自定义SCompoundWidget下拉菜单,项目编译运行正常,初始下拉菜单显示无异常,但点击下拉选项后,会出现新下拉菜单堆叠在旧菜单上方的视觉问题。
示例下拉菜单选项:
Example Test p1
默认选中首个选项“Example”,下拉菜单采用AutoWidth()适配最长的“Example”宽度。点击下拉菜单显示正常,但选择“Test”后,因“Test”宽度更短,视觉上新菜单堆叠在旧菜单上;选择更短的“p1”时,会出现三层堆叠的假象。
使用Unreal Engine的Widget Inspector工具查看,实际仅存在一个SComboButton,视觉上的旧菜单无法交互。
已尝试的方案:
- 每次选择选项时调用
SAssignNew重新创建SComboButton,方法不够优雅 - 尝试重绘SComboButton,未解决问题
问题代码
SDropDown.h
#pragma once #include "CoreMinimal.h" // 下拉菜单中的单个条目 struct FMenuEntry { FMenuEntry(FText Label, TFunction<void()> Action = (), FText Tooltip = FText::GetEmpty(), FSlateIcon Icon = FSlateIcon()) { this->Label = Label; this->Action = Action; this->Tooltip = Tooltip; this->Icon = Icon; } // 下拉条目显示文本 FText Label; // 选中时触发的回调函数 TFunction<void()> Action; // 悬停提示文本 FText Tooltip; // 条目左侧图标 FSlateIcon Icon; }; class MYPROJ_API SDropDown : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SDropDown) : _MenuEntries(TArray<FMenuEntry>()) { } SLATE_ARGUMENT(TArray<FMenuEntry>, MenuEntries) SLATE_END_ARGS() void Construct(const FArguments& InArgs); virtual bool SupportsKeyboardFocus() const override { return true; } private: // 下拉菜单所有条目 TArray<FMenuEntry> MenuEntries; // 下拉按钮显示的文本 FText FeaturedItem; // 下拉按钮控件 TSharedPtr<SComboButton> ComboButton; // 绑定下拉按钮文本到FeaturedItem FText GetFeaturedItem() const; };
SDropDown.cpp
#include "SDropDown.h" void SDropDown::Construct(const FArguments& InArgs) { // 绑定传入的菜单条目 this->MenuEntries = InArgs._MenuEntries; if(!MenuEntries.Num()) { UE_LOG(LogSlate, Fatal, TEXT("下拉菜单未添加任何条目,请至少添加一个...")); return; } // 默认选中第一个条目 FeaturedItem = MenuEntries[0].Label; FMenuBuilder Menu(true, nullptr); for(const FMenuEntry Entry : MenuEntries) { // 为每个条目添加菜单选项 Menu.AddMenuEntry(Entry.Label, Entry.Tooltip, Entry.Icon, FUIAction(FExecuteAction::CreateLambda( [this, Entry] () { // 更新下拉按钮显示的文本 if(ComboButton.IsValid()) { FeaturedItem = Entry.Label; } // 执行条目绑定的回调 if(Entry.Action) { Entry.Action(); } } ))); } ChildSlot [ SAssignNew(ComboButton, SComboButton) .ButtonContent() [ SNew(STextBlock) .Text(this, &SDropDown::GetFeaturedItem) // 绑定文本到FeaturedItem ] .MenuContent() [ Menu.MakeWidget() ] ]; } FText SDropDown::GetFeaturedItem() const { return FeaturedItem; }
解决方案
这个问题的核心是SComboButton的按钮宽度在文本变化时未重新计算,导致渲染残留。Slate的AutoWidth()默认仅在初始化时计算一次宽度,后续文本变更不会自动触发宽度重算,因此视觉上会保留旧宽度的渲染痕迹。
修复方案1:触发布局强制刷新
使用Slate的FCachedValue绑定文本,同时在文本变更后强制刷新控件布局:
- 修改SDropDown.h中的
FeaturedItem类型:
// 替换原FText FeaturedItem; FCachedValue<FText> FeaturedItem;
- 修改SDropDown.cpp中的初始化和回调逻辑:
void SDropDown::Construct(const FArguments& InArgs) { // ... 其他代码 ... // 初始化CachedValue,绑定当前Widget以通知更新 FeaturedItem = FCachedValue<FText>(this, MenuEntries[0].Label); // ... 其他代码 ... for(const FMenuEntry Entry : MenuEntries) { Menu.AddMenuEntry(Entry.Label, Entry.Tooltip, Entry.Icon, FUIAction(FExecuteAction::CreateLambda( [this, Entry] () { if(ComboButton.IsValid()) { // 更新CachedValue,自动通知Widget值变更 FeaturedItem.Set(Entry.Label); // 强制刷新按钮布局,重新计算宽度 ComboButton->InvalidateLayoutAndVolatility(); } if(Entry.Action) { Entry.Action(); } } ))); } // ... 其他代码 ... } FText SDropDown::GetFeaturedItem() const { return FeaturedItem.Get(); }
修复方案2:固定按钮为最大宽度
如果希望按钮始终保持所有选项中的最大宽度,避免宽度变化,可以提前计算所有条目的文本宽度,固定按钮宽度:
void SDropDown::Construct(const FArguments& InArgs) { // ... 其他代码 ... // 计算所有条目的最大文本宽度 const FSlateFontInfo FontInfo = FCoreStyle::Get().GetFontStyle("NormalFont"); const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); float MaxWidth = 0.0f; for(const FMenuEntry& Entry : MenuEntries) { const FVector2D TextSize = FontMeasure->Measure(Entry.Label, FontInfo); MaxWidth = FMath::Max(MaxWidth, TextSize.X); } // 添加按钮时设置固定宽度 ChildSlot [ SAssignNew(ComboButton, SComboButton) .ButtonContent() [ SNew(STextBlock) .Text(this, &SDropDown::GetFeaturedItem) ] .MenuContent() [ Menu.MakeWidget() ] .ButtonStyle(&FCoreStyle::Get().GetWidgetStyle<FButtonStyle>("ComboButton")) .WidthOverride(MaxWidth + 30) // 加上按钮图标和边距的额外宽度 ]; }
内容的提问来源于stack exchange,提问作者Septimis
相关产品推荐
相关产品推荐

