5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【UE4】フォルダ選択ダイアログを作る

Posted at

uprojectの整理のためのEditor Utility Widgetを作成中に知ったことの備忘録。
フォルダを選択するUIがほしいが、いわゆる Blutility には公開されていない様子。
なので、なんとなく自作してみた。

image.png

IContentBrowserSingleton::CreatePathPicker()

IContentBrowserSingleton にある CreatePathPicker を使えばよい様子。
大雑把には、

  1. FPathPickerConfig に必要な情報をセットする。
  2. FContentBrowserModule を取得し、CreatePathPicker() を呼び出す。
  3. Slate 製 SPathPicker が得られるので、それをダイアログウィンドウに貼り付ける。

という手順で作れた。

  • FContentBrowserModule を使うので、ContentBrowserModule.h のインクルードと、ContentBrowser モジュールを .Build.cs に追加する必要がある。
  • OK ボタンや Cancel ボタンはないので、Slate で自作する必要がある。

ソースコード

class ValidPath

  • 存在しないパスを指定されたら、"/Game"に設定するだけのユーティリティクラス。
  • UEditorAssetLibrary::DoesDirectoryExist() を使うので、EditorAssetLibrary.h のインクルードと、EditorScriptingUtilities モジュールの .Build.cs への追加が必要。

アセット操作では AssetRegistry, AssetTools, ContentBrowser などのモジュールはよく使うものの、UEditorAssetLibrary の存在を忘れがち(私は)。
困ったら、なんかもう一つあったよね?(= UEditorAssetLibrary) を思い出すべし。
※UEditorAssetLibrary は、いわゆる Blutility にも公開されている関数群。

ValidPath
namespace assetutils
{
	class ValidPath
	{
	public:
		ValidPath(const FString& InPath)
		{
			if (UEditorAssetLibrary::DoesDirectoryExist(InPath))
			{
				Path = InPath;
			}
			else
			{
				UE_LOG(LogRinderonEditor, Warning, TEXT("assetutils::ValidPath()  InPath(%s) is not exist, \"/Game\" is used instead."), *InPath);
				Path = TEXT("/Game");
			}
		}
	public:
		const FString& Get() const { return Path; }
	private: // internal properties
		FString Path;
	}; // class ValidPath

} // namespace assetutils

class SDirectoryDialog

パス選択ダイアログ本体。
Slate はろくにいじったことがないので、これで合っているか不安。

  • SHorizontalBox buttonsBox は、OK ボタンと Cancel ボタンを持っている。
  • SVerticalBox mainBox は、上側に SPathPicker を、下側に buttonsBox を持っている。
SDirectoryDialog
// directory selection dialog (slate)
class SDirectoryDialog : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SDirectoryDialog) {}

	SLATE_END_ARGS()

public:
	SDirectoryDialog() {}
	virtual ~SDirectoryDialog() {}

	virtual void Construct(const FArguments& InArgs, const FString& InBasePath)
	{
		assetutils::ValidPath basePath(InBasePath);

		FPathPickerConfig config;
		config.DefaultPath = basePath.Get();
		config.OnPathSelected = FOnPathSelected::CreateRaw(this, &SDirectoryDialog::OnPathSelected);

		FContentBrowserModule& contentBrowserModule
			= FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
		TSharedPtr<SPathPicker> pathPicker = StaticCastSharedRef<SPathPicker>(
			contentBrowserModule.Get().CreatePathPicker(config));

		TSharedRef<SHorizontalBox> buttonsBox = SNew(SHorizontalBox)
			+ SHorizontalBox::Slot()
				.AutoWidth()
				.VAlign(VAlign_Bottom)
				.Padding(4.0f, 3.0f)
				[
					SNew(SButton)
					.Text(LOCTEXT("DirectoryPickerConfirmButton", "OK"))
					.ContentPadding(FMargin(8.0f, 2.0f))
					.IsEnabled(this, &SDirectoryDialog::IsConfirmButtonEnabled)
					.OnClicked(this, &SDirectoryDialog::OnConfirmButtonClicked)
				]
			+ SHorizontalBox::Slot()
				.AutoWidth()
				.VAlign(VAlign_Bottom)
				.Padding(4.0f, 3.0f)
				[
					SNew(SButton)
					.Text(LOCTEXT("DirectoryPickerCancelButton", "Cancel"))
					.ContentPadding(FMargin(8.0f, 2.0f))
					.OnClicked(this, &SDirectoryDialog::OnCancelButtonClicked)
				];

		TSharedRef<SVerticalBox> mainBox = SNew(SVerticalBox)
			+ SVerticalBox::Slot()
				.FillHeight(1.0f)
				.Padding(0.0f, 0.0f, 0.0f, 4.0f)
				[
					pathPicker.ToSharedRef()
				]
			+ SVerticalBox::Slot()
				.AutoHeight()
				.HAlign(HAlign_Fill)
				.Padding(0.0f)
				[
					buttonsBox
				];

		ChildSlot
			[
				mainBox
			];
	}

public: // public methods
	bool HasValidResult() const { return ConfirmedFlag && (!SelectedPath.IsEmpty()); }
	const FString& GetPath() const { return SelectedPath; }

private: // internal methods: utilities
	void CloseDialog()
	{
		TSharedPtr<SWindow> window = FSlateApplication::Get().FindWidgetWindow(AsShared());
		if (window.IsValid())
		{
			window->RequestDestroyWindow();
		}
	}

private: // internal methods: delegates
	bool IsConfirmButtonEnabled() const { return true; }

	FReply OnConfirmButtonClicked()
	{
		CloseDialog();
		ConfirmedFlag = true;
		return FReply::Handled();
	}

	FReply OnCancelButtonClicked()
	{
		CloseDialog();
		ConfirmedFlag = false;
		return FReply::Handled();
	}

	void OnPathSelected(const FString& InCurrentPath) { SelectedPath = InCurrentPath; }

private: // internal properties
	FString SelectedPath;
	bool ConfirmedFlag = false;
};

BrowseForFolder() 関数

ダイアログを呼び出すための Blueprint Function Library の関数。

  • IMainFrameModule のために、Interfaces/IMainFrameModule.h のインクルードと、MainFrame モジュールの .Build.cs への追加が必要。
BrowseForFolder
void URinderonEditorFunctionLibrary::BrowseForFolder(const FString& InBasePath,
    bool& OutSuccess, FString& OutPath)
{
	TSharedRef<SWindow> window = SNew(SWindow)
		.Title(LOCTEXT("BrowseForFolderTitle", "Browse Folders"))
		.ClientSize(FVector2D(320.0f, 320.0f))
		.SizingRule(ESizingRule::UserSized)
		.SupportsMaximize(false)
		.SupportsMinimize(false);

	TSharedRef<SDirectoryDialog> dialog = SNew(SDirectoryDialog, InBasePath);
	window->SetContent(dialog);

	IMainFrameModule& mainFrameModule
		= FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
	const TSharedPtr<SWindow>& parentWindow = mainFrameModule.GetParentWindow();
	if (parentWindow.IsValid())
	{
		FSlateApplication::Get().AddModalWindow(window, parentWindow.ToSharedRef());
		if (dialog->HasValidResult())
		{
			OutSuccess = true;
			OutPath = dialog->GetPath();
			return;
		}
	}

	OutSuccess = false;
	OutPath.Empty();
	return;
}

Editor Utility Widget からの呼び出し

image.png

非常にシンプルに使えて便利。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?