赞
踩
为了实现与UserWidget一致的右键添加,便有了此章
注:这里使用的是UE5.3
这里可以参考UserWidget的源码,拷贝一份属于自己的就ok(本篇章只是全改成了属于自己的CommonUserWidget)
首先创建一份自己Runtime模块的对象,我这取名是UCommonUserWidget
.h
#pragma once
#include "Blueprint/UserWidget.h"
#include "CommonUserWidget.generated.h"
UCLASS(BlueprintType, Blueprintable)
class DIVINEPROLOGUE_API UCommonUserWidget : public UUserWidget
{
GENERATED_BODY()
public:
}
};
主要实现内容是:UFactory
这里需要创建一个Editor模块,添加以下代码:
.h
//这个创建是因为本着改了都改,就创建了一份
#pragma once
#include "WidgetBlueprint.h"
#include "CommonWidgetBlueprint.generated.h"
/**
* The widget blueprint enables extending UCommonWidgetBlueprint the user extensible UWidget.
*/
UCLASS(BlueprintType)
class UCommonWidgetBlueprint : public UWidgetBlueprint
{
GENERATED_BODY()
public:
UCommonWidgetBlueprint(){}
};
.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "Templates/SubclassOf.h" #include "Factories/Factory.h" #include "Engine/Blueprint.h" #include "CommonUserWidgetFactory.generated.h" UCLASS(HideCategories=Object, MinimalAPI) class UCommonUserWidgetFactory : public UFactory { GENERATED_UCLASS_BODY() // The type of blueprint that will be created UPROPERTY(EditAnywhere, Category=WidgetBlueprintFactory) TEnumAsByte<enum EBlueprintType> BlueprintType; // The parent class of the created blueprint UPROPERTY(EditAnywhere, Category=WidgetBlueprintFactory, meta=(AllowAbstract = "")) TSubclassOf<class UUserWidget> ParentClass; //~ Begin UFactory Interface virtual bool ConfigureProperties() override; virtual bool ShouldShowInNewMenu() const override; virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn, FName CallingContext) override; virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; //~ Begin UFactory Interface private: UPROPERTY(Transient) TObjectPtr<UClass> RootWidgetClass; };
.cpp
// Copyright Epic Games, Inc. All Rights Reserved. #include "CommonUserWidgetFactory.h" #include "UObject/Interface.h" #include "Misc/MessageDialog.h" #include "Blueprint/UserWidget.h" #include "Blueprint/WidgetBlueprintGeneratedClass.h" #include "WidgetBlueprint.h" #include "Kismet2/KismetEditorUtilities.h" #include "Modules/ModuleManager.h" #include "UMGEditorModule.h" #include "Blueprint/WidgetTree.h" #include "UMGEditorProjectSettings.h" #include "ClassViewerModule.h" #include "Kismet2/SClassPickerDialog.h" #include "ClassViewerFilter.h" #include "CommonUserWidget.h" #include "CommonWidgetBlueprint.h" #include "Components/CanvasPanel.h" #define LOCTEXT_NAMESPACE "UCommonUserWidgetFactory" /*------------------------------------------------------------------------------ UCommonUserWidgetFactory implementation. ------------------------------------------------------------------------------*/ class FWidgetClassFilter : public IClassViewerFilter { public: /** All children of these classes will be included unless filtered out by another setting. */ TSet <const UClass*> AllowedChildrenOfClasses; /** Disallowed class flags. */ EClassFlags DisallowedClassFlags; virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { return !InClass->HasAnyClassFlags(DisallowedClassFlags) && InFilterFuncs->IfInChildOfClassesSet(AllowedChildrenOfClasses, InClass) != EFilterReturn::Failed; } virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { return !InUnloadedClassData->HasAnyClassFlags(DisallowedClassFlags) && InFilterFuncs->IfInChildOfClassesSet(AllowedChildrenOfClasses, InUnloadedClassData) != EFilterReturn::Failed; } }; UCommonUserWidgetFactory::UCommonUserWidgetFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bCreateNew = true; bEditAfterNew = true; SupportedClass = UCommonWidgetBlueprint::StaticClass(); ParentClass = nullptr; } bool UCommonUserWidgetFactory::ConfigureProperties() { if (GetDefault<UUMGEditorProjectSettings>()->bUseUserWidgetParentClassViewerSelector || GetDefault<UUMGEditorProjectSettings>()->bUseUserWidgetParentDefaultClassViewerSelector) { FClassViewerModule& ClassViewerModule = FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer"); // Fill in options FClassViewerInitializationOptions Options; Options.DisplayMode = EClassViewerDisplayMode::Type::TreeView; Options.Mode = EClassViewerMode::ClassPicker; Options.bShowNoneOption = false; Options.bExpandAllNodes = true; Options.bShowDefaultClasses = GetDefault<UUMGEditorProjectSettings>()->bUseUserWidgetParentDefaultClassViewerSelector; Options.bShowClassesViewer = GetDefault<UUMGEditorProjectSettings>()->bUseUserWidgetParentClassViewerSelector; TSharedPtr<FWidgetClassFilter> Filter = MakeShareable(new FWidgetClassFilter); Options.ClassFilters.Add(Filter.ToSharedRef()); const TArray<TSoftClassPtr<UUserWidget>>& FavoriteWidgetParentClasses = GetDefault<UUMGEditorProjectSettings>()->FavoriteWidgetParentClasses; for (int32 Index = 0; Index < FavoriteWidgetParentClasses.Num(); ++Index) { UClass* FavoriteWidgetParentClass = FavoriteWidgetParentClasses[Index].LoadSynchronous(); if (FavoriteWidgetParentClass && FavoriteWidgetParentClass->IsChildOf(UCommonUserWidget::StaticClass())) { if (!Options.ExtraPickerCommonClasses.Contains(FavoriteWidgetParentClass)) { Options.ExtraPickerCommonClasses.Add(FavoriteWidgetParentClass); } } } if (Options.ExtraPickerCommonClasses.Num() == 0) { Options.ExtraPickerCommonClasses.Add(UCommonUserWidget::StaticClass()); } Filter->DisallowedClassFlags = CLASS_Deprecated | CLASS_NewerVersionExists | CLASS_Hidden | CLASS_HideDropDown; Filter->AllowedChildrenOfClasses.Add(UCommonUserWidget::StaticClass()); const FText TitleText = LOCTEXT("CreateCommonWidgetBlueprint", "Pick Parent Class for New Widget Blueprint"); UClass* ChosenParentClass = nullptr; bool isSuccessful = SClassPickerDialog::PickClass(TitleText, Options, ChosenParentClass, UCommonUserWidget::StaticClass()); ParentClass = ChosenParentClass ? ChosenParentClass : UCommonUserWidget::StaticClass(); if (!isSuccessful) { return false; } } if (GetDefault<UUMGEditorProjectSettings>()->bUseWidgetTemplateSelector) { // Load the classviewer module to display a class picker FClassViewerModule& ClassViewerModule = FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer"); // Fill in options FClassViewerInitializationOptions Options; Options.Mode = EClassViewerMode::ClassPicker; Options.bShowNoneOption = true; TArray<TSoftClassPtr<UPanelWidget>> CommonRootWidgetClasses = GetDefault <UUMGEditorProjectSettings>()->CommonRootWidgetClasses; for (int32 Index = 0; Index < CommonRootWidgetClasses.Num(); ++Index) { UClass* PanelWidgetClass = CommonRootWidgetClasses[Index].LoadSynchronous(); if (PanelWidgetClass && PanelWidgetClass->IsChildOf(UPanelWidget::StaticClass())) { if (!Options.ExtraPickerCommonClasses.Contains(PanelWidgetClass)) { Options.ExtraPickerCommonClasses.Add(PanelWidgetClass); } } } if (Options.ExtraPickerCommonClasses.Num() == 0) { Options.ExtraPickerCommonClasses.Add(UCanvasPanel::StaticClass()); } TSharedPtr<FWidgetClassFilter> Filter = MakeShareable(new FWidgetClassFilter); Options.ClassFilters.Add(Filter.ToSharedRef()); Filter->DisallowedClassFlags = CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists; Filter->AllowedChildrenOfClasses.Add(UPanelWidget::StaticClass()); const FText TitleText = LOCTEXT("CreateRootWidgetBlueprint", "Pick Root Widget for New Widget Blueprint"); return SClassPickerDialog::PickClass(TitleText, Options, static_cast<UClass*&>(RootWidgetClass), UPanelWidget::StaticClass()); } return true; } bool UCommonUserWidgetFactory::ShouldShowInNewMenu() const { return true; } UObject* UCommonUserWidgetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn, FName CallingContext) { // Make sure we are trying to factory a Anim Blueprint, then create and init one check(Class->IsChildOf(UCommonWidgetBlueprint::StaticClass())); UClass* CurrentParentClass = ParentClass; if (CurrentParentClass == nullptr) { CurrentParentClass = UCommonUserWidget::StaticClass(); } // If they selected an interface, force the parent class to be UInterface if (BlueprintType == BPTYPE_Interface) { CurrentParentClass = UInterface::StaticClass(); } if ( (CurrentParentClass == nullptr) || !FKismetEditorUtilities::CanCreateBlueprintOfClass(CurrentParentClass) || !CurrentParentClass->IsChildOf(UCommonUserWidget::StaticClass()) ) { FFormatNamedArguments Args; Args.Add( TEXT("ClassName"), CurrentParentClass ? FText::FromString( CurrentParentClass->GetName()) : LOCTEXT("Null", "(null)") ); FMessageDialog::Open( EAppMsgType::Ok, FText::Format( LOCTEXT("CannotCreateCommonWidgetBlueprint", "Cannot create a Common Widget Blueprint based on the class '{ClassName}'."), Args ) ); return nullptr; } else { if (!GetDefault<UUMGEditorProjectSettings>()->bUseWidgetTemplateSelector) { RootWidgetClass = GetDefault<UUMGEditorProjectSettings>()->DefaultRootWidget; } UCommonWidgetBlueprint* NewBP = CastChecked<UCommonWidgetBlueprint>(FKismetEditorUtilities::CreateBlueprint(CurrentParentClass, InParent, Name, BlueprintType, UCommonWidgetBlueprint::StaticClass(), UWidgetBlueprintGeneratedClass::StaticClass(), CallingContext)); // Create the desired root widget specified by the project if ( NewBP->WidgetTree->RootWidget == nullptr ) { if (TSubclassOf<UPanelWidget> RootWidgetPanel = RootWidgetClass) { UWidget* Root = NewBP->WidgetTree->ConstructWidget<UWidget>(RootWidgetPanel); NewBP->WidgetTree->RootWidget = Root; } } { IUMGEditorModule::FWidgetBlueprintCreatedArgs Args; Args.ParentClass = CurrentParentClass; Args.Blueprint = NewBP; IUMGEditorModule& UMGEditor = FModuleManager::LoadModuleChecked<IUMGEditorModule>("UMGEditor"); UMGEditor.OnWidgetBlueprintCreated().Broadcast(Args); } return NewBP; } } UObject* UCommonUserWidgetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) { return FactoryCreateNew(Class, InParent, Name, Flags, Context, Warn, NAME_None); } #undef LOCTEXT_NAMESPACE
这样运行起来之后,会发现右键内容已经有了但是,名字不对,类型也不对,但是已经能正常创建出来了,因为我们还需要创建一个UAssetDefinitionDefault
.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "AssetDefinitionDefault.h" #include "AssetDefinition_CommonWidgetBlueprint.generated.h" UCLASS() class UAssetDefinition_CommonWidgetBlueprint : public UAssetDefinitionDefault { GENERATED_BODY() public: UAssetDefinition_CommonWidgetBlueprint(); virtual ~UAssetDefinition_CommonWidgetBlueprint() override; // UAssetDefinition Begin virtual FText GetAssetDisplayName() const override; virtual FLinearColor GetAssetColor() const override; virtual TSoftClassPtr<UObject> GetAssetClass() const override; virtual TConstArrayView<FAssetCategoryPath> GetAssetCategories() const override; virtual EAssetCommandResult OpenAssets(const FAssetOpenArgs& OpenArgs) const override; virtual EAssetCommandResult PerformAssetDiff(const FAssetDiffArgs& DiffArgs) const override; virtual FText GetAssetDescription(const FAssetData& AssetData) const override; // UAssetDefinition End };
.cpp
#include "AssetDefinition_CommonWidgetBlueprint.h" #include "CommonWidgetBlueprint.h" #include "WidgetBlueprintEditor.h" #include "Misc/MessageDialog.h" #include "SBlueprintDiff.h" #define LOCTEXT_NAMESPACE "AssetTypeActions" UAssetDefinition_CommonWidgetBlueprint::UAssetDefinition_CommonWidgetBlueprint() = default; UAssetDefinition_CommonWidgetBlueprint::~UAssetDefinition_CommonWidgetBlueprint() = default; FText UAssetDefinition_CommonWidgetBlueprint::GetAssetDisplayName() const { return LOCTEXT("UAssetDefinition_CommonWidgetBlueprint", "Common Widget Blueprint"); } FLinearColor UAssetDefinition_CommonWidgetBlueprint::GetAssetColor() const { return FLinearColor(FColor(44, 89, 180)); } TSoftClassPtr<> UAssetDefinition_CommonWidgetBlueprint::GetAssetClass() const { return UCommonWidgetBlueprint::StaticClass(); } TConstArrayView<FAssetCategoryPath> UAssetDefinition_CommonWidgetBlueprint::GetAssetCategories() const { static const TArray<FAssetCategoryPath, TFixedAllocator<1>> Categories = { EAssetCategoryPaths::UI }; return Categories; } EAssetCommandResult UAssetDefinition_CommonWidgetBlueprint::OpenAssets(const FAssetOpenArgs& OpenArgs) const { EToolkitMode::Type Mode = OpenArgs.GetToolkitMode(); EAssetCommandResult Result = EAssetCommandResult::Unhandled; for (UBlueprint* Blueprint : OpenArgs.LoadObjects<UBlueprint>()) { if (Blueprint && Blueprint->SkeletonGeneratedClass && Blueprint->GeneratedClass) { TSharedRef<FWidgetBlueprintEditor> NewBlueprintEditor(new FWidgetBlueprintEditor); const bool bShouldOpenInDefaultsMode = false; TArray<UBlueprint*> Blueprints; Blueprints.Add(Blueprint); NewBlueprintEditor->InitWidgetBlueprintEditor(Mode, OpenArgs.ToolkitHost, Blueprints, bShouldOpenInDefaultsMode); } else { FMessageDialog::Open( EAppMsgType::Ok, LOCTEXT("FailedToLoadWidgetBlueprint", "Widget Blueprint could not be loaded because it derives from an invalid class.\nCheck to make sure the parent class for this blueprint hasn't been removed!")); } Result = EAssetCommandResult::Handled; } return Result; } EAssetCommandResult UAssetDefinition_CommonWidgetBlueprint::PerformAssetDiff(const FAssetDiffArgs& DiffArgs) const { const UBlueprint* OldBlueprint = Cast<UBlueprint>(DiffArgs.OldAsset); const UBlueprint* NewBlueprint = Cast<UBlueprint>(DiffArgs.NewAsset); UClass* AssetClass = GetAssetClass().Get(); SBlueprintDiff::CreateDiffWindow(OldBlueprint, NewBlueprint, DiffArgs.OldRevision, DiffArgs.NewRevision, AssetClass); return EAssetCommandResult::Handled; } FText UAssetDefinition_CommonWidgetBlueprint::GetAssetDescription(const FAssetData& AssetData) const { FString Description = AssetData.GetTagValueRef<FString>( GET_MEMBER_NAME_CHECKED( UBlueprint, BlueprintDescription ) ); if ( !Description.IsEmpty() ) { Description.ReplaceInline( TEXT( "\\n" ), TEXT( "\n" ) ); return FText::FromString( MoveTemp(Description) ); } return FText::GetEmpty(); } #undef LOCTEXT_NAMESPACE
这时候就得到了我们想要的结果:
往自己的UserWidget添加测试代码,发现能正常反射处理,因此此篇幅完成
对,别忘记添加Editor模块:
"UnrealEd",
"UMGEditor",
"UMG",
"AssetDefinition",
"Kismet",
Runtime模块:
"UMG",
但是这样只会发现新建的控件放在蓝图中无法正常出现,因为仔细看它的模块,其实它因为这添加的支持内容很多,因此,我们这里最终考虑还是创建UWidgetBlueprint,但是支持的SupportClass依旧是我们自定义的Class就行,这样,我们就能用UserWdiget支持的一套变量反射,它内部做这部分UI映射做了很多处理,因此这里不考虑自己去写这部分内容,这里只需要在Factory真正创建的位置去修改就行,
FactoryCreateNew方法修改:
UObject* UCommonUserWidgetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn, FName CallingContext) { // Make sure we are trying to factory a Anim Blueprint, then create and init one check(Class->IsChildOf(UCommonWidgetBlueprint::StaticClass())); UClass* CurrentParentClass = ParentClass; if (CurrentParentClass == nullptr) { CurrentParentClass = UCommonUserWidget::StaticClass(); } // If they selected an interface, force the parent class to be UInterface if (BlueprintType == BPTYPE_Interface) { CurrentParentClass = UInterface::StaticClass(); } if ( (CurrentParentClass == nullptr) || !FKismetEditorUtilities::CanCreateBlueprintOfClass(CurrentParentClass) || !CurrentParentClass->IsChildOf(UCommonUserWidget::StaticClass()) ) { FFormatNamedArguments Args; Args.Add( TEXT("ClassName"), CurrentParentClass ? FText::FromString( CurrentParentClass->GetName()) : LOCTEXT("Null", "(null)") ); FMessageDialog::Open( EAppMsgType::Ok, FText::Format( LOCTEXT("CannotCreateCommonWidgetBlueprint", "Cannot create a Common Widget Blueprint based on the class '{ClassName}'."), Args ) ); return nullptr; } else { if (!GetDefault<UUMGEditorProjectSettings>()->bUseWidgetTemplateSelector) { RootWidgetClass = GetDefault<UUMGEditorProjectSettings>()->DefaultRootWidget; } -------------------------------修改部分 ------------------------------- UWidgetBlueprint* NewBP = CastChecked<UWidgetBlueprint>(FKismetEditorUtilities::CreateBlueprint(CurrentParentClass, InParent, Name, BlueprintType, UWidgetBlueprint::StaticClass(), UWidgetBlueprintGeneratedClass::StaticClass(), CallingContext)); // Create the desired root widget specified by the project if ( NewBP->WidgetTree->RootWidget == nullptr ) { if (TSubclassOf<UPanelWidget> RootWidgetPanel = RootWidgetClass) { UWidget* Root = NewBP->WidgetTree->ConstructWidget<UWidget>(RootWidgetPanel); NewBP->WidgetTree->RootWidget = Root; } } { IUMGEditorModule::FWidgetBlueprintCreatedArgs Args; Args.ParentClass = CurrentParentClass; Args.Blueprint = NewBP; IUMGEditorModule& UMGEditor = FModuleManager::LoadModuleChecked<IUMGEditorModule>("UMGEditor"); UMGEditor.OnWidgetBlueprintCreated().Broadcast(Args); } return NewBP; } }
很遗憾,自己要去处理这部分内容时间上不划算,不过,最终创建时候,依旧是选的我们的自定义内容,这还是理想的。
到此,篇幅结束,当然,有人可能比较喜欢像我一样做一些明显的图标,便于选中,这里只需要添加Style就行:
Editor模块:
.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Styling/SlateStyle.h" /** Manages the style which provides resources for niagara editor widgets. */ class FDivinePrologueEditorStyle : public FSlateStyleSet { public: static void Register(); static void Unregister(); static void Shutdown(); /** reloads textures used by slate renderer */ static void ReloadTextures(); /** @return The Slate style set for niagara editor widgets */ static const FDivinePrologueEditorStyle& Get(); static void ReinitializeStyle(); virtual const FName& GetStyleSetName() const override; private: FDivinePrologueEditorStyle(); void InitIcons(); static TSharedPtr<FDivinePrologueEditorStyle> DivinePrologueEditorStyle; };
.cpp // Copyright Epic Games, Inc. All Rights Reserved. #include "DivinePrologueEditorStyle.h" #include "Styling/SlateStyleMacros.h" #include "Styling/SlateStyleRegistry.h" TSharedPtr<FDivinePrologueEditorStyle> FDivinePrologueEditorStyle::DivinePrologueEditorStyle = nullptr; void FDivinePrologueEditorStyle::Register() { FSlateStyleRegistry::RegisterSlateStyle(Get()); } void FDivinePrologueEditorStyle::Unregister() { FSlateStyleRegistry::UnRegisterSlateStyle(Get()); } void FDivinePrologueEditorStyle::Shutdown() { Unregister(); DivinePrologueEditorStyle.Reset(); } const FVector2D Icon8x8(8.0f, 8.0f); const FVector2D Icon12x12(12.0f, 12.0f); const FVector2D Icon16x16(16.0f, 16.0f); const FVector2D Icon20x20(20.0f, 20.0f); const FVector2D Icon32x32(32.0f, 32.0f); const FVector2D Icon40x40(40.0f, 40.0f); const FVector2D Icon64x64(64.0f, 64.0f); FDivinePrologueEditorStyle::FDivinePrologueEditorStyle() : FSlateStyleSet("DivinePrologueEditorStyle") { FSlateStyleSet::SetContentRoot(FPaths::ProjectContentDir() / TEXT("StyleTextures")); FSlateStyleSet::SetCoreContentRoot(FPaths::ProjectContentDir() / TEXT("StyleTextures")); InitIcons(); } void FDivinePrologueEditorStyle::InitIcons() { Set("CommonUserWidget.Icon", new IMAGE_BRUSH("Icon", Icon64x64)); } void FDivinePrologueEditorStyle::ReloadTextures() { FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); } const FDivinePrologueEditorStyle& FDivinePrologueEditorStyle::Get() { if(!DivinePrologueEditorStyle.IsValid()) { DivinePrologueEditorStyle = MakeShareable(new FDivinePrologueEditorStyle()); } return *DivinePrologueEditorStyle; } void FDivinePrologueEditorStyle::ReinitializeStyle() { Unregister(); DivinePrologueEditorStyle.Reset(); Register(); } const FName& FDivinePrologueEditorStyle::GetStyleSetName() const { static FName StyleName("DivinePrologueEditorStyle"); return StyleName; }
这是我的路径:
当然创建之后需要在模块启动时候注册它:
在UCommonUserWidgetFactory里面重载下面方法,返回我们Style里面对应的图片:
virtual FName GetNewAssetThumbnailOverride() const override
{
return TEXT("CommonUserWidget.Icon");
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。