トップ回答者
【WPF】MVVMでBusyIndicatorを使う場合(または非同期コマンドでUIをブロックする場合)の最適ソリューションて何だと思いますか?

質問
-
訳あってWPFを使うことになり、MVVMで実装してるのですが、非同期コマンドでつまずきました。
ボタンを押してデータベースからリストを取ってくる間、BusyIndicatorを表示させたい(UIをブロックしたい)のですが、MVVMでやると難しくないですか?
結構当たり前にやる処理だと思ったのですが、調べてみても適当なソリューションが見つからず、皆さんどうやって実装してるのか気になってトピックを立てました。
みなさんの実装をご紹介いただけると幸です。
ちなみに、調べて見つかったのは
◆非同期プログラミング - 非同期 MVVM アプリケーションのパターン: コマンド
https://msdn.microsoft.com/ja-jp/magazine/dn630647.aspx
↑この例は、UIブロックしてません。
◆Implementing a busy indicator using a visual overlay in MVVM
http://awkwardcoder.blogspot.jp/2013/06/implementing-busy-indicator-using.html
↑この例ですと、かなり大変な実装になっています。
もっと簡単に出来る方法があるんじゃないかと・・・。
どうでしょうか。
※開発環境、フレームワークは最新、MVVMフレームワークはPrismを使っていますが、Prismにこだわらず紹介いただけると幸です。
回答
-
ViewModelからBusyプロパティでインジケータの表示非表示を変えれば良いです。
<Window x:Class="WpfApplication6.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:tool="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:local="clr-namespace:WpfApplication6" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <Window.Resources> <BooleanToVisibilityConverter x:Key="conv" /> </Window.Resources> <Grid> <Button Content="hoge" Width="100" Height="30" Command="{Binding LoadCommand}" /> <Grid Visibility="{Binding IsBusy, Converter={StaticResource conv}}"> <Grid.Background> <SolidColorBrush Color="Black" Opacity="0.75" /> </Grid.Background> <!--NuGet:Extended WPF Toolkit--> <tool:BusyIndicator IsBusy="True" BusyContent="Loading..." /> </Grid> </Grid> </Window>
class MainWindowViewModel : BindableBase { private bool _IsBusy; public bool IsBusy { get { return _IsBusy; } set { SetProperty(ref _IsBusy, value); } } private DelegateCommand _LoadCommand; public DelegateCommand LoadCommand { get { if(_LoadCommand == null) { _LoadCommand = new DelegateCommand(Loading); } return _LoadCommand; } } private async void Loading() { IsBusy = true; await Task.Run(() => { Thread.Sleep(3000); }); IsBusy = false; } }
各画面に予めインジケータを埋め込むのが手間であれば、
先に挙げて頂いたURL2つ目のようにコントロールをホストしてインジケータを埋め込む感じでしょうか。
それについてはURL先からダウンロードできるソースコードを読めばわかります。
- 回答としてマーク アドマイヤコジーン 2016年5月31日 6:04
すべての返信
-
レスが付かないようですし、私が勘違いしているかもしれませんので、もう少し教えて下さい。
一般的にUIをブロックしたくないので非同期で実行すると思うのですが、その辺りはどうなのでしょうか?
UIをブロックするのであれば、UIと同期してデータベースからリストを取ってくるという普通の処理を行うだけで良いような気がするのですが・・・???
つまり、非同期で実行している間にユーザーが何もできないのであれば、非同期で実行する意味がないのではないのかという疑問です。★良い回答には回答済みマークを付けよう! MVP - .NET http://d.hatena.ne.jp/trapemiya/
-
こんにちは。
MVVMかどうかで難易度が変わりますか?
IsBusyプロパティなどをバインドさせるか、Message機能を使ってViewで処理させるだけではないでしょうか。View側ではいくつか方法があると思いますが「UIブロック」とはどういう定義でしょう。
操作不能であれば、後者のURLのようにレイヤーを上に被せるとか、
WindowをDisableにしてIndicatorを別で表示させるなどになると思います。
※後者のURLは複雑な実装になってますが、要点としてはバインドさせたパラメータでWindowを覆ったGridの表示・非表示を切り替えているだけのように見えます。厳密にはアプリケーションの終了は出来ますので、操作不能にさせるよりも正しい中断処理を実装することのほうが重要でしょうか。
-
>「UIブロック」とはどういう定義でしょう。
WebやiOSアプリ等でよく見る、待ってる間にグルグルアニメーションなどを表示させて、他のボタンを押させない、みたいな実装です。
別にアプリケーションの終了は構いません。画面内の他の機能をブロックしたい&アニメーションを見せたい、ということです。
(普通に良くあることだと思ってて通じると思ってましたすみません)
>IsBusyプロパティなどをバインド
>WindowをDisableにしてIndicatorを別で表示させるなどになると思います。
ここもう少し詳しく紹介して頂けないでしょうか?
やっぱり各画面にBusyIndicatorを配置しておいて、TriggerActionを自作して、とかでしょうか?
もっと簡単にできます?
-
ViewModelからBusyプロパティでインジケータの表示非表示を変えれば良いです。
<Window x:Class="WpfApplication6.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:tool="http://schemas.xceed.com/wpf/xaml/toolkit" xmlns:local="clr-namespace:WpfApplication6" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <Window.Resources> <BooleanToVisibilityConverter x:Key="conv" /> </Window.Resources> <Grid> <Button Content="hoge" Width="100" Height="30" Command="{Binding LoadCommand}" /> <Grid Visibility="{Binding IsBusy, Converter={StaticResource conv}}"> <Grid.Background> <SolidColorBrush Color="Black" Opacity="0.75" /> </Grid.Background> <!--NuGet:Extended WPF Toolkit--> <tool:BusyIndicator IsBusy="True" BusyContent="Loading..." /> </Grid> </Grid> </Window>
class MainWindowViewModel : BindableBase { private bool _IsBusy; public bool IsBusy { get { return _IsBusy; } set { SetProperty(ref _IsBusy, value); } } private DelegateCommand _LoadCommand; public DelegateCommand LoadCommand { get { if(_LoadCommand == null) { _LoadCommand = new DelegateCommand(Loading); } return _LoadCommand; } } private async void Loading() { IsBusy = true; await Task.Run(() => { Thread.Sleep(3000); }); IsBusy = false; } }
各画面に予めインジケータを埋め込むのが手間であれば、
先に挙げて頂いたURL2つ目のようにコントロールをホストしてインジケータを埋め込む感じでしょうか。
それについてはURL先からダウンロードできるソースコードを読めばわかります。
- 回答としてマーク アドマイヤコジーン 2016年5月31日 6:04
-
ここでいいかな?
非同期処理というより、時間のかかる処理を行うので、何とかしたいということですよね?
待機中を表すポップアップウィンドウを作って、それをモーダル表示(ShowDialog)するではだめですか?
MVVM だからほかのポップアップがあってはだめということはありませんので、それが一番簡素だと思います。
見た目に凝ったことをしたい(Webアプリのようにレイヤーをかぶせるなど)のなら、これでは実現できませんが、一般的なデスクトップの世界ならユーザー的には、一番当たり障りのない実装だと思います。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx