[英]Progress bar not updating from background worker
我想要做的是將進度條作為單獨的 window,它將根據另一個表單上其他長時間處理工作的進度進行更新。 當我通過進度條代碼進行調試時,DoWork 和 ProgressChanged 都按預期被調用,但 UI 無法更新。 代碼甚至到達 Worker_RunWorkerCompleted 並關閉進度條。 我已經閱讀了大約 20 篇文章,但無法確定這個問題。 我知道我應該為 WPF 使用視圖 model 但下面的代碼被簡化以查看我是否可以讓進度條更新。 我知道我遺漏了一些簡單的東西,但我無法確定它是什么。
XAML:
<Window x:Class="CGXAcquisition.Forms.Dialog.FileProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="File Upload Progress" Height="100" Width="300">
<Grid Margin="20">
<ProgressBar Minimum="0" Maximum="100" Name="pbStatus" />
<TextBlock Text="{Binding ElementName=pbStatus, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Window>
背后的代碼:
public partial class FileProgressBar : Window, INotifyPropertyChanged
{
BackgroundWorker worker = new BackgroundWorker();
private int fileLines = 0;
private int totalFileLines = 0;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public int FileLines
{
get { return fileLines; }
set
{
fileLines = value;
Percentage = (int)Math.Round((double)fileLines / TotalFileLines);
OnPropertyChanged("FileLines");
}
}
public int TotalFileLines
{
get { return totalFileLines; }
set { totalFileLines = value; }
}
public int Percentage { get; set; }
public FileProgressBar(int totalLines)
{
InitializeComponent();
TotalFileLines = totalLines;
Loaded += Window_ContentRendered;
}
private void Window_ContentRendered(object sender, EventArgs e)
{
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.Close();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for(int i = 0; i < 100; i++)
{
Thread.Sleep(100);
int percent = i / 100;
worker.ReportProgress(percent, i);
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pbStatus.Value = e.ProgressPercentage;
}
}
創建存在於另一種形式上的進度條的代碼:
private void ShowProgressBar()
{
progress = new FileProgressBar(FileInfo.LineCount);
progress.Show();
}
我在 OnLoaded 事件中調用 ShowProgressBar()
private void OnLoaded(object sender, RoutedEventArgs e)
{
ShowProgressBar();
}
在我的構造函數中,我將事件處理程序添加到 Loaded 事件
Loaded += OnLoaded;
我希望看到 UI 使用我擁有的代碼進行更新。 我試過使用 Dispatcher 來更新 UI,但據我了解,worker_ProgressChanged 應該可以減少對調度程序的需求。 我查看了 StackOverflow、MSDN 和其他幾個博客,但沒有任何內容可以幫助我確定我遺漏了什么。
預先感謝您的幫助。
更新我終於注意到我的數學是錯誤的。 這一行 int percent = i / 100; 應該是整數百分比 ((int)(double)i / 100 * 100);
我知道我可以只使用 i 但我想做對。
Backgroundworker
已棄用 API。相反,您應該盡可能使用Task.Run
或異步 API。 要報告進度,您應該使用Progress<T>
class。
您的代碼的主要問題:您的計算有誤。 目前,您的公式產生的最大值為1
( 100/100
)。
此外, BackgroundWorker.ReportProgress
接受一個int
作為百分比參數。 從double
轉換為int
的隱式類型將截斷double
值的小數位。 因此, 0.9d
變為0
。
這意味着ProgressBar.Value
將一直為0
直到i == 100
。 然后該值將為1
並且您關閉Window
。
計算百分比的正確公式是: value / max_value * 100
。
同樣重要的是要理解,由於除法,您參與的數字變量或除數必須是double
類型。 否則,由於從double
到int
的隱式轉換,中間結果值將被截斷。 換句話說,你完全失去了小數位(沒有四舍五入)。
您沒有透露有關后台工作的任何詳細信息。 但是根據窗口的名稱,我假設您是從文件中讀取的。 在這種情況下,不要在后台線程上執行作業。 而是使用StreamReader
的異步 API。
此外,控件從不實現INotifyPropertyChanged
。 它有幾個缺點:性能、屬性不能作為綁定目標、不能動畫等。
相反,控件或一般擴展DependencyObject
的類型應該將它們的屬性實現為依賴屬性。
改進后的FileProgressBar
實現和固定計算應如下所示:
FileProgressBar.xaml.cs
public partial class FileProgressBar : Window
{
public int FileLines
{
get => (int)GetValue(FileLinesProperty);
set => SetValue(FileLinesProperty, value);
}
public static readonly DependencyProperty FileLinesProperty = DependencyProperty.Register(
"FileLines",
typeof(int),
typeof(FileProgressBar),
new PropertyMetadata(default(int), OnFileLinesChanegd));
// If this property is used for data binding it must be a dependency property too
public int TotalFileLines { get; set; }
// If this property is used for data binding it must be a dependency property too
public int Percentage { get; set; }
public FileProgressBar(int totalLines)
{
InitializeComponent();
this.TotalFileLines = totalLines;
this.Loaded += Window_ContentRendered;
}
private static void OnFileLinesChanegd(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as FileProgressBar;
this_.Percentage = (int)Math.Round((double)e.NewValue / this_.TotalFileLines * 100d);
}
private async void Window_ContentRendered(object sender, EventArgs e)
{
var progressReporter = new Progress<double>(progress => pbStatus.Value = progress);
await DoWorkAsync(progressReporter);
OnWorkCompleted();
}
private void OnWorkCompleted() => Close();
async Task DoWorkAsync(IProgress<double> progressReporter)
=> await Task.Run(async () => await LongRunningTaskAsync(progressReporter));
private static async Task LongRunningTaskAsync(IProgress<double> progressReporter)
{
for (int i = 0; i < 100; i++)
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
double percent = i / 100d * 100d;
progressReporter.Report(percent);
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.