通过访问另一个 class 中的 backgroundWorker 来更新 ProgressBar
Update ProgressBar by accessing backgroundWorker in another class
我是 WPF 和 C# 编程的新手并且习惯了 OOP,我正在尝试使用最佳编程实践编写我的程序,但我已经坚持了几天,但我不是能够弄清楚如何完成这个,希望有人能提供帮助。
我想做的很简单,我有一个带有进度条和按钮的 WPF window,仅此而已,在这个 window 的 C# 后端代码中,我有一个从另一个文件调用另一个 class 中的方法的单击事件,因为 class 我有一个 BackgroundWorker,我一直在尝试更新基于 BackgroundWorker 的 ProgressBar。
我通过在控制台中写入进度和事件来确认代码有效,但我不知道该怎么做的是如何访问另一个 class 中的 BackgroundWorker 以更新我的进度条xaml 文件,或者如果这不是最好的方法,我想知道最好的方法是什么
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="35"/>
<RowDefinition Height="15"/>
<RowDefinition Height="40"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ProgressBar x:Name="progressBar"
Grid.Row = "1"
Width ="500"
Value="0"/>
<Button x:Name="btn_start"
Grid.Row = "3"
Content ="Start"
Height="30"
Width="125"
Click="btn_start_Click"/>
</Grid>
c# XAML 后端:
namespace progressBar
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btn_start_Click(object sender, RoutedEventArgs e)
{
GeneralTasks GT = new GeneralTasks();
GT.start();
}
}
}
最后是不可读的 class“GeneralTasks”:
public class GeneralTasks
{
BackgroundWorker worker = new BackgroundWorker();
public void start()
{
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine(e.ProgressPercentage + "% " + (string)e.UserState);
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
worker.ReportProgress(0, string.Format("Percentage completed: 0"));
for (int i = 0; i <= 100; i++)
{
worker.ReportProgress(i, string.Format("Percentage completed: {0}", i));
Console.WriteLine("Percentage Completed: {0}",i);
Thread.Sleep(100);
}
worker.ReportProgress(100, string.Format("Done Processing"));
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
Console.WriteLine("Done Processing");
}
}
我会跳过 GeneralTasks;除了持有 BGW 并订阅其事件外,它似乎没有做任何其他事情,但随后没有对它们做任何有用的事情。我将 BGW 放在 XAML 后端代码中,然后从 ProgressChanged 事件更新 ProgressBar(它连接到后端中改变 PB 值的方法),当 DoWork 处理程序(也在 XAML 后端文件中连接)调用 ReportProgress
如果你想使用 GeneralTasks class 作为各种“执行应该在后台完成的事情的方法”的持有者,那么你基本上可以使它成为一个方法集合,其签名与 DoWork 一致, 将其中之一指定为工作处理程序,然后启动工作程序以完成工作
如果你想坚持让 GT 持有 BGW,你需要做一些事情,比如公开一个 GT 可以在进度发生变化时引发的事件,或者传入一个可以连接到 ProgressChanged 的委托,并让XAML 后端代码传递一个更改进度条的委托(或者更简单地说,将进度条传递给 GT)
就我个人而言,我只是将所有 BGW 内容放入 XAML 后端文件中(除非它会做大量不同的事情,在这种情况下我会将这些内容放入 GT 中并从 XAML 后端的 DoWork 处理程序中调用它们)
I'm new at programming in WPF and C# and getting used to OOP...
我建议您在训练中使用更典型的现代 WPF 和 Sharp 实现。
- BackgroundWorker - 有点过时了。现在使用Task和async/await方法来实现异步
- UI元素的属性取值的主要方式是
绑定到数据上下文的属性。绑定还解决了
在任何线程中异步更改源属性的问题,
这不能直接用 UI 元素属性完成。
- 要在 WPF 中调用数据上下文的方法,它们被包装在
命令。而一些 UI 元素(包括按钮、菜单项)可以
调用这些命令。
- 由于与数据通信的主要方式是绑定,WPF
实际上不使用代码隐藏 Windows.
您的任务的演示示例,但以更现代的方式实现。
ViewModel class - 这用于设置数据上下文。
使用 .
实现
using Simplified;
using System;
using System.Threading.Tasks;
namespace ProgressView
{
public class ProgressViewModel : BaseInpc
{
private bool isProgressExecute;
private RelayCommand _startProgressCommand;
private TimeSpan _progressTime;
private double _rangeEnd;
private double _rangeBegin;
private double _currentValue;
public double RangeBegin { get => _rangeBegin; set =>Set(ref _rangeBegin, value); }
public double RangeEnd { get => _rangeEnd; set => Set(ref _rangeEnd, value); }
public TimeSpan ProgressTime { get => _progressTime; set =>Set(ref _progressTime, value); }
public double CurrentValue { get => _currentValue; private set => Set(ref _currentValue, value); }
public RelayCommand StartProgressCommand => _startProgressCommand
?? (_startProgressCommand = new RelayCommand
(
StartProgressExecuteAsync,
() => !isProgressExecute
));
private async void StartProgressExecuteAsync()
{
if (isProgressExecute)
return;
isProgressExecute = true;
StartProgressCommand.RaiseCanExecuteChanged();
double begin = RangeBegin;
double end = RangeEnd;
double range = end - begin;
TimeSpan time = ProgressTime;
DateTime beginTime = DateTime.Now;
TimeSpan elapsed;
while ((elapsed = DateTime.Now - beginTime) < time)
{
CurrentValue = begin + range * elapsed.TotalMilliseconds / time.TotalMilliseconds;
await Task.Delay(10);
}
CurrentValue = end;
isProgressExecute = false;
StartProgressCommand.RaiseCanExecuteChanged();
}
}
}
Window XAML:
<Window x:Class="ProgressView.ProgressWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ProgressView"
mc:Ignorable="d"
Title="ProgressWindow" Height="450" Width="800">
<Window.DataContext>
<local:ProgressViewModel RangeBegin="0"
RangeEnd="100"
ProgressTime="0:0:10"/>
</Window.DataContext>
<UniformGrid Columns="2">
<TextBlock Text="Begin: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding RangeBegin}" VerticalAlignment="Center"/>
<TextBlock Text="End: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding RangeEnd}" VerticalAlignment="Center"/>
<TextBlock Text="Time: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding ProgressTime}" VerticalAlignment="Center"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right">
<Run Text="Curent:"/>
<Run Text="{Binding CurrentValue, Mode=OneWay}"/>
</TextBlock>
<ProgressBar VerticalAlignment="Center" Height="20"
Minimum="{Binding RangeBegin}"
Maximum="{Binding RangeEnd}"
Value="{Binding CurrentValue, Mode=OneWay}"/>
<Button Content="Start Progress"
Command="{Binding StartProgressCommand, Mode=OneWay}"/>
</UniformGrid>
</Window>
我是 WPF 和 C# 编程的新手并且习惯了 OOP,我正在尝试使用最佳编程实践编写我的程序,但我已经坚持了几天,但我不是能够弄清楚如何完成这个,希望有人能提供帮助。
我想做的很简单,我有一个带有进度条和按钮的 WPF window,仅此而已,在这个 window 的 C# 后端代码中,我有一个从另一个文件调用另一个 class 中的方法的单击事件,因为 class 我有一个 BackgroundWorker,我一直在尝试更新基于 BackgroundWorker 的 ProgressBar。
我通过在控制台中写入进度和事件来确认代码有效,但我不知道该怎么做的是如何访问另一个 class 中的 BackgroundWorker 以更新我的进度条xaml 文件,或者如果这不是最好的方法,我想知道最好的方法是什么
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="35"/>
<RowDefinition Height="15"/>
<RowDefinition Height="40"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ProgressBar x:Name="progressBar"
Grid.Row = "1"
Width ="500"
Value="0"/>
<Button x:Name="btn_start"
Grid.Row = "3"
Content ="Start"
Height="30"
Width="125"
Click="btn_start_Click"/>
</Grid>
c# XAML 后端:
namespace progressBar
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btn_start_Click(object sender, RoutedEventArgs e)
{
GeneralTasks GT = new GeneralTasks();
GT.start();
}
}
}
最后是不可读的 class“GeneralTasks”:
public class GeneralTasks
{
BackgroundWorker worker = new BackgroundWorker();
public void start()
{
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine(e.ProgressPercentage + "% " + (string)e.UserState);
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
worker.ReportProgress(0, string.Format("Percentage completed: 0"));
for (int i = 0; i <= 100; i++)
{
worker.ReportProgress(i, string.Format("Percentage completed: {0}", i));
Console.WriteLine("Percentage Completed: {0}",i);
Thread.Sleep(100);
}
worker.ReportProgress(100, string.Format("Done Processing"));
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
Console.WriteLine("Done Processing");
}
}
我会跳过 GeneralTasks;除了持有 BGW 并订阅其事件外,它似乎没有做任何其他事情,但随后没有对它们做任何有用的事情。我将 BGW 放在 XAML 后端代码中,然后从 ProgressChanged 事件更新 ProgressBar(它连接到后端中改变 PB 值的方法),当 DoWork 处理程序(也在 XAML 后端文件中连接)调用 ReportProgress
如果你想使用 GeneralTasks class 作为各种“执行应该在后台完成的事情的方法”的持有者,那么你基本上可以使它成为一个方法集合,其签名与 DoWork 一致, 将其中之一指定为工作处理程序,然后启动工作程序以完成工作
如果你想坚持让 GT 持有 BGW,你需要做一些事情,比如公开一个 GT 可以在进度发生变化时引发的事件,或者传入一个可以连接到 ProgressChanged 的委托,并让XAML 后端代码传递一个更改进度条的委托(或者更简单地说,将进度条传递给 GT)
就我个人而言,我只是将所有 BGW 内容放入 XAML 后端文件中(除非它会做大量不同的事情,在这种情况下我会将这些内容放入 GT 中并从 XAML 后端的 DoWork 处理程序中调用它们)
I'm new at programming in WPF and C# and getting used to OOP...
我建议您在训练中使用更典型的现代 WPF 和 Sharp 实现。
- BackgroundWorker - 有点过时了。现在使用Task和async/await方法来实现异步
- UI元素的属性取值的主要方式是 绑定到数据上下文的属性。绑定还解决了 在任何线程中异步更改源属性的问题, 这不能直接用 UI 元素属性完成。
- 要在 WPF 中调用数据上下文的方法,它们被包装在 命令。而一些 UI 元素(包括按钮、菜单项)可以 调用这些命令。
- 由于与数据通信的主要方式是绑定,WPF 实际上不使用代码隐藏 Windows.
您的任务的演示示例,但以更现代的方式实现。
ViewModel class - 这用于设置数据上下文。
使用
using Simplified;
using System;
using System.Threading.Tasks;
namespace ProgressView
{
public class ProgressViewModel : BaseInpc
{
private bool isProgressExecute;
private RelayCommand _startProgressCommand;
private TimeSpan _progressTime;
private double _rangeEnd;
private double _rangeBegin;
private double _currentValue;
public double RangeBegin { get => _rangeBegin; set =>Set(ref _rangeBegin, value); }
public double RangeEnd { get => _rangeEnd; set => Set(ref _rangeEnd, value); }
public TimeSpan ProgressTime { get => _progressTime; set =>Set(ref _progressTime, value); }
public double CurrentValue { get => _currentValue; private set => Set(ref _currentValue, value); }
public RelayCommand StartProgressCommand => _startProgressCommand
?? (_startProgressCommand = new RelayCommand
(
StartProgressExecuteAsync,
() => !isProgressExecute
));
private async void StartProgressExecuteAsync()
{
if (isProgressExecute)
return;
isProgressExecute = true;
StartProgressCommand.RaiseCanExecuteChanged();
double begin = RangeBegin;
double end = RangeEnd;
double range = end - begin;
TimeSpan time = ProgressTime;
DateTime beginTime = DateTime.Now;
TimeSpan elapsed;
while ((elapsed = DateTime.Now - beginTime) < time)
{
CurrentValue = begin + range * elapsed.TotalMilliseconds / time.TotalMilliseconds;
await Task.Delay(10);
}
CurrentValue = end;
isProgressExecute = false;
StartProgressCommand.RaiseCanExecuteChanged();
}
}
}
Window XAML:
<Window x:Class="ProgressView.ProgressWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ProgressView"
mc:Ignorable="d"
Title="ProgressWindow" Height="450" Width="800">
<Window.DataContext>
<local:ProgressViewModel RangeBegin="0"
RangeEnd="100"
ProgressTime="0:0:10"/>
</Window.DataContext>
<UniformGrid Columns="2">
<TextBlock Text="Begin: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding RangeBegin}" VerticalAlignment="Center"/>
<TextBlock Text="End: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding RangeEnd}" VerticalAlignment="Center"/>
<TextBlock Text="Time: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding ProgressTime}" VerticalAlignment="Center"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right">
<Run Text="Curent:"/>
<Run Text="{Binding CurrentValue, Mode=OneWay}"/>
</TextBlock>
<ProgressBar VerticalAlignment="Center" Height="20"
Minimum="{Binding RangeBegin}"
Maximum="{Binding RangeEnd}"
Value="{Binding CurrentValue, Mode=OneWay}"/>
<Button Content="Start Progress"
Command="{Binding StartProgressCommand, Mode=OneWay}"/>
</UniformGrid>
</Window>