在已使用 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 相关的)逻辑和视图之间的良好分离。
在我的 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 相关的)逻辑和视图之间的良好分离。