Как обновить GUI для WPF приложения (MVVM) с помощью DataBinding и данных из другого потока?

Приступил к изучению потоков и решил проверить полученные знания на простеньком приложении. Делаю в WPF по MVVM примитивный секундомер. В GUI есть текстбокс, где отображается и кнопка запуска\остановки, которая меняет вид в зависимости от того, работает секундомер или нет. ViewModel файл в отдельном классе, его задача обновлять открытое свойство DisplayTime, куда записывается время из секундомера в этом классе реализован интерфейс OnPropertyChanged.

Сам секундомер на основе Stopwatch класса в отдельном файле (Model). В нем я сделал событие, которое срабатывает 60 раз в секунду, Дисплей подписан на это событие и при срабатывании обновляет свойство DisplayTime, и уведомляет об изменении в OnProperty Changed.

MainWindow и текстбокс для отображения связан с помощью DataBinding. Подскажите, пожалуйста, как при такой реализации правильно создать поток для секундомера, и чтобы свойство обновлялось с помощью DataBinding.

Я посмотрел разные туториалы с BackgroundWorker, попытался применить то, что там было сказано, но получается просто создать поток, но я не понимаю как им управлять, не смотря на то, что код самого секундомера работает, GUI не обновляется. К тому же все примеры, всю логику приложений делали в MainWindow.

    public partial class MainWindow : Window
{
    private Display display;
    private bool isRunning = false;
    

    public MainWindow()
    {
        InitializeComponent();
        DataContext = display;
    }
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (isRunning)
        {
            startButtonText.Text = "Start";
            isRunning = false;
            display.StartTimer();
        }
        else if(!isRunning)
        {
            startButtonText.Text = "Stop";
            display.StopTimer();
        }
    }
}

Здесь только логика работы кнопки. Я, честно, теряюсь как правильно создать поток.

Это код ViewModel дисплея

    internal class Display : INotifyPropertyChanged
{

    string displayTimer;
    private bool isRunning;
    
    private StopWatch stopWatch = new StopWatch();

    public Display()
    {
        DisplayTimer = "0:00:00";
        stopWatch.timeStamp += UpdateTimer;
    }

    public bool IsRunning
    {
        get { return isRunning; }
    }

    public string DisplayTimer
    {
        get { return displayTimer; }
        set { displayTimer = value; OnPropertyChange(nameof(DisplayTimer)); }
    }

    private void UpdateTimer(TimeSpan ts)
    {
        DisplayTimer = String.Format($"{ts.Minutes:00}:{ts.Seconds:00}:{ts.Milliseconds:00}");
    }


    public void StartTimer()
    {
        isRunning = true;
        stopWatch.EnableTimer();
       
    }
    public void StopTimer()
    {
        isRunning = false;
        stopWatch.DisableTimer();
    }


    public event PropertyChangedEventHandler? PropertyChanged;
    public void OnPropertyChange(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Это код самого секундомера

     internal class StopWatch
{
    private Stopwatch sw = new Stopwatch();
    private int delay;

    private bool isEnabled;
    private TimeSpan timespan;

    public StopWatch()
    {
        
        delay = 16;
    }

    public StopWatch(int delay)
    {
        Delay = delay;
    }

    public int Delay
    {
        get { return delay; }
        set { delay = value; }
    }
    public void EnableTimer()
    {
        isEnabled = true;
        double timer = delay;
        
        sw.Start();

        while (isEnabled)
        {
            TimeSpan t1 = sw.Elapsed;
            TimeSpan t2 = sw.Elapsed;
            timespan = t2 - t1;
            
            timer -= timespan.Milliseconds;
            if (timer <= 0)
            {
                timespan = sw.Elapsed;
                timeStamp?.Invoke(timespan);
                timer = delay;
            }
        }
    }

    public void DisableTimer()
    {
        isEnabled = false;
        sw.Stop();
    }

    public delegate void TimeStampHandler(TimeSpan ts);
    public event TimeStampHandler timeStamp;
}

Код секундомера работает. Его реализация может вызывать вопросы, но мне нужно прежде всего понять, как заставить его обновлять GUI. Вопросы по этой теме были тут но все примеры, что я видел были с кодом в главном окне.


Ответы (0 шт):