I'm using VB.NET winforms and have a particular form that take a while to load up so I've decided to implement a loading screen to make it feel a bit less freezey. Here is the code I'm using in my freezey form load.
Private Sub HanleyView_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim loaderForm As New Loader
loaderForm.Show()
AllOpenOrdersList.FullRowSelect = True
NeedsAttentionList.FullRowSelect = True
StockManagementList.FullRowSelect = True
Dim lowStockCount = HelperMethods.ReviewLowStock()
ReviewLowStockButton.Text = "Review Low Stock (" & lowStockCount & ")"
RefreshAllOpenOrdersList()
RefreshNeedsAttentionList()
RefreshStockManagementList()
loaderForm.Close()
End Sub
So I start by showing the loading form and finish by closing it.
The good new is that the loading form appears, but the bad news is the "LOADING..." text which is a label on my loading form doesn't show, I just get a white patch there instead. I've tried two approaches, the above and calling Loader.Show and Loader.Close. I've also tried setting loaderForm.Label1.Text = "LOADING..." but this didn't make any difference. Each time the form loads (and the title loads which says "Loading please wait") but not the label on the form itself.
I've now also tried:
Dim loaderForm As New Loader
Dim lbl As New Label
loaderForm.Controls.Add(lbl)
lbl.Text = "LOADING..."
lbl.Location = New System.Drawing.Point(42, 21)
loaderForm.Show()
AllOpenOrdersList.FullRowSelect = True
NeedsAttentionList.FullRowSelect = True
StockManagementList.FullRowSelect = True
Dim lowStockCount = HelperMethods.ReviewLowStock()
ReviewLowStockButton.Text = "Review Low Stock (" & lowStockCount & ")"
RefreshAllOpenOrdersList()
RefreshNeedsAttentionList()
RefreshStockManagementList()
loaderForm.Close()
But this hasn't worked either.
EDIT: I've tried Varocarbas' code but was still unsuccessful. The form loads but the text remains a white patch
Dim loaderForm As Form = New Form
With loaderForm
.Height = 200
.Width = 300
.Location = New System.Drawing.Point(12, 12)
End With
Dim label1 As Label = New Label
loaderForm.Controls.Add(label1)
With label1
.Text = "LOADING..."
.Location = New System.Drawing.Point(12, 45)
End With
loaderForm.Show()
EDIT 2: For clarity, here is my code now it is working using Franck suggestion
Private Sub HanleyView_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim loaderForm As New Loader
loaderForm.Show()
Application.DoEvents()
AllOpenOrdersList.FullRowSelect = True
NeedsAttentionList.FullRowSelect = True
StockManagementList.FullRowSelect = True
Dim lowStockCount = HelperMethods.ReviewLowStock()
ReviewLowStockButton.Text = "Review Low Stock (" & lowStockCount & ")"
RefreshAllOpenOrdersList()
RefreshNeedsAttentionList()
RefreshStockManagementList()
loaderForm.Close()
End Sub
I have kept my original code and simply added Application.DoEvents() below loaderform.Show and it works properly now.
Also the below screenshot is what I mean by using the designer (and not doing it programmtically):
Multithreading!
Create and run the loaderForm on a separate thread. But then you need to be careful about cross-thread operations, so have a self-invoking method on your loaderForm, such as:
Public Sub ParseStatus(msg as String)
If Me.InvokeRequired Then Me.Invoke(New Action(Of String)(AddressOf Me.ParseStatus), msg) Else Me.Label1.Text = msg
End Sub
Also in your loaderForm you want something like:
Public Sub Finish()
Me.DialogResult = Windows.Forms.DialogResult.OK
Me.Close()
End Sub
Then in the Load procedure of your form:
Private Sub HanleyView_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim loaderForm As New Loader
Dim loaderThread As New Threading.Thread(New Threading.ThreadStart(AddressOf loaderForm.ShowDialog))
loaderThread.Start()
loaderForm.ParseStatus("Loading ...")
AllOpenOrdersList.FullRowSelect = True
NeedsAttentionList.FullRowSelect = True
StockManagementList.FullRowSelect = True
Dim lowStockCount = HelperMethods.ReviewLowStock()
ReviewLowStockButton.Text = "Review Low Stock (" & lowStockCount & ")"
LoaderForm.ParseStatus("Refreshing open orders ...")
RefreshAllOpenOrdersList()
loaderForm.ParseStatus("Refreshing needs attentions?") ' etc
RefreshNeedsAttentionList()
RefreshStockManagementList()
loaderForm.Finish()
End Sub
But Visual Studio has a neat thing called SplashScreen in its project templates for VB.NET. I'd use that one, if you aren't already…
Edit: I corrected the syntax errors in the code.
Related
VB.NET Framework 4.7.2
Visual Studio Community 2019 V16.11.2
I have a WinForms application and, for the most part, I start with a form to display the loading of datatables. This is handled in the Application Events module:
Private Sub MyApplication_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
If String.IsNullOrEmpty(My.Settings.TheCompany) Then
MainForm = FormConnection
Else
MainForm = FormDataLoad
End If
End Sub
When the datatables are all loaded the FormDataLoad has a close button which is enabled by the procedure which loads all the datatables:
With FormDataLoad
.ButtonClose.Visible = True
End With
When the button is pressed it calls another form which displays various charts etc based on the datatables which have been loaded:
Private Sub ButtonClose_Click(sender As Object, e As EventArgs) Handles ButtonClose.Click
Me.Close()
End Sub
Private Sub FormDataLoad_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
With FormOverview
.Show()
End With
End Sub
When FormOverview loads, its labels, charts etc are all initialised and drawn BUT, mysteriously, the form closes and the application ends. The Application Shutdown event is raised:
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
Private Sub MyApplication_Shutdown(sender As Object, e As EventArgs) Handles Me.Shutdown
MessageBox.Show("Closing the application for some unknown reason")
End Sub
When this message box is displayed it means that the application has closed but not as a result of any unhandled exception. Nowhere in the project is there any code to call FormOverview.close so I am at a loss to understand what is happening.
The form load code is here:
Private Sub FormOverview_Load(sender As Object, e As EventArgs) Handles Me.Load
With Me
'Set the form caption and back colour and font
.Text = "Retail Management Hero Data Visualisation Overview"
.BackColor = Color.GhostWhite
.Font = U.BasicFontSmall
'Add event handler for the DAL
AddHandler D.ConnectionOpened, AddressOf Connected
'Form level variables for the KPJs
Dim ITO As String = ""
Dim ATV As String = ""
Dim CRR As String = ""
Dim GMROI As String = ""
Dim OOS As String = ""
Dim StocksSales As String = ""
With KPIData.Rows(0)
Me.Text = My.Settings.TheCompany & " " & Me.Text
ITO = .Item("ITO").ToString
CRR = .Item("CRR").ToString
ATV = "€" & .Item("ATV").ToString
GMROI = "€" & .Item("GMROI").ToString
OOS = .Item("OOSRatio").ToString
StocksSales = .Item("StocksToSales").ToString
End With
For Each CTL As Control In .Controls
If TypeOf CTL Is Label Then
With CTL
If .Name.Contains("Value") Then
.Font = U.BasicFontSuperLarge
.BackColor = Color.Yellow
Else
.BackColor = U.Spurs
.ForeColor = Color.GhostWhite
End If
End With
End If
Next
With .LabelReportDate
.Text = "Reports As Of " & ReportDate
End With
With .LabelATVValue
.Text = U.DecimalToSuperSubFormat(ATV, True)
End With
With .LabelGMROIValue
.Text = U.DecimalToSuperSubFormat(GMROI, True)
End With
With .LabelCRRValue
.Text = U.DecimalToSuperSubFormat(CRR, True) & "%"
End With
With .LabelITOValue
.Text = U.DecimalToSuperSubFormat(ITO, True)
End With
With .LabelOOSValue
.Text = U.DecimalToSuperSubFormat(OOS, True) & "%"
End With
With .LabelStocksSalesValue
.Text = U.DecimalToSuperSubFormat(StocksSales, True) & "%"
End With
'Extract the names of the X and Y axes
Dim Xname As String = ""
Dim Yname As String = ""
With WineData
Xname = .Columns("Department").ColumnName
Yname = .Columns("Day").ColumnName
End With
'Draw the charts
DrawColumnChart(WineData, .ChartWine, "Wine sales", Xname, Yname, 250, False)
DrawColumnChart(OtherData, .ChartNonWine, "Other sales", Xname, Yname, 25, False)
'Centre on the screen
.CenterToScreen()
End With
End Sub
So, what should happen is that this form displays centre screen with charts etc all displayed correctly.
If anyone has any ideas I would be most grateful.
Dermot
I found out what was causing the problem.
In the Project's properties, in the Application tab, there Combo selector, Shutdown mode.
The option I had selected was When startup form closes - by changing this to When last form closes the issue went away.
Like many problems, when you look at it too long you can't see the woods for the trees.
A co-worker made some improvements to my app, but at the same time, he changed the whole app to use MDI child forms. So where I used to be able to use ShowDialog() to make the subroutine wait until the new form closed to continue, now I can't. I adapted some old VB6 code to try and replicate the same functionality, but it uses DoEvents() and I've been told to avoid that at all costs.
At this point I can't help but feel like there's just a fundamental flaw in my methodology here, and that there is probably a far simpler way to accomplish the same task.
Private Sub Label_EditNode_Click(sender As Object, e As EventArgs) Handles Label_EditNode.Click
Dim Frm As New frmVariableEditor With {
.Tag = "FrmVariableEditor",
.MdiParent = FrmMDIMain,
.Dock = DockStyle.Fill,
.Location = Me.Location,
.Size = Me.Size,
.myFieldName = "NodeClick",
.myResultText = txbNodeClick.Text
}
Frm.Show()
Call WaitOnFormUnload()
txbNodeClick.Text = Frm.myResultText
End Sub
Public Sub WaitOnFormUnload()
Dim bIsLoaded As Boolean = True
Dim FormName As String = Me.MdiParent.ActiveMdiChild.Name ' Form.ActiveForm.Name
Do While bIsLoaded
bIsLoaded = False
For Each f As Form In Application.OpenForms
If f.Name = FormName Then
bIsLoaded = True
Application.DoEvents()
Exit For ' breaks the for loop
End If
Next f
Loop
End Sub
Goes a something like this:
Private Sub Label_EditNode_Click(sender As Object, e As EventArgs) Handles Label_EditNode.Click
Dim Frm As New frmVariableEditor With {
.Tag = "FrmVariableEditor",
.MdiParent = FrmMDIMain,
.Dock = DockStyle.Fill,
.Location = Me.Location,
.Size = Me.Size,
.myFieldName = "NodeClick",
.myResultText = txbNodeClick.Text
}
AddHandler Frm.FormClosing, AddressOf RetrieveBlahData
Frm.Show()
End Sub
Private Sub RetrieveBlahData(sender as Object, e as FormClosingEventArgs)
txbNodeClick.Text = DirectCast(sender, frmVariableEditor).myResultText
End Sub
Probably doesn't get more minimal than that, except for maybe sharing a single data object:
Dim Frm As New frmVariableEditor With {
.Tag = "FrmVariableEditor",
.MdiParent = FrmMDIMain,
.Dock = DockStyle.Fill,
.Location = Me.Location,
.Size = Me.Size,
.myFieldName = "NodeClick",
.DataSetToBindTo = Me.DataSetWhatever
}
You'll have to re-code your form so that it takes this passed in dataset (or other data container) and gets its data from it (databinding) .. then as the editor form is changing the data, it's actually changing the original dataset in the main form too, because they're the same thing
In an attempt to fix my previous problem, i tried to move Form2's(contacts) load event codes to form1's background worker...The code is :
Private Sub Contact_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Con.open
dim cmd as new sqlcommand("Select * from Users",con)
Dim adapter As New SqlDataAdapter(cmd)
Dim table As New DataTable
adapter.Fill(table)
datagrid2.DataSource = table
con.Close()
datagrid2.Columns(0).Frozen = True
datagrid2.Columns(0).Width = 48
datagrid2.Columns(1).Width = 33
AddSelectAllCheckBox(datagrid2)
Dim img As DataGridViewImageColumn
img = datagrid2.Columns(1)
img.ImageLayout = DataGridViewImageCellLayout.Stretch
ExtensionMethods2.DoubleBuffered(datagrid2, True)
ExtensionMethods1.DoubleBuffered(Panel3, True)
ExtensionMethods1.DoubleBuffered(Panel9, True)
mymy1.DoubleBuffered(inpeople, True)
If datagrid2.Rows.Count <= 0 Then
added.Visible = True
Else
added.Visible = False
End If
Try
MetroComboBox2.Items.AddRange(File.ReadAllLines(Application.StartupPath + "\ct_list.ofptx"))
moneylist.Items.AddRange(File.ReadAllLines(Application.StartupPath + "\cr_list.ofptx"))
ttlist.Items.AddRange(File.ReadAllLines(Application.StartupPath + "\tt_list.ofptx"))
Catch ex As Exception
MsgBox("One or more files required by OffPo is missing/corrupted" & vbCrLf & "" & vbCrLf & "Error occured in : ct_list.ofptx or cr_list.ofptx")
End Try
entrylabel.Text = "There are/is " & datagrid2.Rows.Count & " contact entries"
svbtn.Enabled = False
upbtn.Enabled = False
dtb.Enabled = False
End Sub
To fix my previous problem(to fix the BACKGROUND WORKER FREEZING UI issue), i moved this code to the form1's BackGroundWorker's DoWork event. The code for RunCompleted event looks like this :
Private Sub BackgroundWorker2_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker2.RunWorkerCompleted
Contact.Show()
waiting.Hide()
End Sub
The problem is, when BackGroundWorker shows the Form2(contacts), the datagridview becomes empty.It only loads the column names but no rows/data...What am i missing ?? Why isn't the dgvw populating ?
I didn't add the BackGroundWorker Do_work event's code as it is the same as Form2's load event code
I have a Winforms Application which should notify the user when something in a database changes. For that i use Sql-Dependencys, which works fine.
When the Dependency fires i am able to show a form with some Buttons so the user can decide what he wants to do.
After one Button i want to show a Dialog, but the first Dialog always gets closed instantly.
The only fix i found until now is restoring the base form and activating it, but that is not the solution i am looking for.
For the code i am doing the Following:
This Method gets called when something changes in the Database
Private Sub NutzerBenachrichtigen(Aenderung As Aenderung)
If InvokeRequired Then
Me.BeginInvoke(New MethodInvoker(Sub()
ErzeugeBenachrichtigung(Aenderung)
End Sub))
Else
ErzeugeBenachrichtigung(Aenderung)
End If
End Sub
This Method Displays the first form (HeadsUp is taken from this here :https://github.com/glm9637/MaterialWinforms/blob/master/MaterialWinforms/Controls/HeadsUp.cs)
Private Sub ErzeugeBenachrichtigung(ByVal Aenderung As Aenderung)
If Aenderung.istAktuellerBenutzer Then
Dim objHeadsUp As New HeadsUp()
objHeadsUp.Titel = "Neue Aenderung"
If Aenderung.EventTyp.ToLower = "alter" Then
objHeadsUp.Text = String.Format("Du hast etwas an {0} {1} {2} geändert. {3}Willst du etwas dazu schreiben?", _
If(Aenderung.BetroffenesObjekt.EntitaetTyp.Name = New EntitaetTyp.Trigger().Name, "dem", "der"), _
Aenderung.BetroffenesObjekt.EntitaetTyp.Name, Aenderung.BetroffenesObjekt.Name, vbNewLine)
Else
objHeadsUp.Text = String.Format("Du hast {0} {1} {2} erstellt. {3}Willst du etwas dazu schreiben?", _
If(Aenderung.BetroffenesObjekt.EntitaetTyp.Name = New EntitaetTyp.Trigger().Name, "den", "die"), _
Aenderung.BetroffenesObjekt.EntitaetTyp.Name, Aenderung.BetroffenesObjekt.Name, vbNewLine)
End If
objHeadsUp.Tag = Aenderung.BetroffenesObjekt
Dim objButtonSchliessen = New MaterialFlatButton
objButtonSchliessen.Tag = objHeadsUp
objButtonSchliessen.Text = "Schliessen"
AddHandler objButtonSchliessen.Click, AddressOf SchliesseHeadsUp
objHeadsUp.Buttons.Add(objButtonSchliessen)
Dim objButtonHistorie = New MaterialFlatButton
objButtonHistorie.Tag = objHeadsUp
objButtonHistorie.Text = "Historieneintrag"
AddHandler objButtonHistorie.Click, AddressOf HistorienEintragHinzufuegen
objHeadsUp.Buttons.Add(objButtonSchliessen)
Dim objButtonDokumentation = New MaterialFlatButton
objButtonDokumentation.Tag = objHeadsUp
objButtonDokumentation.Text = "Dokumentation"
AddHandler objButtonDokumentation.Click, AddressOf DokumentationBearbeiten
objHeadsUp.Buttons.Add(objButtonSchliessen)
objHeadsUp.Buttons.Add(objButtonHistorie)
objHeadsUp.Buttons.Add(objButtonDokumentation)
objHeadsUp.Show()
ElseIf Aenderung.EventTyp = "CLOSE_MESSAGE" Then
Dim objHeadsUp As New HeadsUp()
objHeadsUp.Titel = "Achtung"
objHeadsUp.Text = "Die Anwendung muss für eine Aktualisierung geschlossen werden."
Dim objButtonSchliessen = New MaterialFlatButton
objButtonSchliessen.Tag = objHeadsUp
objButtonSchliessen.Text = "Anwendung Schliessen"
AddHandler objButtonSchliessen.Click, AddressOf AnwendungSchliessen
objHeadsUp.Buttons.Add(objButtonSchliessen)
objHeadsUp.Show()
Else
If mtc_UebersichtTabControl.SelectedTab.Text = "Aenderung" Then
mAenderungenUebersicht.Aktualisieren()
End If
End If
End Sub
Finally when the "Historieneintrag" Button is pressed this Method gets called:
Private Sub HistorienEintragHinzufuegen(sender As Object, e As EventArgs)
Dim blnNachDialogVerstecken As Boolean = False
Dim objFlatButton As MaterialFlatButton = sender
Dim HeadsUp As HeadsUp = objFlatButton.Tag
Dim Objekt As Entitaet = HeadsUp.Tag
Dim objOldLocation As System.Drawing.Point = Location
HeadsUp.Close()
Dim objDialogContent As New HistorienEintrag()
''Hacky: Ansonsten wird der Dialog sofort geschlossen
If WindowState = FormWindowState.Minimized Or Not Visible Then
Location = New System.Drawing.Point(-Width * 2, -Height - 2)
Me.Show()
blnNachDialogVerstecken = True
End If
Activate()
If MaterialDialog.Show("Neuer Historien Eintrag", objDialogContent, MaterialWinforms.Controls.MaterialDialog.Buttons.OKCancel, MaterialDialog.Icon.Info) = DialogResult.OK Then
Objekt.HistorieSpeichern(objDialogContent.Ergebniss)
End If
If blnNachDialogVerstecken Then
Me.Hide()
Location = objOldLocation
End If
End Sub
In here the first modal Form, so a MessageBox.Show("") or any other form closes Instantly if i don't do the show and activate part.
What am i doing wrong here?
Project > (Project) Properties > Application > Shutdown mode
change from when startup form closes to when last form closes
I am having an issue where I get multiple entries in my ListView for the same item if I run my action more than once.
I am creating a simple network scanner/hostname grabber that will add the items to the listview as they come back alive to my ping test.
When I run it the first time it runs fine and creates one entry as it should.
When I run it subsequent times it creates the item as many times as I have ran the code ex. 3rd time hitting start it creates each entry 3 times when it should just create the entry once.
Here is my go button code:
Private Sub Go_Click(sender As Object, e As EventArgs) Handles Go.Click
Dim verifyIP
ListView1.Items.Clear()
chkDone = 0
verifyIP = ipChk(ipAdd.Text)
If verifyIP = 1 Then
ipAddy = Split(ipAdd.Text, ".")
pingTest1.WorkerReportsProgress = True
pingTest1.WorkerSupportsCancellation = False
AddHandler pingTest1.ProgressChanged, AddressOf pingTest1_ProgressChanged
pingTest1.RunWorkerAsync()
pingTest2.WorkerReportsProgress = True
pingTest2.WorkerSupportsCancellation = False
AddHandler pingTest2.ProgressChanged, AddressOf pingTest2_ProgressChanged
pingTest2.RunWorkerAsync()
pingTest3.WorkerReportsProgress = True
pingTest3.WorkerSupportsCancellation = False
AddHandler pingTest3.ProgressChanged, AddressOf pingTest3_ProgressChanged
pingTest3.RunWorkerAsync()
pingTest4.WorkerReportsProgress = True
pingTest4.WorkerSupportsCancellation = False
AddHandler pingTest4.ProgressChanged, AddressOf pingTest4_ProgressChanged
pingTest4.RunWorkerAsync()
While chkDone < 4
wait(25)
End While
Else
MsgBox("IP Invalid")
End If
MsgBox("Done")
End Sub
Here is the code from one of the background workers I am using:
Private WithEvents pingTest1 As BackgroundWorker = New BackgroundWorker
Private Sub pingTest1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles pingTest1.DoWork
Try
Dim hostCheck
pingResult1 = 0
pingTestDone1 = 0
tryIP1 = ipAddy(0) & "." & ipAddy(1) & "." & ipAddy(2) & ".1"
If My.Computer.Network.Ping(tryIP1) = True Then
'Dim pingsender As New Net.NetworkInformation.Ping
'If pingsender.Send(tryIP).Status = Net.NetworkInformation.IPStatus.Success Then
Try
'Dim host As System.Net.IPHostEntry
hostCheck = ""
'host = System.Net.Dns.GetHostByAddress(tryIP3)
'MsgBox(host.HostName)
'host3 = host.HostName
'hostCheck = System.Net.Dns.GetHostEntry(tryIP3).HostName
hostCheck = System.Net.Dns.GetHostByAddress(tryIP1)
'get the hostname property
hostCheck = hostCheck.HostName
pingTest1.ReportProgress("1", hostCheck)
Catch f As Exception
'MsgBox("Error: " & f.Message)
pingTest1.ReportProgress("1", "No Hostname Found")
End Try
Else
pingResult1 = 2
End If
Catch d As Exception
MsgBox("There was an error trying to ping the IP Address: " & d.Message)
End Try
End Sub
Private Sub pingTest1_ProgressChanged(e.ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
MsgBox("Hey")
Dim str(2) As String
Dim itm As ListViewItem
str(0) = tryIP1 & " Is Alive!!!"
str(1) = e.UserState
itm = New ListViewItem(str)
ListView1.Items.Add(itm)
str(0) = ""
str(1) = ""
itm = Nothing
End Sub
Private Sub pingTest1_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles pingTest1.RunWorkerCompleted
chkDone = chkDone + 1
End Sub
I added the Hey box and sure enough the ProgressChanged event gets triggered the amount of times I have hit the Go button. Is it something I have coded incorrectly?
It's most likely because you're adding, but not removing your handlers for the progress changed, so you're handling the event multiple times.
Try adding your Progress Changed Event Handlers when you're instantiating your Background workers, rather than every time you click your button. This way they will only handled once.