How to remove the most recently added control? - vb.net

I Intended to display an PictureBox in my form when the mouse hovered over another control. I then wanted to use a separate event for when the mouse left the control. This event would remove the displayed PictureBox from controls. However, because my events are private subs, I can't directly access the name of the control in the latter event. A solution to this would be a method that removes the most recently added control. If no such method exists, or there is an alternative way of approaching this problem, any help would be appreciated.
I tried simply using Controls.Remove(), but this requires a parameter. The name of the control as a string did not work either, as the parameter must be a control itself.
Private Sub Tile_MouseEnter(Sender As Object, e As EventArgs)
Dim CloseUpPic As New PictureBox With {Properties}
CloseUpPic.Image = Sender.Image
Controls.Add(CloseUpPic)
Refresh()
End Sub
Private Sub Tile_MouseLeave(Sender As Object, e As EventArgs)
Me.Controls.Remove()
End Sub
The program won't compile due to .Remove() needing a parameter
I expected for the Control to be created and displayed when the mouse entered the tile, and to cease to exist when the mouse left the tile.

For future reference, controls have Tag property which allows you to store whatever you like. In this case, you can store a reference to the newly created PictureBox. Furthermore, the "Sender" parameter tells you which control was the source of the event. You can cast sender to a control, then store the reference. Then, in the leave event, you can cast sender to a control, cast the .Tag to a control, and finally remove it:
Private Sub Tile_MouseEnter(Sender As Object, e As EventArgs)
Dim ctl As Control = DirectCast(Sender, Control)
Dim CloseUpPic As New PictureBox With {Properties}
CloseUpPic.Image = Sender.Image
Controls.Add(CloseUpPic)
ctl.Tag = CloseUpPic
Refresh()
End Sub
Private Sub Tile_MouseLeave(Sender As Object, e As EventArgs)
Dim ctl As Control = DirectCast(Sender, Control)
Dim ctlToRemove As Control = DirectCast(ctl.Tag, Control)
Me.Controls.Remove(ctlToRemove)
End Sub

I ended up using the following code to solve my issue:
For Each Closeup In Controls.OfType(Of CloseUp)
Controls.Remove(Closeup)
Next
I created a new class of my own called Closeup, that inherits PictureBox. I then looped through each Closeup in controls (There was only one but this code works for multiple controls), and removed them.

Related

How do I make controls inside of a FlowLayoutPanel do things?

I'm adding controls to a FlowLayoutPanel like this:
Dim box As New PictureBox
(I'm making the controls using code, then I'm using FlowPanelLayout1.Controls.Add(box)).
How do I make these controls do things? In my code I'm using For Each, so multiple are made using this, my goal is to make each one be able to do what I want and the code for each would be different. How can I achieve this goal?
I believe what you're after is AddHandler. AddHandler allows you to programmatically assign code to an event at runtime. So you can create a Sub specifically to handle events from the picture boxes you create.
You can also retrieve the control that raised the event. This allows you to modify it, or access it's tag, allowing you to attach data to the PictureBox when you add it to the FlowLayoutPanel.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Create a new picturebox
Dim PB As New PictureBox With {.Size = New Drawing.Size(50, 50), .SizeMode = PictureBoxSizeMode.Normal}
PB.Image = Image.FromFile("res.png")
'Add it to the FlowLayoutPanel
FlowLayoutPanel1.Controls.Add(PB)
'Assign HandlePictureboxClick() to handle the PictureBox's DoubleClick event.
AddHandler PB.DoubleClick, AddressOf HandlePictureboxClick
End Sub
Public Sub HandlePictureboxClick(sender As Object, e As EventArgs)
'Retrive the control that raised the event (In this case, the one you double-clicked)
Dim Picturebox As PictureBox = CType(sender, PictureBox)
'Do whatever you want to do with it
Picturebox.SizeMode = PictureBoxSizeMode.Zoom
End Sub
The example above will change the PictureBox's size mode to zoom when the user double clicks on it, without modifying the other PictureBoxes in the FlowLayoutPanel

How to set an event for MDI child forms without adding code to each form?

