Is there a Dynamic Solution to this that someone might be able to explain? - vb.net

Ref: There are 20ish panels that need this event at visibility change, all with the same naming conventions being project_x (x being the panel number)
I'm not able to do an expectation vs reality on this code because I was not able to find how to best do this.
Further Explanation: On the event 'VisibleChanged' Which is controlled by an External Class, We want a way to apply this dynamically in a shorter code.
Sure we can do this and hard code everything in and it works fine, but I was wondering if there was a way to do this dynamically.
Private Sub project_1_VisibleChanged(sender As Object, e As EventArgs) Handles project_1.VisibleChanged
Try
If project_1.Visible = True Then
'There's code here but it's been omitted
End If
Catch ex As Exception
End Try
End Sub

You can find all relevant panels in form load and subscribe each to a single event handler method
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
'assuming the panels are on the main form you would use Me
'else you would use the name of the control that the panel is located within - e.g. SomeControlName.Controls...
Dim panels = Me.Controls.OfType(Of Panel).Where(Function(panel) panel.Name.StartsWith("project_"))
For Each panel In panels
AddHandler panel.VisibleChanged, AddressOf PanelVisibilityChanged
Next
End Sub
Private Sub PanelVisibilityChanged(sender As Object, e As EventArgs)
Dim panel = DirectCast(sender, Panel)
Try
If panel.Visible Then
'assuming the picture box is within the panel
Dim pictureBox = panel.Controls.Find($"{panel.Name}_img", True)(0)
'There's code here but it's been omitted
End If
Catch ex As Exception
End Try
End Sub

Yes, like this:
Private Sub panels_VisibleChanged(sender As Object, e As EventArgs) _
Handles project_1.VisibleChanged,
project_2.VisibleChanged,
project_3.VisibleChanged,
project_4.VisibleChanged,
project_5.VisibleChanged,
project_6.VisibleChanged,
project_7.VisibleChanged
Dim ctrl As Panel = DirectCast(sender, Panel)
Try
If ctrl.Visible = True Then
'There's code here but it's been omitted
End If
Catch ex As Exception
End Try
End Sub
There are a couple of other ways to do this as well. This is probably the cleanest for VB.net and your situation.

Related

vb.net / C# External handler

I have a form used to display options about processes.
When options are applyed :
frmOptions
For Each ltvi As ListViewItem In ltvProcesses.CheckedItems
Dim proc As Process = CType(ltvi.Tag, Process)
targeted_processes.Add(proc)
AddHandler proc.Exited, AddressOf frmAET.a_target_process_has_been_exited
proc.EnableRaisingEvents = True
Next
And in a tools module :
Public Sub a_target_process_has_been_exited(sender As Object, e As EventArgs)
frmAET.btnStatus.ForeColor = Color.Red
msgbox("OK")
End Sub
And... the messagebox displays its message but the color doesn't change.
After some tries, the problem is when a_target_process_has_been_exited is actived by the handler.
If I do this (Button1 belongs to frmAET, like btnStatus) :
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
a_target_process_has_been_exited()
End Sub
It works ! But not when I really want (when a process is ended).
So, the problem is when the sub is called by the process end event.
And when I try to specify this (maybe a frmAET's sub can modify its controls) :
AddHandler leproc.Exited, AddressOf frmAET.a_target_process_has_been_exited
Error : Reference to a non-shared member requires an objet reference
Could you help me ?
Your AddHandler seems to use AddressOf frmAET.a_target_process_has_been_exited, that means method in frmAET form itself. Not tools module as you stated.
Let's consider your frmOptions is correct and frmAET is containing this (with removed explicit reference to frmAET, since it's local)
Public Sub a_target_process_has_been_exited(sender As Object, e As EventArgs)
btnStatus.ForeColor = Color.Red
MsgBox("OK")
End Sub
As comments already explained, your event handler is called in another thread and you need to sync yourself to main UI thread. For example like this:
Public Sub a_target_process_has_been_exited(sender As Object, e As EventArgs)
Me.BeginInvoke(Sub() HandleProcessExit())
End Sub
Public Sub HandleProcessExit
btnStatus.ForeColor = Color.Red
MsgBox("OK")
End Sub
This version will block main UI thread until you click on the MsgBox button.
You should add some Try/Catch block. Exception in another threads are difficult to detect otherwise.
This code depends on implicit form instances that VB.NET creates for you. I expect your frmAET is actually My.Forms.frmAET instance to make this work.

Determine Form Size from another Form

