-
Oct 28th, 2021, 11:26 AM
#1
Thread Starter
Addicted Member
[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
-
Oct 28th, 2021, 08:00 PM
#2
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...
- Coding Examples:
- Features:
- Online Games:
- Compiled Games:
-
Oct 29th, 2021, 04:34 AM
#3
Thread Starter
Addicted Member
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
-
Oct 29th, 2021, 07:49 AM
#4
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.
-
Oct 29th, 2021, 11:24 AM
#5
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.
-
Oct 29th, 2021, 11:42 AM
#6
Re: Issue managing multi same forms with backgroudWorker
Lets take as example editing cities info using following record of information:
VB.NET Code:
Public Class City
Public Property CityID As Integer
Public Property Name As String
Public Property Mayor As String
Public Property Population As Integer
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.
-
Oct 29th, 2021, 11:43 AM
#7
Re: Issue managing multi same forms with backgroudWorker
MainForm code:
VB.NET Code:
Imports System.Threading
Public Class MainForm
Private _forms As New Dictionary(Of Integer, CityManagementForm) ' Key=CityID, value=CityManagementForm
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitControls()
AddHandler CitiesList.SelectedIndexChanged, AddressOf OnCitySelected
End Sub
Private Sub InitControls()
CitiesList.DisplayMember = "Name"
CitiesList.ValueMember = "CityID"
CitiesList.Items.Clear()
CitiesList.Items.AddRange(
{
New City() With {.CityID = 1, .Name = "Rome"},
New City() With {.CityID = 2, .Name = "London"},
New City() With {.CityID = 3, .Name = "New York"},
New City() With {.CityID = 4, .Name = "Tokyo"}
}
)
End Sub
Private Sub AddToLogInternal(s As String)
LogBox.AppendText(s & vbCrLf)
End Sub
Private Sub AddToLog(s As String)
If LogBox.InvokeRequired() Then
LogBox.Invoke(Sub() AddToLogInternal(s))
Else
AddToLogInternal(s)
End If
End Sub
Private Function GetOrCreateCityManagementForm(city As City) As CityManagementForm
Dim frm As CityManagementForm = Nothing
If _forms.TryGetValue(city.CityID, frm) Then
Return frm
End If
frm = New CityManagementForm(city)
_forms.Add(city.CityID, frm)
AddHandler frm.OnSaveClicked, Sub(c) AddToLog($"Saving {c.Name}")
AddHandler frm.OnLongCityProcess, AddressOf OnLongCityProcess
frm.Show()
Return frm
End Function
Private Sub OnLongCityProcess(city As City)
Dim id = Guid.NewGuid().ToString()
AddToLog($"{id} Starting long process for [{city.Name}]")
Task.Run(
Sub()
Thread.Sleep(5000)
AddToLog($"{id} End of processing")
End Sub)
End Sub
Private Sub OnCitySelected(sender As Object, e As EventArgs)
Dim city = CType(CitiesList.SelectedItem, City)
Dim frm = GetOrCreateCityManagementForm(city)
frm.Focus()
End Sub
End Class
-
Oct 29th, 2021, 11:44 AM
#8
Re: Issue managing multi same forms with backgroudWorker
CityManagementForm code:
VB.NET Code:
Public Class CityManagementForm
Public Event OnLongCityProcess(city As City)
Public Event OnSaveClicked(city As City)
Private _city As City
Public Sub New(city As City)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_city = city
End Sub
Private Sub CityManagementForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitControls()
End Sub
Private Sub InitControls()
CityNameLbl.Text = _city.Name
MayorText.Text = _city.Mayor
PopulationText.Text = _city.Population.ToString()
End Sub
Private Sub ProcessBtn_Click(sender As Object, e As EventArgs) Handles ProcessBtn.Click
RaiseEvent OnLongCityProcess(_city)
End Sub
Private Sub SaveBtn_Click(sender As Object, e As EventArgs) Handles SaveBtn.Click
RaiseEvent OnSaveClicked(_city)
End Sub
End Class
-
Oct 30th, 2021, 04:37 AM
#9
Thread Starter
Addicted Member
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?
-
Oct 30th, 2021, 06:38 AM
#10
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
-
Oct 30th, 2021, 12:40 PM
#11
Thread Starter
Addicted Member
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
-
Oct 31st, 2021, 09:15 AM
#12
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
-
Nov 1st, 2021, 10:43 AM
#13
Thread Starter
Addicted Member
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
-
Nov 2nd, 2021, 06:07 AM
#14
Re: Issue managing multi same forms with backgroudWorker
-
Nov 2nd, 2021, 08:24 PM
#15
Re: Issue managing multi same forms with backgroudWorker
Originally Posted by giodepa
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
-
Nov 12th, 2021, 02:51 AM
#16
Thread Starter
Addicted Member
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|