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



在我的 Main(( WPF 程序中,我异步运行一个耗时的方法。当此方法运行时,我会启动一个包含进度条的辅助窗口,我使用 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));
}
}

包含进度条的进度条窗口如下所示:

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();
}
}
}

我从这个 SO 答案中借用了秒表逻辑。 然后,在我的进度栏窗口上,我有一个文本块,我使用了如下所示的绑定,就像上面的答案所说的那样。

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

现在,当我运行程序时,该方法将执行,并且进度条更新正常。但是,我的文本块应该随着经过的时间而更新,但没有得到更新。

为了验证我的计时器是否运行正常,我直接更新了 TextBlock 值,如下所示而不是绑定,它按预期工作并显示"经过的时间":

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

所以我猜我的问题出在绑定上,并且可能在已经异步运行的窗口上使用后台工作者?如何解决此问题,以便可以使用数据绑定?

正如 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 相关(逻辑和视图之间的良好分离。

最新更新