SWidget创建的实例

// 具体代码见百度网盘TestReflection
/***************************** SSURAAR_MainWnd.h ********************************/
#include "CoreMinimal.h"
#include "Slate.h"
#include "Engine/RendererSettings.h"

DECLARE_DELEGATE_OneParam(FOnCustomStencilChanged, ECustomDepthStencil::Type)
DECLARE_DELEGATE_OneParam(FOnAlphaChannelModeChanged, EAlphaChannelMode::Type)
DECLARE_DELEGATE_OneParam(FOnFrameBufferPixelFormatChanged, EDefaultBackBufferPixelFormat::Type)

class SSURAAR_MainWnd : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SSURAAR_MainWnd){}
	SLATE_END_ARGS()

	void Construct(const FArguments& Args);

	TSharedRef<SWidget> MakeCustomStencilCombo(FOnCustomStencilChanged OnChanged, TAttribute<ECustomDepthStencil::Type> SelectedEnum, EOrientation Orientation);
	TSharedRef<SWidget> MakeAlphaChannelModeCombo(FOnAlphaChannelModeChanged OnChanged, TAttribute<EAlphaChannelMode::Type> SelectedEnum, EOrientation Orientation);
	TSharedRef<SWidget> MakeFrameBufferPixelFormatCombo(FOnFrameBufferPixelFormatChanged OnChanged, TAttribute<EDefaultBackBufferPixelFormat::Type> SelectedEnum, EOrientation Orientation);

	TSharedRef<SWidget> MakeSURACameraDriverDetails();

private:
	EVisibility CheckSuraCameraDriverExist() const;
};



/********************************** SSURAAR_MainWnd.cpp ***********************************/

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "SSURAAR_MainWnd.h"
#include <Kismet/GameplayStatics.h>

#include "SSuraEnumComboBox.h"
#include "SURACameraDriver.h"
#include "Customizations/SURACameraDriverDetailsCustomization.h"

#define LOCTEXT_NAMESPACE "SURAAR_MainWnd"

void SSURAAR_MainWnd::Construct(const FArguments& Args)
{
	FText PlaceholderText = LOCTEXT("PlaceholderText", "Placeholder");

	auto Settings = GetMutableDefault<URendererSettings>();

	ChildSlot
	[
		SNew(SVerticalBox)
		+SVerticalBox::Slot()
		.AutoHeight()
		[
			SNew(SUniformGridPanel)
			.SlotPadding(TAttribute<FMargin>(FMargin(4.0f)))
			+SUniformGridPanel::Slot(0, 0)
			.VAlign(EVerticalAlignment::VAlign_Center)
			[
				SNew(STextBlock)
				.Text(LOCTEXT("Custom Depth-Stencil Pass", "Custom Depth-Stencil Pass"))
			]
			+SUniformGridPanel::Slot(1, 0)
			.HAlign(EHorizontalAlignment::HAlign_Fill)
			.VAlign(EVerticalAlignment::VAlign_Fill)
			[
				MakeCustomStencilCombo(
					FOnCustomStencilChanged::CreateLambda([&](ECustomDepthStencil::Type stencil)
					{
						auto Settings = GetMutableDefault<URendererSettings>();
						Settings->CustomDepthStencil = stencil;
					}),

					TAttribute<ECustomDepthStencil::Type>::Create(TAttribute<ECustomDepthStencil::Type>::FGetter::CreateLambda([]()
					{
						auto Settings = GetDefault<URendererSettings>();
						return Settings->CustomDepthStencil.GetValue();
					})),

					Orient_Vertical
				)
			]
			+SUniformGridPanel::Slot(0, 1)
			.VAlign(EVerticalAlignment::VAlign_Center)
			[
				SNew(STextBlock)
				.Text(LOCTEXT("Enable alpha channel support", "Enable alpha channel support in post processing"))
			]
			+SUniformGridPanel::Slot(1, 1)
			.HAlign(EHorizontalAlignment::HAlign_Fill)
			.VAlign(EVerticalAlignment::VAlign_Fill)
			[
				MakeAlphaChannelModeCombo(
					FOnAlphaChannelModeChanged::CreateLambda([&](EAlphaChannelMode::Type AlphaMode)
					{
						auto Settings = GetMutableDefault<URendererSettings>();
						Settings->bEnableAlphaChannelInPostProcessing = AlphaMode;
					}),

					TAttribute<EAlphaChannelMode::Type>::Create(TAttribute<EAlphaChannelMode::Type>::FGetter::CreateLambda([]()
					{
						auto Settings = GetDefault<URendererSettings>();
						return Settings->bEnableAlphaChannelInPostProcessing.GetValue();
					})),

					Orient_Vertical
				)
			]
			+SUniformGridPanel::Slot(0, 2)
			.VAlign(EVerticalAlignment::VAlign_Center)
			[
				SNew(STextBlock)
				.Text(LOCTEXT("Frame Buffer Pixel Format", "Frame Buffer Pixel Format"))
			]
			+SUniformGridPanel::Slot(1, 2)
			.HAlign(EHorizontalAlignment::HAlign_Fill)
			.VAlign(EVerticalAlignment::VAlign_Fill)
			[
				MakeFrameBufferPixelFormatCombo(
					FOnFrameBufferPixelFormatChanged::CreateLambda([&](EDefaultBackBufferPixelFormat::Type PixelFormat)
					{
						auto Settings = GetMutableDefault<URendererSettings>();
						Settings->DefaultBackBufferPixelFormat = PixelFormat;
					}),

					TAttribute<EDefaultBackBufferPixelFormat::Type>::Create(TAttribute<EDefaultBackBufferPixelFormat::Type>::FGetter::CreateLambda([]()
					{
						auto Settings = GetDefault<URendererSettings>();
						return Settings->DefaultBackBufferPixelFormat.GetValue();
					})),

					Orient_Vertical
				)
			]
		]
		+SVerticalBox::Slot()
		.AutoHeight()
		[
			//SNew(STextBlock).Text(PlaceholderText)
			SNew(SHorizontalBox)
			+SHorizontalBox::Slot()
			.AutoWidth()
			[
				SNew(SButton) // button for checking SURAAcameraDriver 
				.Visibility_Raw(this, &SSURAAR_MainWnd::CheckSuraCameraDriverExist)
				.Content()
				[
					SNew(STextBlock).Text(LOCTEXT("SetupEnv", "Setup Environment"))
				]
			]
		]
		+ SVerticalBox::Slot()
		[
			MakeSURACameraDriverDetails()
		]
		/*+SVerticalBox::Slot()
		[
			SNew(STextBlock).Text(PlaceholderText)
		]*/
	];
}

TSharedRef<SWidget> SSURAAR_MainWnd::MakeCustomStencilCombo( FOnCustomStencilChanged OnChanged,
	TAttribute<ECustomDepthStencil::Type> SelectedEnum, EOrientation Orientation )
{
	TArray<SSuraEnumCombo<ECustomDepthStencil::Type>::FComboOption> CustomStencilInfo;
	CustomStencilInfo.Add(SSuraEnumCombo<ECustomDepthStencil::Type>::FComboOption(
		ECustomDepthStencil::Disabled, FSlateIcon(), LOCTEXT("CustomStencil.Disabled", "Disabled")));
	CustomStencilInfo.Add(SSuraEnumCombo<ECustomDepthStencil::Type>::FComboOption(
		ECustomDepthStencil::Enabled, FSlateIcon(), LOCTEXT("CustomStencil.Enabled", "Enabled")));
	CustomStencilInfo.Add(SSuraEnumCombo<ECustomDepthStencil::Type>::FComboOption(
		ECustomDepthStencil::EnabledOnDemand, FSlateIcon(), LOCTEXT("CustomStencil.EnabledOnDemand", "EnabledOnDemand")));
	CustomStencilInfo.Add(SSuraEnumCombo<ECustomDepthStencil::Type>::FComboOption(
		ECustomDepthStencil::EnabledWithStencil, FSlateIcon(), LOCTEXT("CustomStencil.EnabledWithStencil", "EnabledWithStencil")));

	return SNew(SSuraEnumCombo<ECustomDepthStencil::Type>, MoveTemp(CustomStencilInfo))
		.SelectedEnum(SelectedEnum)
		.OnEnumChanged(OnChanged)
		.Orientation(Orientation);
		//.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("GraphicsPresetTooltip", "Choose the graphical level to target (high-end only or scalable from low-end on up)."), NULL, TEXT("Shared/Editor/Settings/TargetHardware"), TEXT("GraphicalLevel")));
}

TSharedRef<SWidget> SSURAAR_MainWnd::MakeAlphaChannelModeCombo( FOnAlphaChannelModeChanged OnChanged,
	TAttribute<EAlphaChannelMode::Type> SelectedEnum, EOrientation Orientation )
{
	TArray<SSuraEnumCombo<EAlphaChannelMode::Type>::FComboOption> AlphaChannelModeInfo;
	AlphaChannelModeInfo.Add(SSuraEnumCombo<EAlphaChannelMode::Type>::FComboOption(
		EAlphaChannelMode::Disabled, FSlateIcon(), LOCTEXT("AlphaChannelMode.Disabled", "Disabled")));
	AlphaChannelModeInfo.Add(SSuraEnumCombo<EAlphaChannelMode::Type>::FComboOption(
		EAlphaChannelMode::LinearColorSpaceOnly, FSlateIcon(), LOCTEXT("AlphaChannelMode.LinearColorSpaceOnly", "LinearColorSpaceOnly")));
	AlphaChannelModeInfo.Add(SSuraEnumCombo<EAlphaChannelMode::Type>::FComboOption(
		EAlphaChannelMode::AllowThroughTonemapper, FSlateIcon(), LOCTEXT("AlphaChannelMode.AllowThroughTonemapper", "AllowThroughTonemapper")));

	return SNew(SSuraEnumCombo<EAlphaChannelMode::Type>, MoveTemp(AlphaChannelModeInfo))
		.SelectedEnum(SelectedEnum)
		.OnEnumChanged(OnChanged)
		.Orientation(Orientation);
		//.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("GraphicsPresetTooltip", "Choose the graphical level to target (high-end only or scalable from low-end on up)."), NULL, TEXT("Shared/Editor/Settings/TargetHardware"), TEXT("GraphicalLevel")));
}

TSharedRef<SWidget> SSURAAR_MainWnd::MakeFrameBufferPixelFormatCombo( FOnFrameBufferPixelFormatChanged OnChanged,
	TAttribute<EDefaultBackBufferPixelFormat::Type> SelectedEnum, EOrientation Orientation )
{
	TArray<SSuraEnumCombo<EDefaultBackBufferPixelFormat::Type>::FComboOption> FramePixelFormatInfo;
	FramePixelFormatInfo.Add(SSuraEnumCombo<EDefaultBackBufferPixelFormat::Type>::FComboOption(
		EDefaultBackBufferPixelFormat::DBBPF_B8G8R8A8, FSlateIcon(), LOCTEXT("FramePixelFormat.DBBPF_B8G8R8A8", "8bit RGBA")));
	FramePixelFormatInfo.Add(SSuraEnumCombo<EDefaultBackBufferPixelFormat::Type>::FComboOption(
		EDefaultBackBufferPixelFormat::DBBPF_A16B16G16R16_DEPRECATED, FSlateIcon(), LOCTEXT("FramePixelFormat.DBBPF_A16B16G16R16_DEPRECATED", "DEPRECATED - 16bit RGBA"), false));
	FramePixelFormatInfo.Add(SSuraEnumCombo<EDefaultBackBufferPixelFormat::Type>::FComboOption(
		EDefaultBackBufferPixelFormat::DBBPF_FloatRGB_DEPRECATED, FSlateIcon(), LOCTEXT("FramePixelFormat.DBBPF_FloatRGB_DEPRECATED", "DEPRECATED - Float RGB"), false));
	FramePixelFormatInfo.Add(SSuraEnumCombo<EDefaultBackBufferPixelFormat::Type>::FComboOption(
		EDefaultBackBufferPixelFormat::DBBPF_FloatRGBA, FSlateIcon(), LOCTEXT("FramePixelFormat.DBBPF_FloatRGBA", "Float RGBA")));
	FramePixelFormatInfo.Add(SSuraEnumCombo<EDefaultBackBufferPixelFormat::Type>::FComboOption(
		EDefaultBackBufferPixelFormat::DBBPF_A2B10G10R10, FSlateIcon(), LOCTEXT("FramePixelFormat.DBBPF_A2B10G10R10", "10bit RGB, 2bit Alpha")));

	return SNew(SSuraEnumCombo<EDefaultBackBufferPixelFormat::Type>, MoveTemp(FramePixelFormatInfo))
		.SelectedEnum(SelectedEnum)
		.OnEnumChanged(OnChanged)
		.Orientation(Orientation);
		//.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("GraphicsPresetTooltip", "Choose the graphical level to target (high-end only or scalable from low-end on up)."), NULL, TEXT("Shared/Editor/Settings/TargetHardware"), TEXT("GraphicalLevel")));
}

EVisibility SSURAAR_MainWnd::CheckSuraCameraDriverExist() const
{
	TArray<AActor*> OutActors;
	if (GWorld)
	{
		UGameplayStatics::GetAllActorsOfClass(GWorld, ASURACameraDriver::StaticClass(), OutActors);
	}

	if (OutActors.Num() == 0)
	{
		UE_LOG(LogTemp, Log, TEXT("No SuraCameraDriver found."));
		return EVisibility::Visible;
	}
	else
	{
		return EVisibility::Collapsed;
	}
}

TSharedRef<SWidget> SSURAAR_MainWnd::MakeSURACameraDriverDetails( )
{
	FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));

	FDetailsViewArgs Args;
	Args.bAllowSearch = false;
	Args.NameAreaSettings = FDetailsViewArgs::HideNameArea;
	TSharedRef<IDetailsView> View = PropertyModule.CreateDetailView(Args);
	View->RegisterInstancedCustomPropertyLayout(ASURACameraDriver::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FSURACameraDriverDetailsCustomization::MakeInstance));

	TArray<AActor*> OutActors;
	if (GWorld)
	{
		UGameplayStatics::GetAllActorsOfClass(GWorld, ASURACameraDriver::StaticClass(), OutActors);
	}
	if (OutActors.Num() > 0)
	{
		View->SetObject(OutActors[0], true);
	}

	return View;
}

#undef LOCTEXT_NAMESPACE

config/配置存取

 UCLASS(Config=AnjGameSettings, ConfigDoNotCheckDefaults/DefaultConfig)
 class UAnjGameSettings : UDevelopSettings // [OR] UEditorSettings [OR]
 class AAnjGameSettings : Axxx
 {
     UPROPERTY(Config)
     int BroadcastPort = 8888;
     AAnjGameSettings();
     /** Gets the settings container name for the settings, either Project or Editor */
     virtual FName GetContainerName() const override { return TEXT("Project"); }
     /** Gets the category for the settings, some high level grouping like, Editor, Engine, Game...etc. */
     virtual FName GetCategoryName() const override { return TEXT("DataDrivenSettings"); }
     /** The unique name for your section of settings, uses the class's FName. */
     virtual FName GetSectionName() const override { return TEXT("DataDrivenSettings"); }
 }
代码中Config=AnjGameSettings用来设定class中带有Config meta信息的Property要写入AnjGameSettings.ini文件。参考官网文档,这个Config叫做Specifier,指定Configuration Categories,Categories列表在官网能看到。
AAnjGameSettings()构造函数可以用来设定Property的默认值:
 UAnjGameSettings::UAnjGameSettings( )
 {
 	if (GConfig)
 	{
 		FString IPStr;
 		if (!GConfig->GetString(TEXT("/Script/SuraForAnj.AnjGameSettings"), TEXT("LocalIP"), IPStr, GetClass()->GetConfigName()))
 		{
 			bool canBind = false;
 			TSharedRef<FInternetAddr> localIp = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLocalHostAddr(*GLog, canBind);
 			FIPv4Endpoint LocalEndpoint = FIPv4Endpoint(localIp);
 			LocalIP = LocalEndpoint.Address.ToString();
 			SaveConfig();
 		}
 	}
 }
注意上面代码中的GetClass()->GetConfigName(),它返回Config指定的AnjGameSettings.ini文件的存储路径(也可以直接写GGameIni、GEngineIni等值)。SaveConfig()是UObject的函数,注意其Filename参数未指定时,内部实现是设定为GetClass()->GetConfigName()。
ConfigDoNotCheckDefaults与DefaultConfig的区别:
UpdateDefaultConfigFile()函数类似上面的SaveConfig(),但会把值写入AnjGameSettingsDefault.ini文件。而在调用SaveConfig()时,如为DefaultConfig,则当Property的值与xxxDefault.ini文件里的值一致时,xxx.ini文件里Property属性行会被删除,这是因为xxxDefault.ini文件先被读取,然后是xxx.ini,所以与xxxDefault.ini里一致的属性,没必要再往xxx.ini写一条记录。而指定了ConfigDoNotCheckDefaults时,SaveConfig()不检查有无Default.ini文件及里面的属性值,直接更新xxx.ini文件。

从UDeveloperSettings派生的UObject,可以继承其能够在UE编辑器的ProjectSettings/EditorPreferences->Category->Section->(UPROPERTY.Category)里直接编辑属性的能力。

关于config信息的读取,参考
To access the developer settings in C++ we use the CDO (Class Default Object) as that is already automatically instanced for us and accessed using GetDefault<T>();
 void USSaveGameSubsystem::Initialize(FSubsystemCollectionBase& Collection)
 {
 	Super::Initialize(Collection);
 	const USSaveGameSettings* SGSettings = GetDefault<USSaveGameSettings>(); // Access via CDO
 	// Access defaults from DefaultGame.ini
 	SlotName = SGSettings->SaveSlotName;
 	// Make sure it's loaded into memory .Get() only resolves if already loaded previously elsewhere in code
 	UDataTable* DummyTable = SGSettings->DummyTablePath.LoadSynchronous();
 }

有一个全部自定义处理config的参考

获取本地IP地址

// Module: Sockets.build.cs

#include "SocketSubsystem.h"
#include "IPAddress.h"
#include "Interfaces/IPv4/IPv4Endpoint.h"

bool canBind = false;
TSharedRef<FInternetAddr> localIp = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLocalHostAddr(*GLog, canBind);
FIPv4Endpoint LocalEndpoint = FIPv4Endpoint(localIp);
LocalIP = LocalEndpoint.Address.ToString();

ini文件读写 → ...

https://blog.51cto.com/hanzhichao/3201113

from configparser import ConfigParser
conf = ConfigParser()
conf.read(configFilePath, encoding=cur_encoding)
print(conf['section']['key'])


''' LutConfig.py '''
from pathlib import Path
from typing import *
import chardet
from configparser import ConfigParser

def GetDefaultConfigFilePath() -> Path:
    return Path(__file__).parent.absolute() / 'Windows/TestLutPro/Config/DefaultGame.ini'

def GetConfig(configFilePath: str) -> dict:
    # 首先二进制方式打开文件
    with open(configFilePath, 'rb') as frb:
        # 检测编码方式
        cur_encoding = chardet.detect(frb.read())['encoding']

    # 指定文件编码方式
    conf = ConfigParser()
    conf.read(configFilePath, encoding=cur_encoding)

    configResult = {}
    section = '/Script/ApplyLutTools.ApplyLutSettings'
    if conf.has_option(section, 'ServerIP'):
        configResult['ServerIP'] = conf[section]['ServerIP']
    if conf.has_option(section, 'ServerPort'):
        configResult['ServerPort'] = conf[section].getint('ServerPort')
    if conf.has_option(section, 'LocalListeningIP'):
        configResult['LocalListeningIP'] = conf[section]['LocalListeningIP']
    return configResult

def GetDefaultConfig() -> dict:
    return GetConfig(GetDefaultConfigFilePath())

Details视图定制

一个Actor或Property的Details信息的展示界面,可以定制。这项工作涉及几个基本的接口:
以上需要引用【PropertyEditor】模块。
代码示例:
针对特定Class类型的所有实例定制其Details面板的情形:
 StarupModule() {
	 FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
	 // Custom Class Layouts
	 auto AddCustomClass = [this, InPropertyModule = &PropertyModule](FName Name, FOnGetDetailCustomizationInstance InstanceGetter)
	 {
		InPropertyModule->RegisterCustomClassLayout(Name, InstanceGetter);
		CustomClassLayoutNames.Add(Name);
	 };
	 AddCustomClass(ASURACameraDriver::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FSURACameraDriverDetailsCustomization::MakeInstance));
 }
 ShutdownModule() {
	 if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
	 {
		FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
		for (FName ClassName : CustomClassLayoutNames)
		{
			PropertyModule.UnregisterCustomClassLayout(ClassName);
		}
		CustomClassLayoutNames.Reset();
	 }
 }
 // .h
 TArray<FName> CustomClassLayoutNames;
针对特定Class类型的某个实例定制Details面板。需要在该实例对应的IDetailsView中设置,详见代码 Customizations/SURACameraDriverDetailsCustomization代码
IDetailsView面板与UObject建立关联:
 TSharedRef<SWidget> SSURAAR_MainWnd::MakeSURACameraDriverDetails( )
 {
	FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
	FDetailsViewArgs Args;
        Args.bAllowSearch = false; // 隐藏Details面板的搜索框
	Args.NameAreaSettings = FDetailsViewArgs::HideNameArea; // 隐藏Details面板的角色名框
	TSharedRef<IDetailsView> View = PropertyModule.CreateDetailView(Args);
	View->RegisterInstancedCustomPropertyLayout(ASURACameraDriver::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FSURACameraDriverDetailsCustomization::MakeInstance));
	TArray<AActor*> OutActors;
	if (GWorld)
	{
		UGameplayStatics::GetAllActorsOfClass(GWorld, ASURACameraDriver::StaticClass(), OutActors);
	}
	if (OutActors.Num() > 0)
	{
		View->SetObject(OutActors[0], true);
	}
	return View;
 }