需求
实现可以搜索提示的Combox
代码
SMySearchableComboBox.h
// Copyright Epic Games, Inc. All Rights Reservekd.
#pragma once
#include "CoreMinimal.h"
#include "Input/Reply.h"
#include "Widgets/SWidget.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Views/SListView.h"
#include "Widgets/Input/SComboBox.h"
/**
* A searchable text combo box
*/
class TESTMOUSEINPUT_API SMySearchableComboBox : public SComboButton
{
public:
/** Type of list used for showing menu options. */
typedef SListView< TSharedPtr<FString> > SComboListType;
/** Delegate type used to generate widgets that represent Options */
typedef typename TSlateDelegates< TSharedPtr<FString> >::FOnGenerateWidget FOnGenerateWidget;
typedef typename TSlateDelegates< TSharedPtr<FString> >::FOnSelectionChanged FOnSelectionChanged;
SLATE_BEGIN_ARGS(SMySearchableComboBox)
: _Content()
, _ComboBoxStyle(&FAppStyle::Get().GetWidgetStyle<FComboBoxStyle>("ComboBox"))
, _ButtonStyle(nullptr)
, _ItemStyle(&FAppStyle::Get().GetWidgetStyle<FTableRowStyle>("ComboBox.Row"))
, _ContentPadding(_ComboBoxStyle->ContentPadding)
, _ForegroundColor(FSlateColor::UseStyle())
, _OptionsSource()
, _OnSelectionChanged()
, _OnGenerateWidget()
, _InitiallySelectedItem(nullptr)
, _Method()
, _MaxListHeight(450.0f)
, _HasDownArrow(true)
, _SearchVisibility()
{}
/** Slot for this button's content (optional) */
SLATE_DEFAULT_SLOT(FArguments, Content)
SLATE_STYLE_ARGUMENT(FComboBoxStyle, ComboBoxStyle)
/** The visual style of the button part of the combo box (overrides ComboBoxStyle) */
SLATE_STYLE_ARGUMENT(FButtonStyle, ButtonStyle)
SLATE_STYLE_ARGUMENT(FTableRowStyle, ItemStyle)
SLATE_ATTRIBUTE(FMargin, ContentPadding)
SLATE_ATTRIBUTE(FSlateColor, ForegroundColor)
SLATE_ARGUMENT(const TArray< TSharedPtr<FString> >*, OptionsSource)
SLATE_EVENT(FOnSelectionChanged, OnSelectionChanged)
SLATE_EVENT(FOnGenerateWidget, OnGenerateWidget)
/** Called when combo box is opened, before list is actually created */
SLATE_EVENT(FOnComboBoxOpening, OnComboBoxOpening)
/** The custom scrollbar to use in the ListView */
SLATE_ARGUMENT(TSharedPtr<SScrollBar>, CustomScrollbar)
/** The option that should be selected when the combo box is first created */
SLATE_ARGUMENT(TSharedPtr<FString>, InitiallySelectedItem)
SLATE_ARGUMENT(TOptional<EPopupMethod>, Method)
/** The max height of the combo box menu */
SLATE_ARGUMENT(float, MaxListHeight)
/**
* When false, the down arrow is not generated and it is up to the API consumer
* to make their own visual hint that this is a drop down.
*/
SLATE_ARGUMENT(bool, HasDownArrow)
/** Allow setting the visibility of the search box dynamically */
SLATE_ATTRIBUTE(EVisibility, SearchVisibility)
SLATE_END_ARGS()
/**
* Construct the widget from a declaration
*
* @param InArgs Declaration from which to construct the combo box
*/
void Construct(const FArguments& InArgs);
void ClearSelection();
void SetSelectedItem(TSharedPtr<FString> InSelectedItem);
/** @return the item currently selected by the combo box. */
TSharedPtr<FString> GetSelectedItem();
/**
* Requests a list refresh after updating options
* Call SetSelectedItem to update the selected item if required
* @see SetSelectedItem
*/
void RefreshOptions();
protected:
/** Set ths source data for this combo box */
void SetOptionsSource(const TArray< TSharedPtr<FString> >* InOptionsSource);
private:
/** Generate a row for the InItem in the combo box's list (passed in as OwnerTable). Do this by calling the user-specified OnGenerateWidget */
TSharedRef<ITableRow> GenerateMenuItemRow(TSharedPtr<FString> InItem, const TSharedRef<STableViewBase>& OwnerTable);
/** Called if the menu is closed */
void OnMenuOpenChanged(bool bOpen);
/** Invoked when the selection in the list changes */
void OnSelectionChanged_Internal(TSharedPtr<FString> ProposedSelection, ESelectInfo::Type SelectInfo);
/** Invoked when the search text changes */
void OnSearchTextChanged(const FText& ChangedText);
/** Sets the current selection to the first valid match when user presses enter in the filter box */
void OnSearchTextCommitted(const FText& InText, ETextCommit::Type InCommitType);
/** Handle clicking on the content menu */
virtual FReply OnButtonClicked() override;
/** The item style to use. */
const FTableRowStyle* ItemStyle;
/** The padding around each menu row */
FMargin MenuRowPadding;
private:
/** Delegate that is invoked when the selected item in the combo box changes */
FOnSelectionChanged OnSelectionChanged;
/** The item currently selected in the combo box */
TSharedPtr<FString> SelectedItem;
/** The search field used for the combox box's contents */
TSharedPtr<SEditableTextBox> SearchField;
/** The ListView that we pop up; visualized the available options. */
TSharedPtr< SComboListType > ComboListView;
/** The Scrollbar used in the ListView. */
TSharedPtr< SScrollBar > CustomScrollbar;
/** Delegate to invoke before the combo box is opening. */
FOnComboBoxOpening OnComboBoxOpening;
/** Delegate to invoke when we need to visualize an option as a widget. */
FOnGenerateWidget OnGenerateWidget;
/** Updated whenever search text is changed */
FText SearchText;
/** Source data for this combo box */
const TArray< TSharedPtr<FString> >* OptionsSource;
/** Filtered list that is actually displayed */
TArray< TSharedPtr<FString> > FilteredOptionsSource;
};
SMySearchableComboBox.cpp
// Copyright Epic Games, Inc. All Rights Reservekd.
#include "SMySearchableComboBox.h"
#include "Framework/Application/SlateUser.h"
#define LOCTEXT_NAMESPACE "SearchableComboBox"
void SMySearchableComboBox::Construct(const FArguments& InArgs)
{
check(InArgs._ComboBoxStyle);
ItemStyle = InArgs._ItemStyle;
MenuRowPadding = InArgs._ComboBoxStyle->MenuRowPadding;
// Work out which values we should use based on whether we were given an override, or should use the style's version
const FComboButtonStyle& OurComboButtonStyle = InArgs._ComboBoxStyle->ComboButtonStyle;
const FButtonStyle* const OurButtonStyle = InArgs._ButtonStyle ? InArgs._ButtonStyle : &OurComboButtonStyle.ButtonStyle;
this->OnComboBoxOpening = InArgs._OnComboBoxOpening;
this->OnSelectionChanged = InArgs._OnSelectionChanged;
this->OnGenerateWidget = InArgs._OnGenerateWidget;
OptionsSource = InArgs._OptionsSource;
CustomScrollbar = InArgs._CustomScrollbar;
FilteredOptionsSource.Append(*OptionsSource);
TAttribute<EVisibility> SearchVisibility = InArgs._SearchVisibility;
const EVisibility CurrentSearchVisibility = SearchVisibility.Get();
TSharedRef<SWidget> ComboBoxMenuContent =
SNew(SBox)
.MaxDesiredHeight(InArgs._MaxListHeight)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(this->SearchField, SEditableTextBox)
.HintText(LOCTEXT("Search", "Search"))
.OnTextChanged(this, &SMySearchableComboBox::OnSearchTextChanged)
.OnTextCommitted(this, &SMySearchableComboBox::OnSearchTextCommitted)
.Visibility(SearchVisibility)
]
+ SVerticalBox::Slot()
[
SAssignNew(this->ComboListView, SComboListType)
.ListItemsSource(&FilteredOptionsSource)
.OnGenerateRow(this, &SMySearchableComboBox::GenerateMenuItemRow)
.OnSelectionChanged(this, &SMySearchableComboBox::OnSelectionChanged_Internal)
.SelectionMode(ESelectionMode::Single)
.ExternalScrollbar(InArgs._CustomScrollbar)
]
];
// Set up content
TSharedPtr<SWidget> ButtonContent = InArgs._Content.Widget;
if (InArgs._Content.Widget == SNullWidget::NullWidget)
{
SAssignNew(ButtonContent, STextBlock)
.Text(NSLOCTEXT("SMySearchableComboBox", "ContentWarning", "No Content Provided"))
.ColorAndOpacity(FLinearColor::Red);
}
SComboButton::Construct(SComboButton::FArguments()
.ComboButtonStyle(&OurComboButtonStyle)
.ButtonStyle(OurButtonStyle)
.Method(InArgs._Method)
.ButtonContent()
[
ButtonContent.ToSharedRef()
]
.MenuContent()
[
ComboBoxMenuContent
]
.HasDownArrow(InArgs._HasDownArrow)
.ContentPadding(InArgs._ContentPadding)
.ForegroundColor(InArgs._ForegroundColor)
.OnMenuOpenChanged(this, &SMySearchableComboBox::OnMenuOpenChanged)
.IsFocusable(true)
);
if (CurrentSearchVisibility == EVisibility::Visible)
{
SetMenuContentWidgetToFocus(SearchField);
}
else
{
SetMenuContentWidgetToFocus(ComboListView);
}
// Need to establish the selected item at point of construction so its available for querying
// NB: If you need a selection to fire use SetItemSelection rather than setting an IntiallySelectedItem
SelectedItem = InArgs._InitiallySelectedItem;
if (TListTypeTraits<TSharedPtr<FString>>::IsPtrValid(SelectedItem))
{
ComboListView->Private_SetItemSelection(SelectedItem, true);
}
}
void SMySearchableComboBox::ClearSelection()
{
ComboListView->ClearSelection();
}
void SMySearchableComboBox::SetSelectedItem(TSharedPtr<FString> InSelectedItem)
{
if (TListTypeTraits<TSharedPtr<FString>>::IsPtrValid(InSelectedItem))
{
ComboListView->SetSelection(InSelectedItem, ESelectInfo::OnNavigation);
}
else
{
ComboListView->SetSelection(SelectedItem, ESelectInfo::OnNavigation);
}
}
TSharedPtr<FString> SMySearchableComboBox::GetSelectedItem()
{
return SelectedItem;
}
void SMySearchableComboBox::RefreshOptions()
{
// Need to refresh filtered list whenever options change
FilteredOptionsSource.Reset();
if (SearchText.IsEmpty())
{
FilteredOptionsSource.Append(*OptionsSource);
}
else
{
for (TSharedPtr<FString> Option : *OptionsSource)
{
if (Option->Find(SearchText.ToString(), ESearchCase::Type::IgnoreCase) >= 0)
{
FilteredOptionsSource.Add(Option);
}
}
}
ComboListView->RequestListRefresh();
}
void SMySearchableComboBox::SetOptionsSource(const TArray<TSharedPtr<FString>>* InOptionsSource)
{
OptionsSource = InOptionsSource;
FilteredOptionsSource.Empty();
FilteredOptionsSource.Append(*OptionsSource);
}
TSharedRef<ITableRow> SMySearchableComboBox::GenerateMenuItemRow(TSharedPtr<FString> InItem, const TSharedRef<STableViewBase>& OwnerTable)
{
if (OnGenerateWidget.IsBound())
{
return SNew(SComboRow<TSharedPtr<FString>>, OwnerTable)
.Style(ItemStyle)
.Padding(MenuRowPadding)
[
OnGenerateWidget.Execute(InItem)
];
}
else
{
return SNew(SComboRow<TSharedPtr<FString>>, OwnerTable)
[
SNew(STextBlock).Text(NSLOCTEXT("SlateCore", "ComboBoxMissingOnGenerateWidgetMethod", "Please provide a .OnGenerateWidget() handler."))
];
}
}
void SMySearchableComboBox::OnMenuOpenChanged(bool bOpen)
{
if (bOpen == false)
{
if (TListTypeTraits<TSharedPtr<FString>>::IsPtrValid(SelectedItem))
{
// Ensure the ListView selection is set back to the last committed selection
ComboListView->SetSelection(SelectedItem, ESelectInfo::OnNavigation);
}
// Set focus back to ComboBox for users focusing the ListView that just closed
TSharedRef<SWidget> ThisRef = AsShared();
FSlateApplication::Get().ForEachUser([&ThisRef](FSlateUser& User) {
if (User.HasFocusedDescendants(ThisRef))
{
User.SetFocus(ThisRef);
}
});
}
}
void SMySearchableComboBox::OnSelectionChanged_Internal(TSharedPtr<FString> ProposedSelection, ESelectInfo::Type SelectInfo)
{
if (!ProposedSelection)
{
return;
}
// Ensure that the proposed selection is different from selected
if (ProposedSelection != SelectedItem)
{
SelectedItem = ProposedSelection;
OnSelectionChanged.ExecuteIfBound(ProposedSelection, SelectInfo);
}
// close combo as long as the selection wasn't from navigation
if (SelectInfo != ESelectInfo::OnNavigation)
{
this->SetIsOpen(false);
}
else
{
ComboListView->RequestScrollIntoView(SelectedItem, 0);
}
}
void SMySearchableComboBox::OnSearchTextChanged(const FText& ChangedText)
{
SearchText = ChangedText;
RefreshOptions();
}
void SMySearchableComboBox::OnSearchTextCommitted(const FText& InText, ETextCommit::Type InCommitType)
{
if ((InCommitType == ETextCommit::Type::OnEnter) && FilteredOptionsSource.Num() > 0)
{
ComboListView->SetSelection(FilteredOptionsSource[0], ESelectInfo::OnKeyPress);
}
}
FReply SMySearchableComboBox::OnButtonClicked()
{
// if user clicked to close the combo menu
if (this->IsOpen())
{
// Re-select first selected item, just in case it was selected by navigation previously
TArray<TSharedPtr<FString>> SelectedItems = ComboListView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
OnSelectionChanged_Internal(SelectedItems[0], ESelectInfo::Direct);
}
}
else
{
SearchField->SetText(FText::GetEmpty());
OnComboBoxOpening.ExecuteIfBound();
}
return SComboButton::OnButtonClicked();
}
#undef LOCTEXT_NAMESPACE
MyComboBoxStringSearchable.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Fonts/SlateFontInfo.h"
#include "Layout/Margin.h"
#include "Styling/SlateColor.h"
#include "Styling/SlateTypes.h"
#include "Widgets/SWidget.h"
#include "SMySearchableComboBox.h"
#include "Widgets/Input/SComboBox.h"
#include "Components/Widget.h"
#include "MyComboBoxStringSearchable.generated.h"
/**
* The combobox allows you to display a list of options to the user in a dropdown menu for them to select one.
*/
UCLASS(meta=( DisplayName="MyComboBoxStringSearch (String)"))
class TESTMOUSEINPUT_API UMyComboBoxStringSearchable : public UWidget
{
GENERATED_UCLASS_BODY()
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSelectionChangedEvent, FString, SelectedItem, ESelectInfo::Type, SelectionType);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnOpeningEvent);
private:
/** The default list of items to be displayed on the combobox. */
UPROPERTY(EditAnywhere, Category=Content)
TArray<FString> DefaultOptions;
/** The item in the combobox to select by default */
UPROPERTY(EditAnywhere, Category=Content)
FString SelectedOption;
public:
/** The style. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Style, meta=( DisplayName="Style" ))
FComboBoxStyle WidgetStyle;
/** The item row style. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Style)
FTableRowStyle ItemStyle;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Content)
FMargin ContentPadding;
/** The max height of the combobox list that opens */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Content, AdvancedDisplay)
float MaxListHeight;
/**
* When false, the down arrow is not generated and it is up to the API consumer
* to make their own visual hint that this is a drop down.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Content, AdvancedDisplay)
bool HasDownArrow;
/**
* When false, directional keys will change the selection. When true, ComboBox
* must be activated and will only capture arrow input while activated.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Content, AdvancedDisplay)
bool EnableGamepadNavigationMode;
/**
* The default font to use in the combobox, only applies if you're not implementing OnGenerateWidgetEvent
* to factory each new entry.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Style)
FSlateFontInfo Font;
/** The foreground color to pass through the hierarchy. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Style, meta=(DesignerRebuild))
FSlateColor ForegroundColor;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Interaction)
bool bIsFocusable;
public: // Events
/** Called when the widget is needed for the item. */
UPROPERTY(EditAnywhere, Category=Events, meta=( IsBindableEvent="True" ))
FGenerateWidgetForString OnGenerateWidgetEvent;
/** Called when a new item is selected in the combobox. */
UPROPERTY(BlueprintAssignable, Category=Events)
FOnSelectionChangedEvent OnSelectionChanged;
/** Called when the combobox is opening */
UPROPERTY(BlueprintAssignable, Category=Events)
FOnOpeningEvent OnOpening;
public:
UFUNCTION(BlueprintCallable, Category="ComboBox")
void AddOption(const FString& Option);
UFUNCTION(BlueprintCallable, Category="ComboBox")
bool RemoveOption(const FString& Option);
UFUNCTION(BlueprintCallable, Category="ComboBox")
int32 FindOptionIndex(const FString& Option) const;
UFUNCTION(BlueprintCallable, Category="ComboBox")
FString GetOptionAtIndex(int32 Index) const;
UFUNCTION(BlueprintCallable, Category="ComboBox")
void ClearOptions();
UFUNCTION(BlueprintCallable, Category="ComboBox")
void ClearSelection();
/**
* Refreshes the list of options. If you added new ones, and want to update the list even if it's
* currently being displayed use this.
*/
UFUNCTION(BlueprintCallable, Category="ComboBox")
void RefreshOptions();
UFUNCTION(BlueprintCallable, Category="ComboBox")
void SetSelectedOption(FString Option);
UFUNCTION(BlueprintCallable, Category = "ComboBox")
void SetSelectedIndex(const int32 Index);
UFUNCTION(BlueprintCallable, Category="ComboBox")
FString GetSelectedOption() const;
UFUNCTION(BlueprintCallable, Category="ComboBox")
int32 GetSelectedIndex() const;
/** Returns the number of options */
UFUNCTION(BlueprintCallable, Category="ComboBox")
int32 GetOptionCount() const;
UFUNCTION(BlueprintCallable, Category="ComboBox", Meta = (ReturnDisplayName = "bOpen"))
bool IsOpen() const;
//~ Begin UVisual Interface
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
//~ End UVisual Interface
//~ Begin UObject Interface
virtual void PostInitProperties() override;
virtual void Serialize(FArchive& Ar) override;
virtual void PostLoad() override;
//~ End UObject Interface
#if WITH_EDITOR
virtual const FText GetPaletteCategory() override;
#endif
protected:
/** Refresh ComboBoxContent with the correct widget/data when the selected option changes */
void UpdateOrGenerateWidget(TSharedPtr<FString> Item);
/** Called by slate when it needs to generate a new item for the combobox */
virtual TSharedRef<SWidget> HandleGenerateWidget(TSharedPtr<FString> Item) const;
/** Called by slate when the underlying combobox selection changes */
virtual void HandleSelectionChanged(TSharedPtr<FString> Item, ESelectInfo::Type SelectionType);
/** Called by slate when the underlying combobox is opening */
virtual void HandleOpening();
//~ Begin UWidget Interface
virtual TSharedRef<SWidget> RebuildWidget() override;
//~ End UWidget Interface
protected:
/** The true objects bound to the Slate combobox. */
TArray< TSharedPtr<FString> > Options;
/** A shared pointer to the underlying slate combobox */
TSharedPtr< SMySearchableComboBox > MyComboBox;
/** A shared pointer to a container that holds the combobox content that is selected */
TSharedPtr< SBox > ComboBoxContent;
/** If OnGenerateWidgetEvent is not bound, this will store the default STextBlock generated */
TWeakPtr<STextBlock> DefaultComboBoxContent;
/** A shared pointer to the current selected string */
TSharedPtr<FString> CurrentOptionPtr;
};
MyComboBoxStringSearchable.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MyComboBoxStringSearchable.h"
#include "Widgets/SNullWidget.h"
#include "UObject/EditorObjectVersion.h"
#include "UObject/ConstructorHelpers.h"
#include "Engine/Font.h"
#include "Styling/UMGCoreStyle.h"
#define LOCTEXT_NAMESPACE "MyComboBoxStringSearchable"
/////////////////////////////////////////////////////
// UComboBoxString
static FComboBoxStyle* DefaultComboBoxStyle = nullptr;
static FTableRowStyle* DefaultComboBoxRowStyle = nullptr;
#if WITH_EDITOR
static FComboBoxStyle* EditorComboBoxStyle = nullptr;
static FTableRowStyle* EditorComboBoxRowStyle = nullptr;
#endif
UMyComboBoxStringSearchable::UMyComboBoxStringSearchable(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
if (DefaultComboBoxStyle == nullptr)
{
DefaultComboBoxStyle = new FComboBoxStyle(FUMGCoreStyle::Get().GetWidgetStyle<FComboBoxStyle>("ComboBox"));
// Unlink UMG default colors.
DefaultComboBoxStyle->UnlinkColors();
}
if (DefaultComboBoxRowStyle == nullptr)
{
DefaultComboBoxRowStyle = new FTableRowStyle(FUMGCoreStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.Row"));
// Unlink UMG default colors.
DefaultComboBoxRowStyle->UnlinkColors();
}
WidgetStyle = *DefaultComboBoxStyle;
ItemStyle = *DefaultComboBoxRowStyle;
#if WITH_EDITOR
if (EditorComboBoxStyle == nullptr)
{
EditorComboBoxStyle = new FComboBoxStyle(FCoreStyle::Get().GetWidgetStyle<FComboBoxStyle>("EditorUtilityComboBox"));
// Unlink UMG Editor colors from the editor settings colors.
EditorComboBoxStyle->UnlinkColors();
}
if (EditorComboBoxRowStyle == nullptr)
{
EditorComboBoxRowStyle = new FTableRowStyle(FCoreStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.Row"));
// Unlink UMG Editor colors from the editor settings colors.
EditorComboBoxRowStyle->UnlinkColors();
}
if (IsEditorWidget())
{
WidgetStyle = *EditorComboBoxStyle;
ItemStyle = *EditorComboBoxRowStyle;
// The CDO isn't an editor widget and thus won't use the editor style, call post edit change to mark difference from CDO
PostEditChange();
}
#endif // WITH_EDITOR
WidgetStyle.UnlinkColors();
ItemStyle.UnlinkColors();
ForegroundColor = ItemStyle.TextColor;
bIsFocusable = true;
ContentPadding = FMargin(4.0, 2.0);
MaxListHeight = 450.0f;
HasDownArrow = true;
EnableGamepadNavigationMode = true;
// We don't want to try and load fonts on the server.
if ( !IsRunningDedicatedServer() )
{
static ConstructorHelpers::FObjectFinder<UFont> RobotoFontObj(*UWidget::GetDefaultFontName());
Font = FSlateFontInfo(RobotoFontObj.Object, 16, FName("Bold"));
}
}
void UMyComboBoxStringSearchable::PostInitProperties()
{
Super::PostInitProperties();
// Initialize the set of options from the default set only once.
for (const FString& DefaultOption : DefaultOptions)
{
AddOption(DefaultOption);
}
}
void UMyComboBoxStringSearchable::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
MyComboBox.Reset();
ComboBoxContent.Reset();
}
void UMyComboBoxStringSearchable::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FEditorObjectVersion::GUID);
}
void UMyComboBoxStringSearchable::PostLoad()
{
Super::PostLoad();
// Initialize the set of options from the default set only once.
for (const FString& DefaultOption : DefaultOptions)
{
AddOption(DefaultOption);
}
if (GetLinkerCustomVersion(FEditorObjectVersion::GUID) < FEditorObjectVersion::ComboBoxControllerSupportUpdate)
{
EnableGamepadNavigationMode = false;
}
}
TSharedRef<SWidget> UMyComboBoxStringSearchable::RebuildWidget()
{
int32 InitialIndex = FindOptionIndex(SelectedOption);
if ( InitialIndex != -1 )
{
CurrentOptionPtr = Options[InitialIndex];
}
MyComboBox =
SNew(SMySearchableComboBox)
.ComboBoxStyle(&WidgetStyle)
.ItemStyle(&ItemStyle)
.ForegroundColor(ForegroundColor)
.OptionsSource(&Options)
.InitiallySelectedItem(CurrentOptionPtr)
.ContentPadding(ContentPadding)
.MaxListHeight(MaxListHeight)
.HasDownArrow(HasDownArrow)
//.EnableGamepadNavigationMode(EnableGamepadNavigationMode)
.OnGenerateWidget(BIND_UOBJECT_DELEGATE(SMySearchableComboBox::FOnGenerateWidget, HandleGenerateWidget))
.OnSelectionChanged(BIND_UOBJECT_DELEGATE(SMySearchableComboBox::FOnSelectionChanged, HandleSelectionChanged))
.OnComboBoxOpening(BIND_UOBJECT_DELEGATE(FOnComboBoxOpening, HandleOpening))
//.IsFocusable(bIsFocusable)
[
SAssignNew(ComboBoxContent, SBox)
];
if ( InitialIndex != -1 )
{
// Generate the widget for the initially selected widget if needed
UpdateOrGenerateWidget(CurrentOptionPtr);
}
return MyComboBox.ToSharedRef();
}
void UMyComboBoxStringSearchable::AddOption(const FString& Option)
{
Options.Add(MakeShareable(new FString(Option)));
RefreshOptions();
}
bool UMyComboBoxStringSearchable::RemoveOption(const FString& Option)
{
int32 OptionIndex = FindOptionIndex(Option);
if ( OptionIndex != -1 )
{
if ( Options[OptionIndex] == CurrentOptionPtr )
{
ClearSelection();
}
Options.RemoveAt(OptionIndex);
RefreshOptions();
return true;
}
return false;
}
int32 UMyComboBoxStringSearchable::FindOptionIndex(const FString& Option) const
{
for ( int32 OptionIndex = 0; OptionIndex < Options.Num(); OptionIndex++ )
{
const TSharedPtr<FString>& OptionAtIndex = Options[OptionIndex];
if ( ( *OptionAtIndex ) == Option )
{
return OptionIndex;
}
}
return -1;
}
FString UMyComboBoxStringSearchable::GetOptionAtIndex(int32 Index) const
{
if (Index >= 0 && Index < Options.Num())
{
return *(Options[Index]);
}
return FString();
}
void UMyComboBoxStringSearchable::ClearOptions()
{
ClearSelection();
Options.Empty();
if ( MyComboBox.IsValid() )
{
MyComboBox->RefreshOptions();
}
}
void UMyComboBoxStringSearchable::ClearSelection()
{
CurrentOptionPtr.Reset();
if ( MyComboBox.IsValid() )
{
MyComboBox->ClearSelection();
}
if ( ComboBoxContent.IsValid() )
{
ComboBoxContent->SetContent(SNullWidget::NullWidget);
}
}
void UMyComboBoxStringSearchable::RefreshOptions()
{
if ( MyComboBox.IsValid() )
{
MyComboBox->RefreshOptions();
}
}
void UMyComboBoxStringSearchable::SetSelectedOption(FString Option)
{
int32 InitialIndex = FindOptionIndex(Option);
SetSelectedIndex(InitialIndex);
}
void UMyComboBoxStringSearchable::SetSelectedIndex(const int32 Index)
{
if (Options.IsValidIndex(Index))
{
CurrentOptionPtr = Options[Index];
// Don't select item if its already selected
if (SelectedOption != *CurrentOptionPtr)
{
SelectedOption = *CurrentOptionPtr;
if (ComboBoxContent.IsValid())
{
MyComboBox->SetSelectedItem(CurrentOptionPtr);
UpdateOrGenerateWidget(CurrentOptionPtr);
}
else
{
HandleSelectionChanged(CurrentOptionPtr, ESelectInfo::Direct);
}
}
}
}
FString UMyComboBoxStringSearchable::GetSelectedOption() const
{
if (CurrentOptionPtr.IsValid())
{
return *CurrentOptionPtr;
}
return FString();
}
int32 UMyComboBoxStringSearchable::GetSelectedIndex() const
{
if (CurrentOptionPtr.IsValid())
{
for (int32 OptionIndex = 0; OptionIndex < Options.Num(); ++OptionIndex)
{
if (Options[OptionIndex] == CurrentOptionPtr)
{
return OptionIndex;
}
}
}
return -1;
}
int32 UMyComboBoxStringSearchable::GetOptionCount() const
{
return Options.Num();
}
bool UMyComboBoxStringSearchable::IsOpen() const
{
return MyComboBox.IsValid() && MyComboBox->IsOpen();
}
void UMyComboBoxStringSearchable::UpdateOrGenerateWidget(TSharedPtr<FString> Item)
{
// If no custom widget was supplied and the default STextBlock already exists,
// just update its text instead of rebuilding the widget.
if (DefaultComboBoxContent.IsValid() && (IsDesignTime() || OnGenerateWidgetEvent.IsBound()))
{
const FString StringItem = Item.IsValid() ? *Item : FString();
DefaultComboBoxContent.Pin()->SetText(FText::FromString(StringItem));
}
else
{
DefaultComboBoxContent.Reset();
ComboBoxContent->SetContent(HandleGenerateWidget(Item));
}
}
TSharedRef<SWidget> UMyComboBoxStringSearchable::HandleGenerateWidget(TSharedPtr<FString> Item) const
{
FString StringItem = Item.IsValid() ? *Item : FString();
// Call the user's delegate to see if they want to generate a custom widget bound to the data source.
if ( !IsDesignTime() && OnGenerateWidgetEvent.IsBound() )
{
UWidget* Widget = OnGenerateWidgetEvent.Execute(StringItem);
if ( Widget != NULL )
{
return Widget->TakeWidget();
}
}
// If a row wasn't generated just create the default one, a simple text block of the item's name.
return SNew(STextBlock)
.Text(FText::FromString(StringItem))
.Font(Font);
}
void UMyComboBoxStringSearchable::HandleSelectionChanged(TSharedPtr<FString> Item, ESelectInfo::Type SelectionType)
{
CurrentOptionPtr = Item;
SelectedOption = CurrentOptionPtr.IsValid() ? CurrentOptionPtr.ToSharedRef().Get() : FString();
// When the selection changes we always generate another widget to represent the content area of the combobox.
if ( ComboBoxContent.IsValid() )
{
UpdateOrGenerateWidget(CurrentOptionPtr);
}
if ( !IsDesignTime() )
{
OnSelectionChanged.Broadcast(Item.IsValid() ? *Item : FString(), SelectionType);
}
}
void UMyComboBoxStringSearchable::HandleOpening()
{
OnOpening.Broadcast();
}
#if WITH_EDITOR
const FText UMyComboBoxStringSearchable::GetPaletteCategory()
{
return LOCTEXT("Input", "Input");
}
#endif
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE
修改testmouseinput.Build.cs文件
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "Slate", "SlateCore" });
最终效果
总结
实际上该控件是在列表上添加一个搜索的编辑框,还需要改进,直接替换掉显示框