-1

I'm new at programming in WPF and C# and getting used to OOP, I'm trying to code my program using the best programming practices but I have been stuck for a couple of days now and I wasn't able to figure out how to complete this, hope someone out there can help.

what I'm trying to do is very simple, I have a WPF window with a progress bar and a button, that's it, and in the C# backend code for this window, I have a click event that calls a method in another class from another file, in that class I have a BackgroundWorker and I have been trying to update a ProgressBar based on the BackgroundWorker.

I confirmed that the code works by writing the progress and events in the console, what I have not idea how to do is how to access that BackgroundWorker in the other class to update the progress bar in my xaml file, or if this is not the best approach I would like to know what would be the best way to do this

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 Backend:

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

And finally the unreadable 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");
    }
}
jamesnet214
  • 1,044
  • 13
  • 21
  • As c# has good async await support meanwhile, Background Workers aren't as common anymore. Do you really need a background worker? – Klamsi May 22 '21 at 07:18

2 Answers2

0

I'd skip having GeneralTasks; it doesn't seem to do anything other than hold a BGW and subscribe to its events, but then doesn't do anything useful with them. I'd put the BGW in the XAML backend code and then update the ProgressBar from the ProgressChanged event (which is wired up to a method in the backend that alters the PB value) which is called when the DoWork handler (also wired up in the XAML backend file) calls ReportProgress

If you want to use the GeneralTasks class as a holder for various "methods that do things that should be done in the background" then you can basically make it a collection of methods whose signatures align with DoWork, assign one of them to be the work handler and then start the worker to get the work done

If you want to persist with GT holding the BGW you'll need to do something like expose an event that GT can raise when progress has changed or pass in a delegate that it can wire up to ProgressChanged, and have the XAML backend code pass a delegate that changes the progressbar (or more simplistically, pass the progressbar to GT)

Personally I'd just put all the BGW stuff in the XAML backend file (unless there is a huge and varied number of things it will do, in which case I'd put those things into GT and call them from the DoWork handler in the XAML backend)

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
0

I'm new at programming in WPF and C# and getting used to OOP...

I advise you to use more typical modern WPF and Sharp implementations in your training.

  1. BackgroundWorker - slightly outdated. Nowadays, Task and async/await methods are used to implement asynchrony.
  2. The main way to get values ​​for the properties of UI elements is to bind to the properties of the Data Context. Bindings also solve the problem of asynchronously changing source properties in any thread, which cannot be done directly with UI element properties.
  3. To call methods of the Data Context in WPF, they are wrapped in commands. And some UI elements (including buttons, menu items) can call these commands.
  4. Since the main way of communicating with data is bindings, WPF practically does not use Code Behind Windows.

Demo example of your task, but implemented in a more modern way.

ViewModel class - This is used to set the data context.
Implemented using BaseInpc and RelayCommand.

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>
EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • Thank you so much for all the explanation, definitively this code is too advanced for me, i will learn and study more and hope i can implement and understand all of this in the near future, again thanks a lot :D – Luis Machado May 23 '21 at 02:04
  • The essence is very simple. You create a numeric property to which you bind the ProgressBar.Value value. After that, you can change this value asynchronously (in the Task). The ProgressBar will automatically display it. And since the processing (waiting) occurs in an asynchronous method, this does not cause GUI lags. Everything else (additional properties, UI elements) is just for testing convenience. – EldHasp May 23 '21 at 05:26