[UWP]UIの自動非表示Behavior

## 要件
動画再生と一緒に表示するコントロールUIをマウスポインタの移動やタッチ操作が一定時間されていないときに自動で非表示にしたい。

ついでにマウスポインタも消せると尚良し。



## ビヘイビアで実装

```csharp:AutoHideBehavior.cs

using Microsoft.Xaml.Interactivity;
using System;
using System.Threading;
using Windows.UI.Core;
using Windows.UI.Xaml;

namespace MyApp.Views.Behaviors
{
	public class AutoHide : Behavior<FrameworkElement>
	{


		#region IsEnable Property

		public static readonly DependencyProperty IsEnableProperty =
			DependencyProperty.Register("IsEnable"
					, typeof(bool)
					, typeof(AutoHide)
					, new PropertyMetadata(default(bool), OnIsEnablePropertyChanged)
				);

		public bool IsEnable
		{
			get { return (bool)GetValue(IsEnableProperty); }
			set { SetValue(IsEnableProperty, value); }
		}


		public static void OnIsEnablePropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
		{
			AutoHide source = (AutoHide)sender;

			var isActive = (bool)args.NewValue;

			if (isActive)
			{
				source.EnableAutoHide();
			}
			else
			{
				source.DisableteAutoHide();
			}
		}

		#endregion



		


		#region Delay Property

		public static readonly DependencyProperty DelayProperty =
			DependencyProperty.Register("Delay"
					, typeof(TimeSpan)
					, typeof(AutoHide)
					, new PropertyMetadata(default(double))
				);

		public TimeSpan Delay
		{
			get { return (TimeSpan)GetValue(DelayProperty); }
			set { SetValue(DelayProperty, value); }
		}


		public static void OnDelayPropertyChanged(object sender, DependencyPropertyChangedEventArgs args)
		{
			AutoHide source = (AutoHide)sender;

			source._NextHideTime = source._PrevPreventTime + source.Delay;
		}

		#endregion





		#region WithCursor Property 

		public static readonly DependencyProperty WithCursorProperty =
			DependencyProperty.Register("WithCursor"
				, typeof(bool)
				, typeof(AutoHide)
				, new PropertyMetadata(true)
			);

		public bool WithCursor
		{
			get { return (bool)GetValue(WithCursorProperty); }
			set { SetValue(WithCursorProperty, value); }
		}

		#endregion







		public void PreventAutoHide()
		{
			_PrevPreventTime = DateTime.Now;
			_NextHideTime = _PrevPreventTime + Delay;

			this.AssociatedObject.Visibility = Visibility.Visible;

			CoreWindow.GetForCurrentThread().PointerCursor = _CoreCursor;
		}








		protected override void OnAttached()
		{
			base.OnAttached();

			_CoreCursor = CoreWindow.GetForCurrentThread().PointerCursor;

		}

		protected override void OnDetaching()
		{
			base.OnDetaching();

			_Timer?.Dispose();
			_Timer = null;

			CoreWindow.GetForCurrentThread().PointerCursor = _CoreCursor;
		}




		private void EnableAutoHide()
		{
			_NextHideTime = DateTime.Now + Delay;

			_Timer = new Timer(async (state) => 
			{
				await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
				{
					if (this.AssociatedObject.Visibility == Visibility.Visible && 
						_NextHideTime < DateTime.Now)
					{
						this.AssociatedObject.Visibility = Visibility.Collapsed;

						if (WithCursor)
						{
							CoreWindow.GetForCurrentThread().PointerCursor = null;
						}
					}
				});
			}
			, this, Delay, TimeSpan.FromMilliseconds(100));

			
		}

		private void DisableteAutoHide()
		{
			_Timer?.Dispose();
			_Timer = null;

			this.AssociatedObject.Visibility = Visibility.Visible;
			CoreWindow.GetForCurrentThread().PointerCursor = _CoreCursor;
		}



		CoreCursor _CoreCursor;
		DateTime _PrevPreventTime;
		DateTime _NextHideTime;
		Timer _Timer;
	}
}


```

## XAMLで利用

```xml:UsingAutoHide.xaml
<!--
xmlns:mybehavior="using:MyApp.Views.Behaviors"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
-->
<i:Interaction.Behaviors>
    <mybehavior:AutoHide x:Name="AutoHideBehavior"
                            IsEnable="{Binding IsAutoHideEnable}"
                            Delay="00:00:03" 
                            WithCursol="True"
                            >
        <i:Interaction.Behaviors>
            <core:EventTriggerBehavior SourceObject="{Binding ElementName=MediaControl}" EventName="Tapped">
                <core:CallMethodAction TargetObject="{Binding ElementName=AutoHideBehavior}" MethodName="PreventAutoHide" />
            </core:EventTriggerBehavior>
            <core:EventTriggerBehavior SourceObject="{Binding ElementName=MediaControl}" EventName="PointerMoved">
                <core:CallMethodAction TargetObject="{Binding ElementName=AutoHideBehavior}" MethodName="PreventAutoHide" />
            </core:EventTriggerBehavior>

        </i:Interaction.Behaviors>
    </mybehavior:AutoHide>
</i:Interaction.Behaviors>
                
```

自動非表示にしたいUIのビヘイビアにAutoHideBehaviorをくっつけます。
ViewModel側で予めbool型のIsAutoHideEnableプロパティを作成しておきます。
"MediaControl"とあるところは、マウス操作やタップ操作を受けてUI非表示を解除したいPanel要素等のx:Nameを指定します。


## ビヘイビア動作の補足説明

前提として、FrameworkElementがVisibilityChangedイベントを持たないため、TimerとNextHideTimeの組み合わせで非表示時間をチェックする方法をとっています。

Timerのコールバック内はTimerスレッドのため、UIスレッドへの切り替えのためDispatcher.RunAsyncを使っています。

WithCursorをTrueにするとウィンドウのカーソルをまるごと表示・非表示を切り替えてます。FrameworkElementごとにカーソルを変える方法もあるようですが、シンプルに書けるのでこの形をとっています。

なお、WithCursorをTrueにすると上記の例ではMediaControl以外の要素上でもカーソルが消えてしまいます。これはウィンドウの既定のカーソル表示が存在しなくなるためです。

この問題を回避するにはMediaControl以外の要素にCursorを指定するか、または、何も表示しないCursorをリソースとして追加して、MediaControl要素に設定します。（前者は検証済みですが、後者は未検証です。）





## コードのライセンス
PD/パブリックドメイン
コードについては、自分の責任で自由に使ってください。