UE4 WidgetをC++クラス継承してアクセスする

# 概要
UnrealEngine の Widget についてのテストをした記録です。
ブループリントで扱うことが多い Widget を C++クラスを継承する形にし、各ウィジェットパーツにC++にてアクセスします。

## 修正履歴
|日付|内容|
|:---------|:------------------|
|2019/06/22|チェックボックス、ボタン、プログレスバーについて追記|
|2019/06/29|コンボボックスについて追記|
|2019/07/11|イメージについて追記|
|2019/09/06|リッチテキストブロックについて追記|


## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.22


## 参考
以下を参考にさせて頂きました、ありがとうございます。

[[UE4]エディタで配置したWidgetを親クラス（C++）で操作](https://flyingpillow.info/moving/ue4%e3%82%a8%e3%83%87%e3%82%a3%e3%82%bf%e3%81%a7%e9%85%8d%e7%bd%ae%e3%81%97%e3%81%9fwidget%e3%82%92%e8%a6%aa%e3%82%af%e3%83%a9%e3%82%b9%ef%bc%88c%ef%bc%89%e3%81%a7%e6%93%8d%e4%bd%9c/)
[UMG, overriding of UsingWidget.Tick](https://answers.unrealengine.com/questions/110425/umg-overriding-of-usingwidgettick.html)



# 実行手順
## UnrealEditor側の処理：Widgetの準備
1. UserWidget を継承したブループリントを作成する。
[新規追加] -> [ユーザーインターフェイス] -> [ウィジェットブループリント]
（例）TestWidgetBP.uasset

1. 作成したウィジェットのデザイナーモードで作成する。
（例）キャンバスにテキストボックスとテキストを配置する。

![TestWidget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/cdf7bcd8-c326-bdca-f779-e6f9d720370d.png)

## UnrealEditor側の処理：C++クラスの設定
1. 上記BPで作成したウィジェットの親クラスとなるC++クラスを UUserWidget を継承して作成する。
（例）UTestWidget.h, UTestWidget.cpp

1. UnrealEditor で ウィジェット(TestWidgetBP.uasset)の親クラスを 作成したC++クラス(UTestWidget)に切り替える。
[グラフ] -> [クラス設定] -> [クラスオプション] -> [親クラス] -> [TestWidget]


## UnrealC++側の処理

UUserWidget継承クラスでのTick処理は NativeTick() を継承して行うようです。
Tick_Implementation()での処理はうまくいきませんでした。

```cpp:UTestWidget.h
	virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
```

```cpp:UTestWidget.cpp
// Tick処理
void UTestWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
	// 継承元処理
	Super::NativeTick(MyGeometry, InDeltaTime);

	// テキストブロックの取得
	UTextBlock* _pParam01_Text = Cast<UTextBlock>(GetWidgetFromName("Param01_Text"));
	// テキスト書き換え
	if (_pParam01_Text) {
		// 数値を表示
		float _Value = 3.1415926f;
		FString _Str = FString::Printf(TEXT("%.2f"), _Value);
		
		_pParam01_Text->SetText(FText::FromString(_Str));

		// カラー設定
		_pParam01_Text->SetColorAndOpacity( FSlateColor(FLinearColor(1.f, 0.f, 0.f, 1.f)) );
	}

}
```

この処理により、Param01_Textに **3.14** が赤色で入ります。

通常は表示したい対象のクラスから情報を取得するメソッドを用意して、そこから値を受け取って表示する使い方になると思います。

### FSlateColorでエラーになる場合
???.Build.csファイルにモジュールの追加を入れる。(???はプロジェクト名)

```C#:Test.Build.cs
PrivateDependencyModuleNames.AddRange(new string[] { "Slate","SlateCore" });
```

# 他のWidgetパーツへのアクセスについて
テキストブロック以外のWidgetパーツについてのメモ書きです。

## チェックボックス
チェックボックスはチェックのオン/オフ時のイベントを受け取るため、デリゲートOnCheckStateChangedにイベントを追加する形でアクセスするようです。
追加したメソッドに処理を書きます。

```cpp:UTestWidget.h
// 呼ばれるイベント
UFUNCTION(BlueprintCallable, Category = "Test")
	void TestChange(bool bChange){ UE_LOG(LogTemp, Log, TEXT("bChange:%d"), bChange); }
```

以下の記述例はNativeTickで書いているため IsBoundで登録済かをチェックしていますが、NativeConstruct で書いた方がいいと思います。

```cpp:UTestWidget.cpp
#include "Components/CheckBox.h"

// NativeTickでの処理
UCheckBox* CheckBox = Cast<UCheckBox>(GetWidgetFromName("CheckBox_Test"));
if (CheckBox) {
	if (!CheckBox->OnCheckStateChanged.IsBound()) {
		// 呼び出しメソッド追加
		CheckBox->OnCheckStateChanged.AddDynamic(this, &UTestWidget::TestChange );
	}
}
```

再生を行うと、チェックボックスのオン/オフ時にアウトプットログに表示されます。


## ボタン
ボタンもチェックボックスと似たような感じの実装になります。
ただ違うのがデリゲート（イベントディスパッチャー）でOnClicked, OnPressed, OnReleased, OnHovered, OnUnhovered と5種類存在します。

テストコードはOnClicked時だけですが、デリゲート名を変えることで各イベントに対応して処理されます。

```cpp:UTestWidget.h
UFUNCTION(BlueprintCallable, Category = "Test")
	void TestClicked(){ UE_LOG(LogTemp, Log, TEXT("Clicked")); }
```

```cpp:UTestWidget.cpp
#include "Components/Button.h"

UButton* Button = Cast<UButton>(GetWidgetFromName("Button_Test"));
if (Button) {
	if (!Button->OnClicked.IsBound()) {
		// デリゲート追加
		Button->OnClicked.AddDynamic(this, &UTestWidget::TestClicked);
	}
}
```
再生を行い、ボタンを押下するとアウトプットログに[Clicked]が表示されます。


## プログレスバー
プログレスバーは
void SetPercent(float InPercent);
void SetFillColorAndOpacity(FLinearColor InColor);
void SetIsMarquee(bool InbIsMarquee);
の3つのメソッドで扱います。

```cpp:UTestWidget.cpp
#include "Components/ProgressBar.h"

UProgressBar* ProgBar = Cast<UProgressBar>(GetWidgetFromName("ProgressBar_Test"));
static float Time = 0.f;
Time += InDeltaTime;
if (ProgBar) {
	float _Rate = FMath::Min(Time / 5.0f, 1.0f);

//	ProgBar->SetIsMarquee(true);	// 進行具合が分からない場合
	ProgBar->SetPercent(_Rate);	// 現在のプログレスバーレートを設定
}
```

上記例は5秒かけてプログレスバーが進行します。


## コンボボックス
コンボボックスは AddOption, RemoveOption で選択肢の追加/削除ができます。
あとコンボボックスが展開された時と選択が変更された時のデリゲート（イベントディスパッチャー） OnOpening と OnSelectionChanged に登録ができます。

![combobox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0c2f92d2-5fbb-3ead-90b2-cf847ae804ff.png)


以下コード例はデリゲートに追加するメソッドです。

```cpp:UTestWidget.h
// コンボボックス展開時
UFUNCTION(BlueprintCallable, Category = "Test")
void TestEvent(){ UE_LOG(LogTemp, Log, TEXT("Open!")); }

// コンボボックス選択時
UFUNCTION(BlueprintCallable, Category = "Test")
void TestSelChange(FString SelectedItem, ESelectInfo::Type SelectionType) {
	const UEnum* _pEnumPtr = FindObject<UEnum>(ANY_PACKAGE, TEXT("ESelectInfo"), true);
	auto _EnumText = _pEnumPtr->GetDisplayNameTextByIndex((uint8)SelectionType);

	UE_LOG(LogTemp, Log, TEXT("SelChange(%s, %s)"), *SelectedItem, *_EnumText.ToString() );
}
```

```cpp:UTestWidget.cpp
#include "Components/ComboBoxString.h"

UComboBoxString* ComboBox = Cast<UComboBoxString>(GetWidgetFromName("ComboBoxString_Test"));
if (ComboBox) {
	// 現在選択されているオプションを調べる
	UE_LOG(LogTemp, Log, TEXT("%s, %d"), *ComboBox->GetSelectedOption(), ComboBox->GetSelectedIndex());

	// [select99]がない場合追加する
	if (ComboBox->FindOptionIndex("select99") < 0) {
		ComboBox->AddOption("select99");
	}

	// コンボボックスが展開された時
	if (!ComboBox->OnOpening.IsBound()) {
		// デリゲート追加
		ComboBox->OnOpening.AddDynamic(this, &UTestWidget::TestEvent);
	}

	// コンボボックスが選択された時
	if(!ComboBox->OnSelectionChanged.IsBound()) {
		// デリゲート追加
		ComboBox->OnSelectionChanged.AddDynamic(this, &UTestWidget::TestSelChange);
	}

}
```

コンボボックスを展開して、[select1]を選択した場合、以下のようなアウトプットになります。
![combobox_output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/2d5b9aca-1ba3-7fa8-9ef8-040dbd0aa482.png)
展開時に[Open!]が出力され、オプションを選んだ後に[SelChange...]が出力されます。

## イメージ
画像データを表示できます。OnMouseButtonDownEventにイベントを登録することで、マウスクリック時にイベントを呼ぶことができます。

```cpp:UTestWidget.h
// イベント
UFUNCTION(BlueprintCallable, Category = "Test")
void TestEvent(FGeometry MyGeometry, const FPointerEvent& MouseEvent) {
	UE_LOG(LogTemp, Log, TEXT("Click!"));
	}

// テクスチャ
UTexture2D* Tex;
```

Tick()でやっていますが、デリゲートのバインドなどは NativeConstruct() あたりでやったほうがいいです。

```cpp:UTestWidget.cpp
// コンストラクタ
// Testフォルダのテクスチャデータ test00.uasset を取得する
static ConstructorHelpers::FObjectFinder<UTexture2D> _DataFile(TEXT("/Game/Test/test00"));
if (_DataFile.Object) {
	Tex = _DataFile.Object;
}

// Tick処理
UImage* _pImage0 = Cast<UImage>(GetWidgetFromName("Image_00"));
if (_pImage0) {
	if (!_pImage0->OnMouseButtonDownEvent.IsBound()) {
		// デリゲート追加
		_pImage0->OnMouseButtonDownEvent.BindUFunction(this, "TestEvent");
		// UTexture2Dをはる
		_pImage0->SetBrushFromTexture(Tex);
	}
}
```

スポーンしたwidgetに対し、マウスクリックをするとイベントが呼ばれ、[Click!]がアウトプットログに表示されます。


## リッチテキストボックス
複数行のテキストを書いたり、カラーの設定ができます。
詳細の[Appearance]に[TextStyleSet]の設定が必要です。

[Default]と[RichText.Emphasis]の２つ設定を持っています。
![TestStyle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/437844d2-c7fe-8bd0-29e4-0a45fdd50a6f.png)

テキストデータを設定するコード例です。

```cpp:UTestWidget.cpp
URichTextBlock* _RText = Cast<URichTextBlock>(GetWidgetFromName("RichTextBlock_000"));
if(_RText){	
	// 2行テキスト表示テスト
	FText _Text = FText::FromString(FString::Printf(TEXT("test1\ntest2")) );
						
	_RText->SetText(_Text);
}
```

フォントスタイルを変更する場合は以下の様なコードになります。ここで[RichText.Emphasis]は [TextStyleSet]で設定したデータ名です。

```cpp:.cpp
// フォントスタイルを一部変更（"BBB"だけ変わる）
FText _Text = FText::FromString(FString::Printf(TEXT("AAA<RichText.Emphasis>BBB</>CCC")));
```


# まとめ
多少仕込みがいりますが、ウィジェットBPのグラフでやるよりは少し楽になるかもしれません。もっと細かい制御をする場合はC++側処理が大変になると思います。(ブループリントでも大変だとは思いますが)

ブループリントでのキャストやちょっとした計算が手間だったので今回の方法を調査＆テストしてみました。ブループリントノードに直接簡単なコードを書いたりできたらいいのですが。:thinking: