Как обновить 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. Вопросы по этой теме были тут но все примеры, что я видел были с кодом в главном окне.