かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

一定時間やりなおしが出来るようにする

メール削除とかしたけど、一定時間取返しがきくみたいなUIがありますよね。 あぁいうのどうやるんだろうというのを考えてみました。

UWPでやってみますが、WPFでもXamarin.Formsでも基本的に同じ感じになると思います。見た目凝るのが一番難しそう。

Modelの作成

とりあえず、ReactivePropertyとPrism.Coreを参照に追加して以下のコードを書きます。

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;

namespace App2
{
    class LazyActionSampleApp
    {
        public ObservableCollection<Guid> Guids { get; } = new ObservableCollection<Guid>();

        public Task<bool> AddGuidAsync(IObservable<Unit> cancel)
        {
            var guid = Guid.NewGuid();
            this.Guids.Add(guid);

            var tcs = new TaskCompletionSource<bool>();

            Observable.Return<Func<bool>>(() => true)
                .Delay(TimeSpan.FromMilliseconds(3000))
                .Amb(cancel.Take(1).Select<Unit, Func<bool>>(_ => () => { this.Guids.Remove(guid); return false; }))
                .Subscribe(x => tcs.SetResult(x()));

            return tcs.Task;
        }
    }
}

Reactive ExtensionsのAmbを使うと先に発火したほうを後ろに流すことができます。これを使ってコミット処理(今回はtrueを返すだけ)とキャンセル処理(今回はコレクションに追加したものを削除する)といったFuncを合成してSubscribeして実行しています。一応処理が成功したのかキャンセルされたのかは戻り値で返すようにしました。

あとは、VMとVを作っておしまい。

using Prism.Mvvm;
using Reactive.Bindings;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;

namespace App2
{
    public class MainPageViewModel : BindableBase
    {
        private LazyActionSampleApp Model { get; } = new LazyActionSampleApp();

        public ObservableCollection<Guid> Guids => this.Model.Guids;

        public ReactiveProperty<bool> IsBusy { get; } = new ReactiveProperty<bool>(false);

        public AsyncReactiveCommand AddCommand { get; }

        public AsyncReactiveCommand CancelAddCommand { get; }

        public MainPageViewModel()
        {
            this.AddCommand = new AsyncReactiveCommand(this.IsBusy.Select(x => !x).AsObservable());
            this.CancelAddCommand = new AsyncReactiveCommand(this.IsBusy.AsObservable());

            var cancelTrigger = new Subject<Unit>();
            this.AddCommand
                .Subscribe(async x =>
                {
                    this.IsBusy.Value = true;
                    await this.Model.AddGuidAsync(cancelTrigger);
                    this.IsBusy.Value = false;
                });

            this.CancelAddCommand.Subscribe(_ =>
            {
                cancelTrigger.OnNext(Unit.Default);
                return Task.CompletedTask;
            });
        }
    }
}
using Windows.UI.Xaml.Controls;

namespace App2
{
    public sealed partial class MainPage : Page
    {
        private MainPageViewModel ViewModel { get; } = new MainPageViewModel();

        public MainPage()
        {
            this.InitializeComponent();
        }
    }
}
<Page
    x:Class="App2.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App2"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Button Content="Add"
                Command="{x:Bind ViewModel.AddCommand}" 
                HorizontalAlignment="Stretch"/>
        <Button Content="Cancel"
                Command="{x:Bind ViewModel.CancelAddCommand}" 
                HorizontalAlignment="Stretch"
                Grid.Row="1" />
        <ListView ItemsSource="{x:Bind ViewModel.Guids}"
                  Grid.Row="2" />
    </Grid>
</Page>

実行結果

こんな感じになります。

www.youtube.com