I would like to set the background color for a certain type of control on all child forms that open. I have an MdiParent form that is used to open the other forms within itself. I don't want to add code to each child form as this would be very extensive. This would be used as a theme feature for the application so I would like to have it automatically change the background colors based on logic in the main form. Is there something like a global event that could trigger for all Form.Load events?
So far I have created an event in the Parent form but it doesn't work for nested controls
Private Sub frmMain_MdiChildActivate(sender As Object, e As EventArgs) Handles Me.MdiChildActivate
Dim ParentControl As frmMain = sender
Dim ChildControl = ParentControl.ActiveControl
If ChildControl IsNot Nothing Then
For Each FormControl As Control In ChildControl.Controls
If FormControl.GetType = GetType(GroupBox) Then
RemoveHandler FormControl.Paint, AddressOf PaintBorderlessGroupbox
AddHandler FormControl.Paint, AddressOf PaintBorderlessGroupbox
End If
Next
End If
End Sub
I was able to accomplish this by using Form.MdiChildActivate and adding the event to the appropriate controls based on the Event and EventHandler.
Private Sub frmMain_MdiChildActivate(sender As Object, e As EventArgs) Handles Me.MdiChildActivate
Dim ParentForm As frmMain = sender
Dim ChildForm = ParentForm.ActiveMdiChild
Dim EventName = "Paint"
Dim EventHandlerName = "PaintBorderlessGroupBox"
If ChildForm IsNot Nothing Then
AddEventToControls(ChildForm, GetType(GroupBox), EventName, EventHandlerName)
End If
End Sub
Private Sub AddEventToControls(Control As Control, ControlType As Type, ControlEventName As String, ControlEventMethod As String)
For Each ChildControl In Control.Controls
If ChildControl.GetType = ControlType Then
If ChildControl.Controls.Count > 0 Then
AddEventToControls(ChildControl, ControlType, ControlEventName, ControlEventMethod)
End If
Dim EventMethod = Me.GetType().GetMethod(ControlEventMethod, BindingFlags.NonPublic Or BindingFlags.Instance)
Dim ControlEvent As EventInfo = ControlType.GetEvent(ControlEventName)
Dim del = [Delegate].CreateDelegate(ControlEvent.EventHandlerType, Me, EventMethod)
ControlEvent.RemoveEventHandler(ChildControl, del)
ControlEvent.AddEventHandler(ChildControl, del)
End If
Next
End Sub
The call to AddEventToControls() assigns the handler to the Control and any child controls that it would also apply to. In this case I am setting the Control.Paint event to paint a GroupBox a specific way. This may not be the cleanest method to accomplish this but I was able to create a "Global Event" for all child forms without ever touching the code on each form.
In your parent form, keep a collection of all Child Forms that have been activated. You can then traverse that collection and change the relevant control property for each one.
For Each ChildForm in MyCollection
ChildForm.TextBox.BackColor = Red
Next
Of course:
The ChildForm control has to be accessible by the parent form
The ChildForm has to still exist (i.e. not been closed in the mean
time)
You can't check for ChildForm closure because you are not adding any
code to the ChildForm to signal such an event.
You have to handle the exceptions when you try to change a form that
has been closed.
Much easier to include a method in your ChildForm design for ChangeColour(), whether that method is fired by event or direct call is your design decision. And to include a method to tell the parent form when a ChildForm dies so that it stops looking for it.

Get form control using string name VB.NET

I have a control on a form UserNameCtrl and that control has a sub called LoadCtrl
I essentially have loads of these subs for clicks, so I want to put them all into one event handler
Private Sub NewsletterBtn_Click(sender As Object, e As EventArgs) Handles NewsletterBtn.Click, NewsletterImage.Click
If Not MainNewsletterCtrl.Loaded() Then MainNewsletterCtrl.Load()
End Sub
However within each of the subs the control names are hardcoded to call the .loaded and .load functionality.
I've wrote a new version of this
Private Sub GenericNavItem_Click(sender As Object, e As EventArgs)
Dim ctrl As Control = Controls.Find(sender.tag, True).FirstOrDefault
'Want to do the Controlname.Load here
End Sub
Using the tag (which I named as the control name) I got the corresponding control. But it's bringing back it as a control rather than of the type I want it to be.
I know I declare it as Control, but I don't know how I can cast it to be the ControlName.Load rather than the generic control.
If they are all the same class (or base class), then just cast to that class. If they are all different class but have the same method Load and Loaded, then I suggest you create an interface.
Interface ISomeName
Sub Load()
Function Loaded() As Boolean()
End Interface
Make sure all your class implement it and then just cast to that interface.
Private Sub GenericNavItem_Click(sender As Object, e As EventArgs)
Dim ctrl As Control = Controls.Find(sender.tag, True).FirstOrDefault
Dim ctrlInterface As ISomeName = CType(ctrl, ISomeName)
If Not ctrlInterface.Loaded() Then ctrlInterface.Load()
End Sub

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.

Datagridview (on usercontrol) painted cell revert to defaultstyle

In my program (Winforms), i use usercontrols as pages. I do this in the following way:
I have a panel on my Form1 in which i load usercontrols, on these usercontrols are my actual controls (buttons, labels, checkboxes etc). So actually i'm using the user controls as "sub" pages in my form.
The usercontrols are declared at the start of runtime, so they are "live" when i load them into the panel. This has allways worked fine, untill i ran into a problem yesterday.
Yesterday I used a datagridview on one of those usercontrols and during the ParentChanged of this control i call a Sub which changes the backcollor of this data grid view. (The sub itself is a public sub which is located in a module)
Like this:
Private Sub Init_ParentChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.ParentChanged
GetRecipe(RecipeDGV, "Bla")
End Sub
Public Sub GetRecipe(ByVal Data As DataGridView, ByVal RecipeID As String)
For r As Integer = 0 To Recipe_Mem.Rows.Count - 1
For c As Integer = 0 To Recipe_Mem.Columns.Count - 1
Data(c, r).Style.BackColor = getPresetColorByID(CInt(Data(c, r).Value))
Data(c, r).ToolTipText = getPresetNameByID(CInt(Data(c, r).Value))
NEXT
NEXT
End Sub
When i run my program, i can see that my dgv gets the data from the database (which happens in the same Sub). But there is no color change in the cells.
Now, something i've noticed is that when i add a button to the user control, and use the click event of this button to call the same sub for the coloring, it does work).
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
GetRecipe(RecipeDGV, "Bla")
End Sub
Any idea why this works on a button click event, but it doesn't in the usercontrol parent changed event? it looks like during the parent changed event there is some kind of repaint event of the dgv. how do i solve this?
Have you tried wrapping the gridview in an ajax control ( with triggers on the ParentChanged event?
I'm a little puzzled by the "RecipeID" argument being passed as it isn't used.