Results 1 to 16 of 16

Thread: [RESOLVED] Issue managing multi same forms with backgroudWorker

  1. #1

    Thread Starter
    Addicted Member
    Join Date
    Oct 2010
    Posts
    164

    Resolved [RESOLVED] Issue managing multi same forms with backgroudWorker

    Hi all,
    I create two forms: formA and formB.
    First one is a combination of several options where it is possible to select i.e. country,city,State etc.
    Second one contains several kpi (key performance indicator) i.e. Availability or Data Volume where selecting each one it is possible to graph kpi for country or city etc
    Each kpi contains a query towards postgreSQL DB.

    My goal is to open several formB and execute different kpi as follow:
    1)Launch FormA, select i.e. country and launch formB
    2)Select again formA, select city and launch a second formB (formB2)
    Now I opened two same formB but with different settings
    3)When I select kpi from formB in order to graph this kpi for country, formB is working to do it.
    4) ISSUE: I cannot select formB2 in order to select a different or same kpi for city. FormB has to finish before selecting formB2


    I used backgroudWorker as follow:

    Code:
    Private Workers() As BackgroundWorker
        Private myNewForm As formB
        Public Delegate Sub formBDelegate(ByVal MyObject As String) 'MyObject can be country,city,State
        Private NumWorkers = 0
        Dim LaunchformBDelegate As formBDelegate
    	
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            NumWorkers = NumWorkers + 1
            ReDim Workers(NumWorkers)
            Workers(NumWorkers) = New BackgroundWorker
            AddHandler Workers(NumWorkers).DoWork, AddressOf WorkersDoWork
            LaunchformBDelegate = AddressOf ShowformB_KPI
            Workers(NumWorkers).RunWorkerAsync()
    
    End Sub
    
    Private Sub WorkersDoWork(sender As Object, e As DoWorkEventArgs)
    
    ...
     formB.WindowState = FormWindowState.Normal
    Me.WindowState = FormWindowState.Minimized
    Me.Activate()
    Me.Invoke(LaunchformBDelegate, oggetto)
    End Sub
    
    Private Sub ShowformB_KPI()
    
     myNewForm = New formB()
            If myNewForm.InvokeRequired Then
                Me.myNewForm.Invoke(New MethodInvoker(AddressOf ShowformB_KPI))
            Else
    		
    		myNewForm(NumWorkers).object = MyObject
    		 myNewForm.Show()
    		 ' myNewForm.ShowDialog() not working as I cannot select formA
    End Sub
    Some help?
    Thank in advanced
    gio

  2. #2
    eXtreme Programmer .paul.'s Avatar
    Join Date
    May 2007
    Location
    Chelmsford UK
    Posts
    25,466

    Re: Issue managing multi same forms with backgroudWorker

    So the only issue is that you want to use formA and formB side by side? ShowDialog shows your form as a modal dialog. The Show method shows a non-modal form. This applies to any modal form. You can't access the other forms until the Dialog is closed...

  3. #3

    Thread Starter
    Addicted Member
    Join Date
    Oct 2010
    Posts
    164

    Re: Issue managing multi same forms with backgroudWorker

    Hi Paul,
    thanks for you reply.
    I want to work with two formB side by side.
    But I have to give you some informations on how formB works to understand better...
    in FormB there is a treview where inside there are several treenodes like (KPI1, KPI2,KPI3, etc)
    When I click on treenode i.e. KPI1 Sub TreeView.afterSelect is triggered
    After collecting data (i.e. get query to execute) a function MakeLabelNodo is called
    MakeLabelNodo creates a label with the name of KPI on a Panel1.
    When control LAbel is added to to Panel1 Sub Panel1.ControlAdded is triggered.
    This Sub connects to DB; executes query, fetching data and then graphs the behaviour of KPI

    Now, before clicking on KPI(treenode) I can swith between formA and formB easily and I can open several formB without problem.
    Issue is now some different.
    Consider two formB opened: formB1 and formB2
    When I click on KPI1 of formB1, all procedures described up start and all process lasts 30 seconds.
    During this time, formB2 seems to freeze.. Seems as I can move fromB2 but any controls (Treeview, etc) of formB2 are freezed
    and clicking on formB2 I see "formB2 is not responding" tipical when a form is busy.
    Moreover, during these 30sec, I cannot select again formA

    I tried also to create again another backgroundWorker that manages TreeView.afterSelect in formB without success
    I'm little in trouble ....

    Thanks
    gio

  4. #4
    Fanatic Member
    Join Date
    Jun 2019
    Posts
    557

    Re: Issue managing multi same forms with backgroudWorker

    Few things that can help you:
    - learn how to define own events and use them
    - learn about async/await and how to use it properly
    - learn how to separate processing code from user interface and how to control your app outside all WinForms events (button_click, etc.)
    - try to use meaningful names of variables and controls even in some sample code as no one is interested to guess what Button83 means and should do

    So with above knowledge you can:
    - define code that will control what happens with your app: tell to forms to view what is required and receive events from your UI when user interacts with elements
    - define events in your UI to be what your app will do and assign meaningful name instead of re-firing button/treenode/whatever click events to upper level (your controlling code)
    - use async code for long running tasks when it is required
    - learn that you can use async sub only to handle WinForms event subs (button clicks event is sub /void in C#/)
    - learn that all other async code should be function and return Task (for standard subs) or Task(Of TResult) for functions that return TResult

    Controller will create main form (formA in your case) and hook some events (addhandler frm.CustomEvent, addressof CustomEventHandler) you defined. Perform load of data (using separate data management class) and initialize main form.

    When user clicks in your main form (formA) and you have to show something if formA, open new formB with the new tree or update info in existing instance of formB - you fire (invoke) your own event, e.g. OnCitySelected. This may happen when user clicks inside list view to select another item (formA.ListView.Click event) or Button18_Click() - your controller is not interested how this happened. It hooked the event OnCitySelected which can also send some params as it is processed as normal sub, e.g. OnCitySelected (cityID as integer).

    At this moment your controller, that already hooked (addhandler mainfrm.OnCitySelected, addressof OnCitySelectedHandler) will be notified that new city was selected and the handler will get the city ID. It can check if there is already instance of formB shown for that city (you have to keep track of opened formB instances, e.g. in dictionary object) and call to update info or it has to show new instance of formB.

    When new instance of formB will be shown you create the form object, hook formB specific events and then show the form:
    Code:
    dim frm = new FormB
    addhandler frm.OnKPISubItemSelected, addressof OnKPISubItemSelectedHandler
    addhandler frm.OnKPISaved, addressof OnKPISavedHandler
    addhandler frm.OnKPIStatusChanged, addressof OnKPIStatusChangedHandler
    ...
    ...
    frm.Show()
    Since you may have multiple instances of formB you have to pass (as parameter) some info that will identify which is the instance of the form so the event handlers (OnKPI***) will know how to proceed. You can pass also other parameters meanwhile - it is a matter of the whole app design.

    async/await is another story but first you need better design of your app code.

  5. #5
    Fanatic Member
    Join Date
    Jun 2019
    Posts
    557

    Re: Issue managing multi same forms with backgroudWorker

    There are some tricky parts if you mix event based design and async code.

    Since events are synchronously executed in .NET, you have to create tasks with your own code, e.g. using Task.Run(Sub() ...).

    Another tricky part is when you run async routines (await-ing them) or run tasks that usually run in separate thread, you can't directly update the UI from these tasks. You have to use Control.InvokeRequired() to find out if the UI update code is running in the UI thread or not and call Control.Invoke() in that case.

  6. #6
    Fanatic Member
    Join Date
    Jun 2019
    Posts
    557

    Re: Issue managing multi same forms with backgroudWorker

    Lets take as example editing cities info using following record of information:
    VB.NET Code:
    1. Public Class City
    2.     Public Property CityID As Integer
    3.     Public Property Name As String
    4.     Public Property Mayor As String
    5.     Public Property Population As Integer
    6. End Class

    There are two forms - MainForm and CityManagementForm. MainForm contains list box with cities. When user clicks on city name new form (CityManagementForm) to edit the info for the specified city is shown. If the city was already shown the app will just put the focus on that form so no duplicate city info edit forms will be shown.

    Main form will contain multiline textbox where some log information will be shown instead of performing some real operations - this is just example app.

    City management form will contain two text boxes to edit mayor name and population - this is just example info editing. Two buttons - Process and Save will be used to perform the long processing and saving the info. But we want to move the processing and saving to controller (in this case MainForm will serve the purpose of controlling code to simplify the example).

    When Process is saved in the main form will be shown unique GUID for the operation when it starts, wait for 5 seconds (this is simulation of some long processing) and shown another message when the process completes. This way the user can open multiple cities to edit and press Process button quickly on each form and the log will show starting and end processing messages for different cities with their unique start processing GUIDs. The user also can press on same form multiple times the Process button to start processing for same city but all processing will have different unique GUID.

  7. #7
    Fanatic Member
    Join Date
    Jun 2019
    Posts
    557

    Re: Issue managing multi same forms with backgroudWorker

    MainForm code:

    VB.NET Code:
    1. Imports System.Threading
    2.  
    3. Public Class MainForm
    4.     Private _forms As New Dictionary(Of Integer, CityManagementForm)    ' Key=CityID, value=CityManagementForm
    5.  
    6.     Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    7.         InitControls()
    8.  
    9.         AddHandler CitiesList.SelectedIndexChanged, AddressOf OnCitySelected
    10.     End Sub
    11.  
    12.     Private Sub InitControls()
    13.         CitiesList.DisplayMember = "Name"
    14.         CitiesList.ValueMember = "CityID"
    15.         CitiesList.Items.Clear()
    16.         CitiesList.Items.AddRange(
    17.             {
    18.                 New City() With {.CityID = 1, .Name = "Rome"},
    19.                 New City() With {.CityID = 2, .Name = "London"},
    20.                 New City() With {.CityID = 3, .Name = "New York"},
    21.                 New City() With {.CityID = 4, .Name = "Tokyo"}
    22.             }
    23.         )
    24.     End Sub
    25.  
    26.     Private Sub AddToLogInternal(s As String)
    27.         LogBox.AppendText(s & vbCrLf)
    28.     End Sub
    29.     Private Sub AddToLog(s As String)
    30.         If LogBox.InvokeRequired() Then
    31.             LogBox.Invoke(Sub() AddToLogInternal(s))
    32.         Else
    33.             AddToLogInternal(s)
    34.         End If
    35.     End Sub
    36.  
    37.  
    38.     Private Function GetOrCreateCityManagementForm(city As City) As CityManagementForm
    39.         Dim frm As CityManagementForm = Nothing
    40.  
    41.         If _forms.TryGetValue(city.CityID, frm) Then
    42.             Return frm
    43.         End If
    44.  
    45.         frm = New CityManagementForm(city)
    46.         _forms.Add(city.CityID, frm)
    47.  
    48.         AddHandler frm.OnSaveClicked, Sub(c) AddToLog($"Saving {c.Name}")
    49.         AddHandler frm.OnLongCityProcess, AddressOf OnLongCityProcess
    50.         frm.Show()
    51.         Return frm
    52.     End Function
    53.  
    54.     Private Sub OnLongCityProcess(city As City)
    55.         Dim id = Guid.NewGuid().ToString()
    56.         AddToLog($"{id} Starting long process for [{city.Name}]")
    57.         Task.Run(
    58.             Sub()
    59.                 Thread.Sleep(5000)
    60.                 AddToLog($"{id} End of processing")
    61.             End Sub)
    62.  
    63.     End Sub
    64.  
    65.     Private Sub OnCitySelected(sender As Object, e As EventArgs)
    66.         Dim city = CType(CitiesList.SelectedItem, City)
    67.         Dim frm = GetOrCreateCityManagementForm(city)
    68.         frm.Focus()
    69.     End Sub
    70. End Class

  8. #8
    Fanatic Member
    Join Date
    Jun 2019
    Posts
    557

    Re: Issue managing multi same forms with backgroudWorker

    CityManagementForm code:
    VB.NET Code:
    1. Public Class CityManagementForm
    2.     Public Event OnLongCityProcess(city As City)
    3.     Public Event OnSaveClicked(city As City)
    4.  
    5.     Private _city As City
    6.  
    7.     Public Sub New(city As City)
    8.         ' This call is required by the designer.
    9.         InitializeComponent()
    10.  
    11.         ' Add any initialization after the InitializeComponent() call.
    12.         _city = city
    13.     End Sub
    14.  
    15.     Private Sub CityManagementForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    16.         InitControls()
    17.     End Sub
    18.  
    19.     Private Sub InitControls()
    20.         CityNameLbl.Text = _city.Name
    21.         MayorText.Text = _city.Mayor
    22.         PopulationText.Text = _city.Population.ToString()
    23.     End Sub
    24.  
    25.     Private Sub ProcessBtn_Click(sender As Object, e As EventArgs) Handles ProcessBtn.Click
    26.         RaiseEvent OnLongCityProcess(_city)
    27.     End Sub
    28.  
    29.     Private Sub SaveBtn_Click(sender As Object, e As EventArgs) Handles SaveBtn.Click
    30.         RaiseEvent OnSaveClicked(_city)
    31.     End Sub
    32. End Class

  9. #9

    Thread Starter
    Addicted Member
    Join Date
    Oct 2010
    Posts
    164

    Re: Issue managing multi same forms with backgroudWorker

    Hi Peterst,
    first THANKS!!!
    You are perfectly right and now I'm studying your code
    But before reading it I understood a little more what you said in your first answer and so I created
    a simple app where I have two forms: form1 and form2
    in Form1 I create a TreeView(TreeView1) where therea are 8 nodes (number doesn't matter) and I associated to this TreeView a ContextMenuStrip (ContextMenuStrip1)
    In ContextMenuStrip1 I call form2; this is code:
    Code:
    Imports System.ComponentModel
    
    Public Class Form1
        Private Worker As BackgroundWorker
        Private myNewForm As Form2
        Public Delegate Sub Form2Delegate()
        Private NumWorkers = 0
        Dim LaunchForm2Delegate As Form2Delegate
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
           TreeView1.ContextMenuStrip = ContextMenuStrip1
        End Sub
    
        Private Sub LaunchForm2(sender As Object, e As EventArgs) Handles LauncheForm2ToolStripMenuItem.Click
            Worker = New BackgroundWorker
            AddHandler Worker.DoWork, AddressOf WorkersDoWork
            LaunchForm2Delegate = AddressOf ShowForm2
            Worker.RunWorkerAsync()
        End Sub
    
        Public Sub WorkersDoWork()
            myNewForm = New Form2
            Me.Invoke(LaunchForm2Delegate)
        End Sub
    
        Public Sub ShowForm2()
            myNewForm.Show()
        End Sub
    End Class
    In form2 I created a simple button1 where inside is present a infinite for cycle
    This is code:
    Code:
    Imports System.ComponentModel
    Public Class Form2
        Dim NumWorkers As Integer
        Dim Worker As BackgroundWorker
        Public Delegate Sub UpdateLabelDelegate(ByVal value As String)
        Dim LaunchUpdateLabelDelegate As UpdateLabelDelegate
    
        Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            LaunchUpdateLabelDelegate = AddressOf UpdateLabel
        End Sub
    
    
        Public Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Button1.Enabled = False
            Worker = New BackgroundWorker
            AddHandler Worker.DoWork, AddressOf WorkerDoWork
            Worker.RunWorkerAsync()
        End Sub
    
    
        Public Sub WorkerDoWork()
    
            For i = 0 To 1111111111111111
                Me.Invoke(LaunchUpdateLabelDelegate, i.ToString)
            Next
        End Sub
    
    
        Public Sub UpdateLabel(value)
            Label1.Text = value
            Label1.Refresh()
        End Sub
    End Class
    Behaviour is as expected but not at all:
    1) Open form2

    Attachment 182815

    2) Click button1 and cycle for starts

    Attachment 182816

    3)Select again form1 and I can select each item in treeview and I can move form1 and form2 too.

    Attachment 182817

    4) Open a second Form2
    5) I can move form1 and I can move form2 and second form2 too.
    Bur now I have ISSUE
    6) Click again on button1 of second form2 and ALL is freezed!!!

    Attachment 182818

    Where am I doing wrong?

  10. #10
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,684

    Re: Issue managing multi same forms with backgroudWorker

    This is just down right wrong

    Code:
    Public Sub WorkerDoWork()
    
    	For i = 0 To 1111111111111111
    		Me.Invoke(LaunchUpdateLabelDelegate, i.ToString)
    	Next
    End Sub
    I would recommend learning how to use the pattern below which can be cancelled. In this case DoWork does nothing other than print the current time to the console every ten seconds. The form remains responsive as would with a BGW. A BackgroundWorker has been the go-to way for asynchronous operations which when used properly is fine in many cases but what you have using a magic number with no way to escape is asking for trouble.

    Requires the following NuGet package System.Threading.Tasks.Dataflow

    Code:
    Imports System.Threading
    Imports System.Threading.Tasks.Dataflow
    
    Public Class Form1
    
        Dim tokenSource As CancellationTokenSource
        Dim actionBlockTask As ActionBlock(Of DateTimeOffset)
        Private Sub StartWork()
    
            tokenSource = New CancellationTokenSource()
    
            actionBlockTask = TryCast(CreateNeverEndingTask(Function(now) _
                DoWork(), tokenSource.Token), ActionBlock(Of DateTimeOffset))
    
            ' Start the task.  Post the time.
            actionBlockTask.Post(DateTimeOffset.Now)
    
        End Sub
        Private Sub StopWork()
    
            Using tokenSource
                tokenSource.Cancel()
            End Using
    
            tokenSource = Nothing
            actionBlockTask = Nothing
    
        End Sub
    
        Private Function DoWork() As Boolean
            Console.WriteLine(Now.ToString("hh:mm:ss"))
            Return True ' TODO
        End Function
    
        Private Function CreateNeverEndingTask(
               action As Action(Of DateTimeOffset),
               cancellationToken As CancellationToken) As ITargetBlock(Of DateTimeOffset)
    
            ' Validate parameters.
            If action Is Nothing Then
                Throw New ArgumentNullException("action")
            End If
    
            Dim block As ActionBlock(Of DateTimeOffset) = Nothing
    
            ' Async so you can wait easily when the delay comes.
            block = New ActionBlock(Of DateTimeOffset)(
                Async Function(item)
                    action(item)
                    Await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).ConfigureAwait(False)
                    block.Post(DateTimeOffset.Now)
                End Function, New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationToken})
    
            Return block
    
        End Function
    
        Private Sub StartButton_Click(sender As Object, e As EventArgs) Handles StartButton.Click
            StartWork()
        End Sub
    
        Private Sub StopButton_Click(sender As Object, e As EventArgs) Handles StopButton.Click
    
            If tokenSource.IsCancellationRequested Then
                tokenSource = New CancellationTokenSource()
            End If
    
            Try
                StopWork()
            Catch ex As Exception
                MessageBox.Show(ex.Message)
            End Try
    
        End Sub
    End Class

  11. #11

    Thread Starter
    Addicted Member
    Join Date
    Oct 2010
    Posts
    164

    Re: Issue managing multi same forms with backgroudWorker

    Thanks Karen,
    I will study your code but it some difficult to understand immediately
    But I'm more and more confused
    I'm just wondering what is the best way to open several forms (starting from a main form) where each one is independent from the others and where each one does some specific operations and then it stops automatically after xx seconds without any operation of cancellation.
    Considering my code, how could you solve it?
    thanks
    gio

  12. #12
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,684

    Re: Issue managing multi same forms with backgroudWorker

    without any operation of cancellation
    Not sure what that means

    If you are not interested in an infinite logic then consider a System.Threading.Timer which accepts a TimerCallback Delegate

  13. #13

    Thread Starter
    Addicted Member
    Join Date
    Oct 2010
    Posts
    164

    Re: Issue managing multi same forms with backgroudWorker

    Hi Karen,
    I meant without button to stop process but I understood what you mean.
    But sorry for further question...
    You said that this code is wrong as there is no way to exit from BGW:

    Code:
    Public Sub WorkerDoWork()
    
    	For i = 0 To 1111111111111111
    		Me.Invoke(LaunchUpdateLabelDelegate, i.ToString)
    	Next
    End Sub
    but if I use:

    Code:
    Public Sub WorkerDoWork()
    Threading.Thread.Sleep(500000)
    End Sub
    I can open many forms as I want

    I cannot understand why...
    I mean Sleep Thread should freeze form as well as For cycle but it isn't so.

    Thanks
    gio

  14. #14
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,684

    Re: Issue managing multi same forms with backgroudWorker


  15. #15
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: Issue managing multi same forms with backgroudWorker

    Quote Originally Posted by giodepa View Post
    Hi Karen,
    I meant without button to stop process but I understood what you mean.
    But sorry for further question...
    You said that this code is wrong as there is no way to exit from BGW:

    Code:
    Public Sub WorkerDoWork()
    
    	For i = 0 To 1111111111111111
    		Me.Invoke(LaunchUpdateLabelDelegate, i.ToString)
    	Next
    End Sub
    but if I use:

    Code:
    Public Sub WorkerDoWork()
    Threading.Thread.Sleep(500000)
    End Sub
    I can open many forms as I want

    I cannot understand why...
    I mean Sleep Thread should freeze form as well as For cycle but it isn't so.

    Thanks
    gio
    In the first case you use Me.Invoke, which will Invoke the GUI thread to do something, so that keeps the GUI thread busy doing things from the background worker, so you're tying both threads together, the background having to wait on the GUI, and the GUI being kept busy with processing from the background worker.

    In the second case, the worker thread is stopped, but the GUI thread is free to do whatever it wants. Sleeping a background thread won't freeze the GUI, likewise processing things on the background thread shouldn't prevent the GUI thread from working in parallel so won't freeze the form either. It is your use of continually Invoking the GUI thread that keeps the GUI thread from doing most of its normal processing.
    "Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930

  16. #16

    Thread Starter
    Addicted Member
    Join Date
    Oct 2010
    Posts
    164

    Re: [RESOLVED] Issue managing multi same forms with backgroudWorker

    Yhanks All!!!
    I'll close thread as Resolved.

    Bye
    Gio

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width