通过访问另一个 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“GeneralTask​​s”:

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

我会跳过 GeneralTask​​s;除了持有 BGW 并订阅其事件外,它似乎没有做任何其他事情,但随后没有对它们做任何有用的事情。我将 BGW 放在 XAML 后端代码中,然后从 ProgressChanged 事件更新 ProgressBar(它连接到后端中改变 PB 值的方法),当 DoWork 处理程序(也在 XAML 后端文件中连接)调用 ReportProgress

如果你想使用 GeneralTask​​s 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 实现。

  1. BackgroundWorker - 有点过时了。现在使用Task和async/await方法来实现异步
  2. UI元素的属性取值的主要方式是 绑定到数据上下文的属性。绑定还解决了 在任何线程中异步更改源属性的问题, 这不能直接用 UI 元素属性完成。
  3. 要在 WPF 中调用数据上下文的方法,它们被包装在 命令。而一些 UI 元素(包括按钮、菜单项)可以 调用这些命令。
  4. 由于与数据通信的主要方式是绑定,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>