在已使用 IProgress 更新的 WPF Window 上使用秒表和数据绑定

Using a Stopwatch and DataBinding on a WPF Window that is already being updated using IProgress

在我的 Main() WPF 程序中,我 运行 一个异步的耗时方法。当此方法为 运行ning 时,我启动了一个包含 ProgressBar 的辅助 window,我使用 IProgress 对其进行了更新。

以下是我的设置示例。

主程序:

public partial class MainWindow : Window
{
    private ProgressBarWindow pbwWindow = null;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void RunMethodAsync(IProgress<int> progress)
    {
        Dispatcher.Invoke(() =>
        {
            pbwWindow = new ProgressBarWindow("Processing...");
            pbwWindow.Owner = this;
            pbwWindow.Show();
        });

        TimeConsumingMethod(progress);
    }

    private void TimeConsumingMethod(IProgress<int> progress)
    {
        for (int i = 1; i <= 100; i++)
        {
            // Thread.Sleep() represents actual time consuming work being done.
            Thread.Sleep(100);
            progress.Report(i);
        }
    }

    private async void btnRun_Click(object sender, RoutedEventArgs e)
    {
        IProgress<int> progress;

        progress = new Progress<int>(i => pbwWindow.SetProgressUpdate(i));
        await Task.Run(() => RunMethodAsync(progress));
    }
}

包含进度条的我的 ProgressBarWindow 如下所示:

public partial class ProgressBarWindow : Window
{
    Stopwatch stopwatch = new Stopwatch();
    BackgroundWorker worker = new BackgroundWorker();
    public string ElapsedTimeString { get; set; }

    public ProgressBarWindow(string infoText)
    {
        InitializeComponent();
        SetTimer();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        StartTimer();
    }

    private void SetTimer()
    {
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;

        worker.DoWork += (s, e) =>
        {
            while (!worker.CancellationPending)
            {
                worker.ReportProgress(0, stopwatch.Elapsed);
                Thread.Sleep(1000);
            }
        };

        worker.ProgressChanged += (s, e) =>
        {
            TimeSpan elapsedTime = (TimeSpan)e.UserState;
            ElapsedTimeString = string.Format("{0}:{1}:{2}", elapsedTime.Minutes, elapsedTime.Seconds, elapsedTime.Milliseconds);
        };
    }

    private void StartTimer()
    {
        stopwatch.Start();
        worker.RunWorkerAsync();
    }

    private void StopTimer()
    {
        stopwatch.Stop();
        worker.CancelAsync();
    }

    public void SetProgressUpdate(int progress)
    {
        pbLoad.Value = progress;
        if (progress >= 100)
        {
            StopTimer();
            Close();
        }
    }
}

我从this SO answer那里借用了秒表逻辑。 然后,在我的 ProgressBarWindow 上,我有一个 TextBlock,我按如下方式使用了 Binding,正如上面的答案所说。

<TextBlock Name="tbElapsedTime" Text="{Binding ElapsedTimeString}"/>

现在当我运行程序时,方法执行,进度条更新就好了。但是,我的应该随时间更新的 TextBlock 没有更新。

为了验证我的计时器 运行 是否正常,我直接按如下方式更新了 TextBlock 值而不是 Binding 并且它按预期工作并显示了 Elapsed Time:

worker.ProgressChanged += (s, e) =>
        {
            TimeSpan elapsedTime = (TimeSpan)e.UserState;
            ElapsedTimeString = string.Format("{0}:{1}:{2}", elapsedTime.Minutes, elapsedTime.Seconds, elapsedTime.Milliseconds);
            tbElapsedTime.Text = ElapsedTimeString;
        };

所以我猜我的问题是 Binding 和可能在已经异步 运行 的 windows 上使用 BackgroundWorker?我该如何解决这个问题,以便我可以使用 DataBinding?

如 Ginger Ninja 所述,您必须实施 INotifyPropertyChanged 并使用 RelativeSource={RelativeSource Self}(作为绑定的附加设置):

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _ElapsedTimeString;
    public string ElapsedTimeString
    {
        get { return _ElapsedTimeString; }
        set
        {
            if (_ElapsedTimeString != value)
            {
                _ElapsedTimeString = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ElapsedTimeString"));
            }
        }
    }

    // ....
}

和 XAML:

<TextBlock Name="tbElapsedTime" Text="{Binding ElapsedTimeString, RelativeSource={RelativeSource Self}}"/>

数据绑定经常和MVVM结合使用。恕我直言,这是解决问题的首选方法......如果你想使用 MVVM,你必须实现一个包含所有逻辑并实现 INotifyPropertyChanged 的视图模型。比您可以简单地将属性从视图模型绑定到视图。这确保了(与 GUI 相关的)逻辑和视图之间的良好分离。