VB2012: In order to do some calculations in my main form I need to know the form size of a secondary form. The form size may change from user to user depending on OS and theme. I understand that the client size stays the same. However I think I am not doing something correctly as I get different numbers depending on where I call for the form size.
As an illustration here is my main form where on the load event I attempt to get the size of an Alert form
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'get the default width and height of an Alert form.
Dim frmW As Integer = frmAlert.Width 'in pixels
Dim frmH As Integer = frmAlert.Height 'in pixels
Dim frCsW As Integer = frmAlert.ClientSize.Width 'in pixels
Dim frmCsH As Integer = frmAlert.ClientSize.Height 'in pixels
Debug.Print("frmW={0} frmH={1} frCsW={2} frmCsH={3}", frmW, frmH, frCsW, frmCsH)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'set up a new alert form
Dim frm As New frmAlert
'show the alert form
frm.StartPosition = FormStartPosition.CenterParent
frm.Show() 'with this option the Alert Forms stay on the screen even if the Main form is minimized.
End Sub
Now the Alert form is set with FormBorderStyle=FixedDialog, ControlBox=False, MaximizeBox=False, and MinimizeBox=False and in the Alert form I have this on the load event:
Private Sub frmAlert_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Debug.Print("me.width={0} me.height={1} cs.width={2} cs.height={3}", Me.Width, Me.Height, Me.ClientSize.Width, Me.ClientSize.Height)
End Sub
and here is the debug output
frmW=380 frmH=168 frCsW=374 frmCsH=162
me.width=390 me.height=200 cs.width=374 cs.height=162
As expected the client size is the same but the total form size is different. I am trying to wrap my head around the differences in .Height and .Width. No other code exists to change the form properties. The second debug statement matches the form Size in the IDE designer. Why are the dimensions different? How would I properly query to get the form size from another form?
Before the form is shown it will have a smaller size compared to when it is visible. This is because when you show the form Windows will do all kinds of stuff to it based on the user's screen and theme settings, such as:
Resize it if the user has different DPI settings.
Apply borders to it based on the user's selected window theme.
(etc.)
Your best bet is to show the form first, then get its size.
If you don't want the form to be visible right away you can set its Opacity property to 0 to make it invisible, then change it back to 1.0 once you need the form to be shown to the user.
Ok so based on #Visual Vincent's suggestion I created a constructor when creating a new form. This goes in frmAlert.Designer.vb
Partial Class frmAlert
Private mIsProcessFormCode As Boolean = True
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.mIsProcessFormCode = True
End Sub
Public Sub New(ByVal IsProcessFormCode As Boolean)
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.mIsProcessFormCode = IsProcessFormCode
End Sub
End Class
Then on the frmMain I add this code:
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Debug.Print("routine={0}", System.Reflection.MethodBase.GetCurrentMethod.Name)
Dim frm As New frmAlert(False) 'create an instance of the form without any of the Form processes
frm.Opacity = 0 'makes the form totally transparent
frm.Visible = False
frm.Show()
Dim frmWidth As Integer = frm.Width
Dim frHeight As Integer = frm.Height
Dim frCsWidth As Integer = frm.ClientSize.Width
Dim frCsHeight As Integer = frm.ClientSize.Height
frm.Close()
frm.Dispose()
Debug.Print("frmWidth={0} frHeight={1} frCsWidth={2} frCsHeight={3}", frmWidth, frHeight, frCsWidth, frCsHeight)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Debug.Print("routine={0}", System.Reflection.MethodBase.GetCurrentMethod.Name)
'set up the alert form normally
Dim frm As New frmAlert
'show the alert form
frm.StartPosition = FormStartPosition.CenterParent
frm.Show() 'with this option the Alert Forms stay on the screen even if the Main form is minimized.
End Sub
and in the frmAlert I add this code:
Private Sub frmAlert_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
Try
'process the form code ONLY if required
Debug.Print("routine={0} mIsProcessFormCode={1}", System.Reflection.MethodBase.GetCurrentMethod.Name, mIsProcessFormCode)
If mIsProcessFormCode Then
'do Closed stuff
End If
Catch ex As Exception
Debug.Print(ex.ToString)
End Try
End Sub
Private Sub frmAlert_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
Try
'process the form code ONLY if required
Debug.Print("routine={0} mIsProcessFormCode={1}", System.Reflection.MethodBase.GetCurrentMethod.Name, mIsProcessFormCode)
If mIsProcessFormCode Then
'do Closing stuff
End If
Catch ex As Exception
Debug.Print(ex.ToString)
End Try
End Sub
Private Sub frmAlert_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
'process the form code ONLY if required
Debug.Print("routine={0} mIsProcessFormCode={1}", System.Reflection.MethodBase.GetCurrentMethod.Name, mIsProcessFormCode)
Debug.Print("me.width={0} me.height={1} cs.width={2} cs.height={3}", Me.Width, Me.Height, Me.ClientSize.Width, Me.ClientSize.Height)
If mIsProcessFormCode Then
'process text file
End If
Catch ex As Exception
Debug.Print(ex.ToString)
End Try
End Sub
and the debug output:
routine=frmMain_Load
routine=frmAlert_Load mIsProcessFormCode=False
me.width=390 me.height=200 cs.width=374 cs.height=162
routine=frmAlert_FormClosing mIsProcessFormCode=False
routine=frmAlert_FormClosed mIsProcessFormCode=False
frmWidth=390 frHeight=200 frCsWidth=374 frCsHeight=162
routine=Button1_Click
routine=frmAlert_Load mIsProcessFormCode=True
me.width=390 me.height=200 cs.width=374 cs.height=162
I believe this is what I want. All frmAlert sizes match what I have in the IDE designer. If you can think of any modifications please let me know. Many thanks.

Labels' Click events

I have an application where I have around 50 labels. In those labels a number is visible.
When the user clicks on the label the number needs to be written to an edit box.
This works fine, the only problem is that I have added 50 functions like below, and every time it’s the same. I was wondering if there is a common function for this
Remark: The labels have different names. So if its possible that this will work for all the labels on the form.
Private Sub LI_L_Click(sender As Object, e As EventArgs) Handles LI_L.Click
cmbOBJID.Text = LI_L.Text
End Sub
In the form designer, you should be able to set the handler for every label to the same function. Then you can use the "sender" parameter to determine which label is raising the event.
Notice also how all the controls that the function is linked to are listed after the "Handles" keyword. This is another way you could connect the code to all the labels if you prefer this over using the Visual Studio UI properties grid.
Private Sub LI_Click(sender As Object, e As EventArgs) Handles Label1.Click, Label2.Click, Label3.Click
cmdOBJID.Text = DirectCast(sender, Label).Text
End Sub
While it is easy to add several events to one handler in the style of
Private Sub LI_Click(sender As Object, e As EventArgs) Handles Label1.Click, Label2.Click, Label3.Click
It will be tedious for more than just a few labels.
You can add handlers programatically if you can find a way to refer to the labels you need to add handlers to. In this example, I put all the labels in a groupbox named "GroupBoxOptions":
Option Infer On
Option Strict On
Public Class Form1
Sub TransferDataToEditBox(sender As Object, e As EventArgs)
Dim lbl = DirectCast(sender, Label)
tbEditThis.Text = lbl.Text
End Sub
Sub InitLabelHandlers()
For Each lbl In GroupBoxOptions.Controls.OfType(Of Label)
AddHandler lbl.Click, AddressOf TransferDataToEditBox
Next
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitLabelHandlers()
End Sub
End Class
You may have some other way of selecting the labels which use the handler.
A pretty nice and quick solution is to traverse all the label controls on a form, assigning through the AddHandler function the event to run when a user clicks a label.
In code:
For Each c As Control In Me.Controls.OfType(Of Label)
AddHandler c.Click, AddressOf myLabelClick
Next
With the prevous snippet, we loop onto all the winform controls of type Label. A loop like that is useful when we have a lot of labels for which an event must be assigned. For each of them, we associate the event Click of the control with a customized Sub named myLabelClick. That subroutine will look like the following:
Private Sub myLabelClick(sender As Object, e As EventArgs)
cmdObjId.Text = DirectCast(sender, Label).Text
End Sub
Here we use the sender variable (which represents the control for which the click has been done) to access its Text property, and change the cmdObjId.Text accordingly.
Just to complement the solution from BlueMonkMN:
If you are using the DevExpress Tools, you need to import DevExpress.XtraEditors and change Label to LabelControl:
DirectCast(sender, LabelControl).Text
This worked for me.

Closing form with Gif throws InvalidOperationException

This is clearly a problem of me not understanding how to properly setup a UI thread, but I can't figure out how to fix it.
I have a datagridview where I click a button, get the information from the network, and then display it on the datagridview with the new data. While it is on the network I have a form I show with an updating gif, a form I called "loading". Within that form I have the gif updating using the typical OnFrameChanged and m_isAnimating code that is on the internet.
However, no matter what format I use, I always get this exception caught here:
Public loader As New Loading
Private Sub OnFrameChanged(ByVal o As Object, ByVal e As EventArgs)
Try ' If animation is allowed call the ImageAnimator UpdateFrames method
' to show the next frame in the animation.
Me.Invalidate()
If m_IsAnimating Then
ImageAnimator.UpdateFrames()
Me.Refresh()
'Draw the next frame in the animation.
Dim aGraphics As Graphics = PictureBox1.CreateGraphics
aGraphics.DrawImage(_AnimatedGif, New Point(0, 0))
aGraphics.Dispose()
End If
Catch ex As InvalidOperationException
End Try
End Sub
And it usually says something along the lines of "was accessed from a thread it wasn't created on" or "Cannot access a disposed object. Object name: 'PictureBox'."
But I don't know why that is, since I am creating a new instance here every time. Here's the button's code:
Private Sub btnSlowSearch_Click(sender As Object, e As EventArgs) Handles btnSlowSearch.Click
Me.Cursor = Cursors.WaitCursor
'get datatable
loader.Show()
BWorkerLoadProp.RunWorkerAsync() 'go get data on network
'bworker will update datagridview with new data
'wait for worker to finish
If BWorkerLoadProp.IsBusy Then
Threading.Thread.Sleep(1)
End If
loader.Close()
End Sub
I realize it isn't very good code, but I have tried putting the loader inside the background worker, I have tried whatever. But no matter what the exception is called.
What's the proper way to show another updating form as I do background work?
The behavior documented is difficult to reproduce.
Probably something between the thread switching causes a call to OnFrameChanged after the call to close in the btnSlowSearch_Click.
In any case logic seems to suggest to call the ImageAnimator.StopAnimate in the close event of the form that shows the animation
So looking at your comment above I would add the following to your animator form
// Not needed
// Public loader As New Loading
Private Sub OnFrameChanged(ByVal o As Object, ByVal e As EventArgs)
Try
Me.Invalidate()
If m_IsAnimating Then
ImageAnimator.UpdateFrames()
Me.Refresh()
'Draw the next frame in the animation.
Dim aGraphics As Graphics = PictureBox1.CreateGraphics
aGraphics.DrawImage(_AnimatedGif, New Point(0, 0))
aGraphics.Dispose()
End If
Catch ex As InvalidOperationException
.. do not leave this empty or remove altogether
End Try
End Sub
Private Sub Form_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
... if you need to stop the closing you should do it here without stopping the animation
If m_IsAnimating Then
ImageAnimator.StopAnimate(AnimatedGif, _
New EventHandler(AddressOf Me.OnFrameChanged))
m_isAnimating = False
End If
End Sub
This is certainly not the only way to do this but I will provide you the simplest working example in hopes that it will help you to correct your own application.
1) Create a new vb.net windows forms application and add a button (Button1) onto the form.
2) Change the Form1 code to this:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If fLoading Is Nothing Then ' can only show one loading screen at a time
Dim oLoadingThread As clsLoadingThread = New clsLoadingThread ' creat new thread
oLoadingThread.ShowWaitScreen() ' show the loading screen
'-----------------------------------------
' your real processing would go here
'-----------------------------------------
For i As Int32 = 0 To 999999
Application.DoEvents()
Next
'-----------------------------------------
oLoadingThread.CloseLoadingScreen() ' we are done processing so close the loading form
oLoadingThread = Nothing ' clear thread variable
End If
End Sub
End Class
Public Class clsLoadingThread
Dim oThread As System.Threading.Thread
Private Delegate Sub CloseLoadingScreenDelegate()
Public Sub ShowWaitScreen()
' create new thread that will open the loading form to ensure animation doesn't pause or stop
oThread = New System.Threading.Thread(AddressOf ShowLoadingForm)
oThread.Start()
End Sub
Private Sub ShowLoadingForm()
Dim fLoading As New frmLoading
fLoading.ShowDialog() ' Show loading form
If fLoading IsNot Nothing Then fLoading.Dispose() : fLoading = Nothing ' loading form should be closed by this point but dispose of it just in case
End Sub
Public Sub CloseLoadingScreen()
If fLoading.InvokeRequired Then
' Since the loading form was created on a seperate thread we need to invoke the thread that created it
fLoading.Invoke(New CloseLoadingScreenDelegate(AddressOf CloseLoadingScreen))
Else
' Now we can close the form
fLoading.Close()
End If
End Sub
End Class
Module Module1
Public fLoading As frmLoading
End Module
3) Add a new form and call it frmLoading. Add a picturebox to the form and set the image to your updating gif.
4) Change the frmLoading code to this:
Public Class frmLoading
Private Sub frmLoading_Load(sender As Object, e As EventArgs) Handles Me.Load
fLoading = Me ' ensure that the global loading form variable is set here so we can use it later
End Sub
Private Sub frmLoading_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
fLoading = Nothing ' clear the global loading form since the form is being disposed
End Sub
End Class
Normally I would add the clsLoadingThread Class and Module1 Module to their own files but it's easier to show the code to you this way.

List of Windows in vb.net application

I have an MDI application where I'm trying to get a list of open windows for a ComponentOne Ribbon Menu. Using VB .NET.
I have this sub for instantiating a new child form within the MDI container:
Private Sub newButton_Click(sender As Object, e As EventArgs) Handles newButton.Click
' Create a new instance of the child form.
Dim ChildForm As New MyProject.MyForm
'Make it a child of this MDI form before showing it.
ChildForm.MdiParent = Me
m_ChildFormNumber += 1
ChildForm.Text = "Window " & m_ChildFormNumber
ChildForm.Show()
End Sub
Then in another Sub for the ribbon menu I try to get the list of windows.
I tried this:
Dim frm As System.Windows.Window
For Each frm In My.Application.Windows
frmButton = New C1.Win.C1Ribbon.RibbonButton(frm.Title)
...
But I get a NullReferenceException on the System.Windows.Window collection.
So then I tried this:
For Each Window In My.Application.Windows
frmButton = New C1.Win.C1Ribbon.RibbonButton(Window.Title)
...
But with that, I get "overload resolution failed because no accessible 'new' can be called without a narrowing conversion" on the arguments for the new RibbonButton. If I turn Option Strict On, of course it says it disallows late binding.
So I guess ultimately I'm wondering why my Windows collection is empty, even if I've opened child forms.
Then even beyond that, why does the New RibbonButton accept frm.Title but not Window.Title.
NOTE (in case you were wondering)...the frmButton is a class object:
Friend WithEvents frmButton As C1.Win.C1Ribbon.RibbonButton
Thank you!
Thanks to clues from multiple sources, I was able to get it working. In case anyone else is wondering how, here's my sample code:
Public Class mainForm
Private m_ChildFormNumber As Integer
Friend WithEvents frmButton As C1.Win.C1Ribbon.RibbonButton
Private Sub newButton_Click(sender As Object, e As EventArgs) Handles newButton.Click
' Create a new instance of the child form.
Dim ChildForm As New ProofOfConcept.FormResize
'Make it a child of this MDI form before showing it.
ChildForm.MdiParent = Me
m_ChildFormNumber += 1
ChildForm.Text = "Window " & m_ChildFormNumber
ChildForm.Show()
End Sub
Private Sub windowMenu_Dropdown(sender As Object, e As EventArgs) Handles windowMenu.DropDown
Dim count As Integer = Me.MdiChildren.Length
windowMenu.Items.ClearAndDisposeItems()
For i As Integer = 0 To count - 1
frmButton = New C1.Win.C1Ribbon.RibbonButton
frmButton.Text = Me.MdiChildren(i).Text
frmButton.Tag = i
If MdiChildren(i) Is ActiveMdiChild Then
frmButton.SmallImage = My.Resources.test
End If
windowMenu.Items.Add(frmButton)
AddHandler frmButton.Click, AddressOf frmButton_Click
Next
End Sub
Private Sub frmButton_Click(sender As Object, e As EventArgs)
Dim Rb As C1.Win.C1Ribbon.RibbonButton = DirectCast(sender, C1.Win.C1Ribbon.RibbonButton)
Me.ActivateMdiChild(MdiChildren(CInt(Rb.Tag)))
Me.MdiChildren(CInt(Rb.Tag)).Focus()
End Sub
End Class
8 years later, this seems to be the only question on how to implement a Window menu, so here's my updated version for the standard MenuStrip control.
Friend WithEvents ChildWindowMenu As ToolStripMenuItem
Private Sub WindowMenu_DropDownOpening(sender As Object, e As EventArgs) Handles windowMenu.DropDownOpening
Try
windowMenu.DropDownItems.Clear()
For Each child In Me.MdiChildren
ChildWindowMenu = New ToolStripMenuItem With {
.Text = child.Text,
.Tag = child
}
If child Is ActiveMdiChild Then
ChildWindowMenu.Checked = True
End If
windowMenu.DropDownItems.Add(ChildWindowMenu)
AddHandler ChildWindowMenu.Click, AddressOf ChildWindowMenu_Click
Next
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub ChildWindowMenu_Click(sender As Object, e As EventArgs)
Try
CType(CType(sender, ToolStripMenuItem).Tag, Form).Activate()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub