[UE4]ListViewでスクロールを検知する

# はじめに

Scroll BoxにあるOn User Scrolledイベントと同じように、List Viewでもスクロールを検知できるようにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668217/ff7ae5f3-39d6-a89c-7452-d644597ac99f.png)

List Viewの改造に関してはこちらの記事も書きました。
今回もエンジンの改造は行わず、C++でListViewを継承する形で実現しています。

https://qiita.com/abcheese/items/68a34a87377864bcd451

# 環境

- Windows 10
- Unreal Engine 4.27

# 実装

思うように処理が作れなかったので、無理やりな実装になってしまいました。
もっときれいに作れるやり方があれば教えてください。

### 作りたかった処理の流れ

下記の流れで実装するつもりでしたが、3のところでBindUObjectを使って通知を受け取るところが上手くいかず、断念しました。
エディタ上での実行だと動くのですが、パッケージングするとクラッシュして動きませんでした。
（AccessViollationが発生、クラッシュした細かな原因までは追えていません。）

1. OnTableViewScrolled（SListViewクラスに元々あるProtectedのデリゲート）でスクロールを検知
1. SListViewを継承したクラスで1の通知を受け取って、Publicなデリゲートを呼び出し
1. UListViewを継承したクラスで2の通知を受け取って、BluePrintに通知するデリゲートを呼び出し

### 実際に作った処理の流れ

1. UListViewを継承したクラスの参照をSListViewを継承したクラスに持たせておく
1. OnTableViewScrolled（SListViewクラスに元々あるProtectedのデリゲート）でスクロールを検知
1. SListViewを継承したクラスで2の通知を受け取って、UListViewを継承したクラスのBluePrintに通知するデリゲートを呼び出し

```SListViewCustom.h
#pragma once

#include "CoreMinimal.h"
#include "Widgets/Views/SListView.h"

/**
 * SListViewを継承したクラス
 */
template <typename ItemType>
class TEST_API SListViewCustom : public SListView<ItemType>
{
public:
	/**
	 * スクロールの通知を受け取るクラスが継承するインターフェース
	 */
	class IListViewCustomBindable {
	public:
		virtual void ExecuteOnScrolled(double)=0;
	};

	SListViewCustom(ETableViewMode::Type InListMode = ETableViewMode::List)
		: SListView(InListMode)
	{
	}

	/** OnTableViewScrolled（元々UEに用意されているデリゲート）にバインド */
	void BindOnScrolled(IListViewCustomBindable* list)
	{
		MyCustomizableListView = list;
		this->OnTableViewScrolled.BindRaw(this, &SListViewCustom::ExecuteOnScrolled);
	}

private:
	IListViewCustomBindable* MyCustomizableListView;

	/** スクロールを検知したときに呼ばれる関数 */
	UFUNCTION()
	void ExecuteOnScrolled(double ScrollOffset)
	{
		MyCustomizableListView->ExecuteOnScrolled(ScrollOffset);
	}
};
```

```CustomizableListView.h
#pragma once

#include "CoreMinimal.h"
#include "Widgets/Views/SListView.h"
#include "Components/ListView.h"
#include "SListViewCustom.h"
#include "CustomizableListView.generated.h"

/**
 * ListViewを継承したクラス
 */
UCLASS()
class TEST_API UCustomizableListView : public UListView, public SListViewCustom<UObject*>::IListViewCustomBindable
{
	GENERATED_BODY()

public:
	UCustomizableListView(const FObjectInitializer& Initializer) : UListView(Initializer){}

	DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListViewScrolled, float, ScrollOffset);	/** Scroll offset from the beginning of the list in items */

	/** BPにスクロールを通知 */
	UPROPERTY(BlueprintAssignable, Category = Events, meta = (DisplayName = "On List View Scrolled"))
	FOnListViewScrolled BP_OnListViewScrolled;

	/** SListViewCustomから呼び出してスクロールを通知するインターフェース関数 */
	UFUNCTION()
	void ExecuteOnScrolled(double ScrollOffset) override;
	
	/** Bind Scroll Event */
	UFUNCTION(BlueprintCallable)
	void BindScrollEvent();

};
```

```CustomizableListView.cpp
#include "CustomizableListView.h"

void UCustomizableListView::BindScrollEvent()
{
	TSharedPtr<SListViewCustom<UObject*>> MyListViewCustom = StaticCastSharedPtr<SListViewCustom<UObject*>>(MyListView);

	// SListViewCustomでデリゲートを作ってスクロール時に呼び出すつもりだったが、
	// BindUObjectではエディタでの実行時は動いたが、パッケージングするとクラッシュした
	// MyListViewCustom->OnListViewScrolled.BindUObject(this, &UCustomizableListView::ExecuteOnScrolled);

	// そのため、thisを引数で渡してSListViewCustomから直接ExecuteOnScrolledを呼び出している
	MyListViewCustom->BindOnScrolled(this);
}

void UCustomizableListView::ExecuteOnScrolled(double ScrollOffset)
{
	BP_OnListViewScrolled.Broadcast(static_cast<float>(ScrollOffset));
}
```

BindScrollEvent関数をBluePrint側でConstructに接続してバインドさせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/668217/a7b409db-72c4-1974-7a37-ecb444ee9bf1.png)

# 参考サイト

https://docs.unrealengine.com/4.26/ja/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/Delegates/

https://dokuro.moe/ue4-cpp-how-to-use-delegate/