読者です 読者をやめる 読者になる 読者になる

かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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

メール削除とかしたけど、一定時間取返しがきくみたいな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