WPF-Viewを明示的に更新する


WPFで重たい処理をスレッド化せずに、処理の途中でViewを更新しようとしても変わらない。
本来はビジネスロジックはスレッドで動かして、UIスレッドを邪魔しない機構を検討すべきではあるが、下記参考ページの方法が手っ取り早かったので採用。
https://www.ipentec.com/document/csharp-wpf-implement-application-doevents

下記、プログレスバーの更新を例に解決方法を説明。

実行結果

XAML

プログレスバーと、重たい処理を発火するボタンを配置。
プログレスバーのレンジは0~100で、進捗値はViewModelの"prog.Value"にバインド。

<Window x:Class="prism_test.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="170" Width="525">
    <Grid>
        <StackPanel>
            <ProgressBar x:Name="progressBar" Maximum="100" Minimum="0" Value="{Binding prog.Value}" Height="50" Margin="10,10,10,10" />
            <Button Content="Button" HorizontalAlignment="Left" Height="40" Margin="10,10,10,10" Width="100" Command="{Binding DoHeavyProc}"/>
        </StackPanel>
    </Grid>
</Window>

コードビハインド

肝になるのが"UpdataView"メソッド。これをViewModelからデリゲートを介して呼び出す。

using System.Windows;
using System.Windows.Threading;

namespace prism_test.Views
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// ViewModelへの参照
        /// </summary>
        ViewModels.MainWindowViewModel vm; 

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            // ViewModelへの参照記憶
            vm = (ViewModels.MainWindowViewModel)DataContext;

            // View側処理をViewModelに設定
            vm.UpdateView = UpdataView;
        }

        /// <summary>
        /// Viewの更新
        /// </summary>
        /// <param name="prog">進捗</param>
        public void UpdataView()
        {
            DispatcherFrame frame = new DispatcherFrame();
            var callback = new DispatcherOperationCallback(obj =>
            {
                ((DispatcherFrame)obj).Continue = false;
                return null;
            });
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, callback, frame);
            Dispatcher.PushFrame(frame);
        }
    }
}

ViewModel

ボタン押下時に進捗値を20ms毎にカウントアップしています。

using Prism.Mvvm;
using Reactive.Bindings;

namespace prism_test.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Application";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        /// <summary>
        /// 進捗
        /// </summary>
        public ReactiveProperty<int> prog { get; set; } = new ReactiveProperty<int>();

        /// <summary>
        /// コマンド
        /// </summary>
        public ReactiveCommand DoHeavyProc { get; }

        /// <summary>
        /// View処理へのデリゲート
        /// </summary>
        public System.Action UpdateView;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindowViewModel()
        {
            prog.Value = 0;
            this.DoHeavyProc = new ReactiveCommand().WithSubscribe(this.buttonpush);
        }

        /// <summary>
        /// ボタン押下
        /// </summary>
        public void buttonpush()
        {
            prog.Value = 0;
            for(var i = 0; i < 100; i++)
            {
                prog.Value++;

                // Viewを更新
                UpdateView();

                System.Threading.Thread.Sleep(20);
            }
        }
    }
}