JC97 JC97 - 29 days ago 7
C# Question

C# ProgressBar Databindings in backgroundworker

I'm no expert in c# but what I'm trying to do is update a progressbar in a Background worker. I'm using the following code:

progressBar1.DataBindings.Add("Value", _dm, "Progress", true,
DataSourceUpdateMode.OnPropertyChanged);


This works when executed without background worker, on the GUI thread. The Progress property is a property that updates (using INotifyPropertyChanged) from the progress of another backgroundworker (where I don't have access to).

How can I make it work so that it updates with the use of a backgroundworker instead of putting it all on the GUI thread?

My code (simplified):

class DownloadManager : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;

private double _progressValue;

public double Progress
{
get { return _progressValue; }
private set
{
if (!value.Equals(_progressValue))
{
_progressValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Progress"));
}
}
}

public void Download()
{
var downloader = new Downloader();

downloader.DownloadProgressChanged += (sender, e)
=> Progress = e.ProgressPercentage;

downloader.Execute();
}
}

public partial class MainForm
{
private readonly DownloadManager _dm;
public MainForm()
{
InitializeComponent();
_dm = new DownloadManager();
}

private void btnDownload_Click(object sender, EventArgs e)
{
//TRIED HERE ...
progressBar1.DataBindings.Add("Value", _dm, "Progress", true,
DataSourceUpdateMode.OnPropertyChanged);
bwDownload.RunWorkerAsync();
}
}

private void bwDownload_DoWork(object sender, DoWorkEventArgs e)
{
//AND TRIED HERE
progressBar1.DataBindings.Add("Value", _dm, "Progress", true, DataSourceUpdateMode.OnPropertyChanged);

//THIS AINT WORKING EITHER
if (progressBar1.InvokeRequired) {
progressBar1.Invoke(new MethodInvoker(()
=> progressBar1.DataBindings.Add("Value", _dm, "Progress", true,
DataSourceUpdateMode.OnPropertyChanged)));
}

_dm.Download();
}
}

Answer

Data bindings should be created in your constructor, as you need to create the binding only once and not every time it's needed:

public MainForm() 
{
    InitializeComponent();
    _dm = new DownloadManager();
    progressBar1.DataBindings.Add("Value", _dm, "Progress", true, DataSourceUpdateMode.OnPropertyChanged);
}

Your code is not working because you try to update the UI from non-ui thread. You need need to wrap the code in Invoke() call, for example using a proxy property on the Form:

public partial class MainForm
{   
    private double _progress;
    public double Progress
    {
        get { return _progress; }
        set
        {
            _progress = value;

            // If not in the UI thread -> wrap the update in an Invoke() call:
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(() => progressBar1.Value = (int) _progress), new object[] { });
                return;
            }

            // Else update directly
            progressBar1.Value = (int) value;
        }
    }

    private readonly DownloadManager _dm;
    public MainForm()
    {
        InitializeComponent();
        _dm = new DownloadManager();

        // Bind _db.Progress <-> this.Progress
        DataBindings.Add("Progress", _dm, "Progress", true, DataSourceUpdateMode.OnPropertyChanged);
    }
Comments