0

as the title suggests I am trying to update a progress bar using an event handler that is spawned by a function in a separate class. I think it's something to do with delegation and ownership of the form however I'm at a loss as to how to implement this properly as all the suggestions are in C# and my attempts at conversion have either errored out or just not worked at all.

My code performs a simple function, using WinSCP it downloads a file (or folder) from a remote directory to a local one, this functionality works fine. However I'm attempting to make progress bars to track the total progress of downloads as some of these may become long operations.

I have set up a BackgroundWorker to fulfil this task and as it stands if I have the function in another class (Desirable due to the nature of how I like to code, to avoid mess etc..) the progress bar does not update on the form, though the values are definitely there as proven by break-points.

Please see below my code, any opinions for help are much appreciated.

This is inside my class BackupUtility.vb (The form class)

Imports System.ComponentModel
Imports WinSCP

Public Class BackupUtility

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        If Not BackgroundWorker.IsBusy Then
            BackgroundWorker.RunWorkerAsync()
        End If
    End Sub

    Private Sub BackgroundWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker.DoWork
        testBackupServer()
    End Sub

    Public Sub BackgroundWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker.ProgressChanged
        Dim progress = e.UserState

        TotalDownloadFileProgress.Value = progress.FileProgress
        TotalDownloadProgress.Value = progress.FolderProgress

        Label1.Text = progress.fileprogress
    End Sub

    Public Function testBackupServer() As Task
        Dim progressObject As New DownloadProgress

        Dim sessionOptions As New SessionOptions
        With sessionOptions
            .Protocol = Protocol.Sftp
            .HostName = "********"
            .UserName = "********"
            .Password = "********"
            .SshHostKeyPolicy = SshHostKeyPolicy.GiveUpSecurityAndAcceptAny
        End With

        sessionOptions.AddRawSettings("FSProtocol", "2")

        Using session As New Session
            AddHandler session.FileTransferProgress,
                Sub(sender, e)
                    progressObject.FileProgress = e.FileProgress * 100
                    progressObject.FolderProgress = e.OverallProgress * 100
                    BackgroundWorker.ReportProgress(1, progressObject)
                End Sub
            ' Connect
            session.Open(sessionOptions)

            ' Transfer files
            session.GetFiles("remote directory", "output directory").Check()

        End Using
    End Function

End Class

Please bare in mind that DownloadProgress is a simple class object I made to pass multiple values through the ReturnProgress event.

The above code works in the sense that it performs what I want, it downloads the file AND updates the progress bar, but this is undesirable as my final vision for this project will get very messy very quickly if I have to contain it all inside of one big class.

I want to be able to invoke the progress bar from outside of the class without it giving me the error "Invoke or BeginInvoke cannot be called on a control until the window handle has been created"

If any of you have further questions I'll be more than happy to provide, apologies if this is long winded, I'm not overly experienced with asking questions here, much more of a lurker. Thanks in advance!

EDIT: DownloadProgress is a value storage class for passing between event handlers, it is not the progress bar. The progress bars are "TotalDownloadProgress" and "TotalDownloadFileProgress"

  • I have a feeling the issue is actually in where you are doing Dim progressObject As New DownloadProgress (Assuming this is your progress display). Looks like you are actually creating it in the background thread rather than the UI thread. Supprised you're not getting cross thread exceptions. Anyhow Move that Button1_Click, then if needed clean up via the BackgroundWorker.RunWorkerCompleted() method – Hursey Nov 08 '22 at 20:19
  • DownloadProgress is the class I made for passing between event handlers for the ProgressChanged event, sorry I should've made that more clear, I'll edit the original post to reflect. – Josh Watson Nov 08 '22 at 20:35
  • Ahh... yes, now I see it. Sorry didn't pick that up first go around. That aside though after re-reading a couple times. You could make a class based on a form/panel with your progress bar, give it a public _UpdateProgress(ByVal progress as DownloadProgress)_ method and call that from the BGW changed event, although to me not really a huge savings over well-structured code, perhaps looking at using Regions to organise your code might be worth looking at. – Hursey Nov 08 '22 at 21:57
  • On the topic of things to look at, think you might want to also turn Option Strict On. Likely with throw up a number of complaints needing fixed to start with but will result in much more robust code. – Hursey Nov 08 '22 at 21:59
  • Create an UpdatePogress method in your DownloadProgress class that takes a background worker as parameter. You call it like progress.UpdateProgress(). Inside the method, do whatever you like with your values, then call .ReportProgress(1, Me) – F0r3v3r-A-N00b Nov 09 '22 at 02:35

0 Answers0