質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.11%

[WPF][MVVM] MVVMで定期的に処理を行う方法について

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 119

WTB_21

score 2

ViewModelで定期的に動作する処理を実装したい

定期的に動作する処理の実装を行いたいと考えております。
コードビハインドでDispatcherTimerを使用することにより実装することが出来ましたが、
MVVMではコードビハインドに書くことは推奨されないとのことですのでMVVMに則った
実装方法についてご教授いただけないでしょうか。

実装したコード

View

<Window x:Class="DispatcherTimerTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Label Content="{Binding LabelContent}"/>
    </Grid>
</Window>
    public partial class MainWindow : Window
    {
        public System.Windows.Threading.DispatcherTimer dispatcherTimer;

        public MainWindow()
        {
            InitializeComponent();
            DataContext = ViewModel.Instance;
            dispatcherTimer = new System.Windows.Threading.DispatcherTimer(System.Windows.Threading.DispatcherPriority.Normal);
            dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            dispatcherTimer.Tick += DispatcherTimer_Tick;
            dispatcherTimer.Start();

        }

        ~MainWindow()
        {
            dispatcherTimer.Stop();
        }

        private void DispatcherTimer_Tick(object sender, EventArgs e)
        {
            ViewModel.Instance.LabelContent += 1;
        }
    }

ViewModel

    class ViewModel : INotifyPropertyChanged
    {
        public static ViewModel Instance { get; } = new ViewModel();

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        public int LabelContent
        {
            get { return _LabelContent; }
            set
            {
                _LabelContent = value;
                OnPropertyChanged(nameof(LabelContent));
            }
        }
        private int _LabelContent;
    }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

ViewModelでDispatcherTimer を使ってはダメということはないので、単に移せばいいんじゃないでしょうか。

UIスレッドで動かす意味はないのでこちらなどでもいいでしょう。
Timer クラス (System.Timers) | Microsoft Docs
Timer クラス (System.Threading) | Microsoft Docs

こういったのもあります。
Observable.Timer Method (System.Reactive.Linq) | Microsoft Docs


追記 実装例
後片付け省略^^;
それぞれ同じ意味にしたつもりですが、間違ってたらすいません^^;

<Window
  x:Class="DispatcherTimerTest.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:DispatcherTimerTest"
  Width="800"
  Height="450"
  DataContext="{x:Static local:ViewModel.Instance}">
  <StackPanel>
    <Label Content="{Binding LabelContent1}" />
    <Label Content="{Binding LabelContent2}" />
    <Label Content="{Binding LabelContent3}" />
    <Label Content="{Binding LabelContent4}" />
  </StackPanel>
</Window>
using System;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;

namespace DispatcherTimerTest
{
    public class ViewModel : INotifyPropertyChanged
    {
        public static ViewModel Instance { get; } = new ViewModel();


        public int LabelContent1 { get => _LabelContent1; set => Set(ref _LabelContent1, value); }
        private int _LabelContent1;

        public int LabelContent2 { get => _LabelContent2; set => Set(ref _LabelContent2, value); }
        private int _LabelContent2;

        public int LabelContent3 { get => _LabelContent3; set => Set(ref _LabelContent3, value); }
        private int _LabelContent3;

        public int LabelContent4 { get => _LabelContent4; set => Set(ref _LabelContent4, value); }
        private int _LabelContent4;

        private System.Windows.Threading.DispatcherTimer dispatcherTimer;
        private System.Timers.Timer timersTimer;
        private System.Threading.Timer threadingTimer;
        private IDisposable observableTimer;

        public ViewModel()
        {
            dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
            dispatcherTimer.Interval = TimeSpan.FromMilliseconds(100);
            dispatcherTimer.Tick += (s, e) => LabelContent1 += 1;
            dispatcherTimer.Start();


            timersTimer = new System.Timers.Timer(100);
            timersTimer.Elapsed += (s, e) => LabelContent2 += 1;
            timersTimer.AutoReset = true;
            timersTimer.Enabled = true;


            threadingTimer = new System.Threading.Timer(x => LabelContent3 += 1, null, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100));


            // NuGet System.Reactive.Linq
            observableTimer = Observable.Timer(TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100))
               .Subscribe(_ => LabelContent4 += 1);

        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool Set<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if(Equals(storage, value)) return false;

            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        #endregion
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/06/30 22:31

    DispatcherTimer の使い方だけでなく4パターンもの実装方法をご教授くださりありがとうございます。
    余談ですが、各プロパティのOnPropertyChangedの実装方法が煩雑だと嘆いていたのですが、
    サンプルで頂いたコードならすっきりと書くことが出来そうなので真似させて頂こうと思います。

    キャンセル

  • 2020/06/30 22:51

    INotifyPropertyChangedはもう定跡になってるといっていいと思います。
    大体皆さんベースのクラスを作っています(最初から入っていたらいいのですがw)
    https://github.com/microsoft/WindowsTemplateStudio/blob/dev/code/src/UI/Mvvm/Observable.cs

    ReactivePropertyも(日本では)大変人気があります。

    キャンセル

  • 2020/07/01 12:27

    定石となっているのですね。ベースクラスを作成したいと思います。
    ReactivePropertyについても調べてみたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.11%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる