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.
Related
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.
I am having a weird error and I am sure it is something very simple, but I can't for the life of me figure out what is going on. I thought I was opening and closing the forms properly, but it appears something is amiss. I am working with VS 2015. I have my program set to close when the last form is closed. There are two forms that I am using right now.
Dashboard
Public Class frmDashboard
Private Sub recExit_Click(sender As Object, e As EventArgs) Handles recExit.Click
Me.Close()
End Sub
Private Sub recMember_Click(sender As Object, e As EventArgs) Handles recMember.Click, lblMember.Click
'Create instance of Member Form
Dim memberForm As New frmMember
'Open an instance
memberForm.Show()
Me.Close()
'Using this code test if the form window is still open
For Each frm As Form In Application.OpenForms
MessageBox.Show(frm.Name)
Next
End Sub
End Class
Members Form
Public Class frmMember
Private Sub frmMember_Load(sender As Object, e As EventArgs) Handles Me.Load
ShowSpouse(rdoMarried.Checked)
End Sub
Private Sub ShowSpouse(ByRef isMarried As Boolean)
If isMarried Then
'Marital status is set to married lets show the extra spouse information
'Lets first make the form larger
Me.Size = New Size(793, 576)
'Now lets unhide the form
pnlSpouse.Visible = True
'pnlSpouse.Enabled = True
lblSpouse.Visible = True
'lblSpouse.Enabled = True
lineSpouse.Visible = True
'lineSpouse.Enabled = True
'Now lets move the buttons to their new location
btnApply.Location = New Point(624, 541)
btnExit.Location = New Point(710, 541)
Else
'Single is Selected
'Let's make sure all the spouse information is hidden
'Lets first make the form smaller
Me.Size = New Size(793, 358)
'Now lets hide the form and disable the controls
pnlSpouse.Visible = False
'pnlSpouse.Enabled = False
lblSpouse.Visible = False
'lblSpouse.Enabled = False
lineSpouse.Visible = False
'lineSpouse.Enabled = False
'Now lets move the buttons to their new location
btnApply.Location = New Point(624, 320)
btnExit.Location = New Point(710, 320)
End If
End Sub
Private Sub MaritalStatusChanged(sender As Object, e As EventArgs) Handles rdoSingle.CheckedChanged, rdoMarried.CheckedChanged
ShowSpouse(rdoMarried.Checked)
End Sub
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click
Dim dashboard As New frmDashboard
'Open the dashboard
dashboard.Show()
'Close the member form
Me.Close()
End Sub
End Class
I havent added the database to it but eventually I will load data into the members form. When I click to open the members form it goes through and runs the code in the form load event for members form but then returns focus back to the dashboard form. Runs the Me.Close on the dashboard, but once it gets to End Sub thats when this exception gets thrown:
System.ObjectDisposedException was unhandled
Message: An unhandled exception of type 'System.ObjectDisposedException' occurred in System.Windows.Forms.dll
Additional information: Cannot access a disposed object.
Any help you guys can give would be greatly appreciated.
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.
On every DataGridView1_SelectionChanged event I need to run a Private Sub OnSelectionChanged() of the form that is loaded into Panel1 (see the image http://tinypic.com/r/2nu2wx/8).
Every form that can be loaded into Panel1 has the same Private Sub OnSelectionChanged() that initiates all the necessary calculations. For instance, I can load a form that calculates temperatures or I can load a form that calculates voltages. If different element is selected in the main form’s DataGridView1, either temperatures or voltages should be recalculated.
The problem is - there are many forms that can be loaded into Panel1, and I’m struggling to raise an event that would fire only once and would run the necessary Sub only in the loaded form.
Currently I’m using Shared Event:
'Main form (Form1).
Shared Event event_UpdateLoadedForm(ByVal frm_name As String)
'This is how I load forms into a panel (in this case frm_SCT).
Private Sub mnu_SCT_Click(sender As Object, e As EventArgs) Handles mnu_SCT.Click
frm_SCT.TopLevel = False
frm_SCT.Dock = DockStyle.Fill
Panel1.Controls.Add(frm_SCT)
frm_SCT.Show()
Var._loadedForm = frm_SCT.Name
RaiseEvent event_UpdateLoadedForm(Var._loadedForm)
End Sub
‘Form that is loaded into panel (Form2 or Form3 or Form4...).
Private WithEvents myEvent As New Form1
Private Sub OnEvent(ByVal frm_name As String) Handles myEvent.event_UpdateLoadedForm
‘Avoid executing code for the form that is not loaded.
If frm_name <> Me.Name Then Exit Sub
End Sub
This approach is working but I’m sure it can be done way better (I'd be thankful for any suggestions). I have tried to raise an event in the main form like this:
Public Event MyEvent As EventHandler
Protected Overridable Sub OnChange(e As EventArgs)
RaiseEvent MyEvent(Me, e)
End Sub
Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) _
Handles DataGridView1.SelectionChanged
OnChange(EventArgs.Empty)
End Sub
but I don't know to subscribe to it in the loaded form.
Thank you.
Taking into account Hans Passant’s comments as well as code he posted in related thread I achieved what I wanted (see the code below).
Public Interface IOnEvent
Sub OnSelectionChange()
End Interface
Public Class Form1
' ???
Private myInterface As IOnEvent = Nothing
' Create and load form.
Private Sub DisplayForm(frm_Name As String)
' Exit if the form is already displayed.
If Panel1.Controls.Count > 0 AndAlso _
Panel1.Controls(0).GetType().Name = frm_Name Then Exit Sub
' Dispose previous form.
Do While Panel1.Controls.Count > 0
Panel1.Controls(0).Dispose()
Loop
' Create form by its full name.
Dim T As Type = Type.GetType("Namespace." & frm_Name)
Dim frm As Form = CType(Activator.CreateInstance(T), Form)
' Load form into the panel.
frm.TopLevel = False
frm.Visible = True
frm.Dock = DockStyle.Fill
Panel1.Controls.Add(frm)
' ???
myInterface = DirectCast(frm, IOnEvent)
End Sub
Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) _
Handles DataGridView1.SelectionChanged
' Avoid error if the panel is empty.
If myInterface Is Nothing Then Return
' Run subroutine in the loaded form.
myInterface.OnSelectionChange()
End Sub
End Class
One last thing – it would be great if someone could take a quick look at the code (it works) and confirm that it is ok, especially the lines marked with “???” (I don’t understand them yet).
I have a windows form project with a main form. There is a textbox leave event that opens a new form. In that new forms load event i have a combobox item loop that populates the combobox items. It works perfectly fine if run on the main form but doesnt work on the second form. Why doesn't the comboboxes on the secondary form populate when that form is opened via a textbox_leave event from the main form?
this is the leave event
Private Sub tbChartTitle_Leave(sender As Object, e As System.EventArgs) Handles tbChartTitle.Leave
If Not tbChartTitle.Text = Nothing Then
frmTitleAttributes.Show()
End If
End Sub
This is the code that populates one of the comboboxes on the second form (it works if run on a combobox on the main form)
Private Sub frmTitleAttributes_Load(sender As Object, e As System.EventArgs) Handles Me.Load
InitializeComponent()
AddFonts()
End Sub
Private Sub AddFonts()
' Get the installed fonts collection.
Dim allFonts As New Drawing.Text.InstalledFontCollection
' Get an array of the system's font familiies.
Dim fontFamilies() As FontFamily = allFonts.Families
' Display the font families.
For i As Integer = 0 To fontFamilies.Length - 1
cbxTitleFonts.Items.Add(fontFamilies(i).Name)
Next
End Sub
make sure that the Load handler is hit after you show your form (use break point)
also you can try to call it in the Shown event
Private Sub frmTitleAttributes_Shown(sender as Object, e as EventArgs) _
Handles frmTitleAttributes.Shown
AddFonts()
End Sub