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
Related
I have a usercontrol form named "ucSETTINGS", where there is a textbox and once the button was clicked, the text inside the textbox will be added to the combobox from another usercontrol form name "ucITEMS"
I tried this code but it's not working
(cboCategory is the name of the combobox from ucITEMS, txtNAME is the textbox from ucSETTINGS)
Private Sub btnSAVE_Click(sender As Object, e As EventArgs) Handles btnSAVE.Click
Dim category As New ucITEMS()
category.cboCATEGORY.Items.Add(txtNAME.Text)
End Sub
Can someone help me?
In this sort of situation, the user controls don't know about each other by default and it should stay that way. The source UC just exposes an interface and lets whomever is watching use that as it sees fit. That means raising an event when something happens and exposing required data via properties, e.g.
Public Class SourceControl
Public ReadOnly Property TextBox1Text As String
Get
Return TextBox1.Text
End Get
End Property
Public Event Button1Click As EventHandler
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
OnButton1Click(EventArgs.Empty)
End Sub
Protected Overridable Sub OnButton1Click(e As EventArgs)
RaiseEvent Button1Click(Me, e)
End Sub
End Class
The Text of the TextBox is exposed via a property and, when the user clicks the Button, the UC raises an event.
The destination UC provides an interface for new items to be provided but it adds them to its own ComboBox, e.g.
Public Class DestinationControl
Public Sub AddItemToComboBox1(item As Object)
ComboBox1.Items.Add(item)
End Sub
End Class
The form then plays go-between, handling the event, getting the property and calling the method:
Private Sub SourceControl1_Button1Click(sender As Object, e As EventArgs) Handles SourceControl1.Button1Click
DestinationControl1.AddItemToComboBox1(SourceControl1.TextBox1Text)
End Sub
Obviously you would use something more specific and appropriate than my generic naming.
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.
I try using delegate to pass info from dialog form to active MDI child form(not parent form) but it only accepts one data, how to I do this with multiple data like shown in the picture below:
this is I use so far: it only accepts one data from textbox
MDI Child Form:
Private Delegate Sub DoSearch(Msg As String)
Private PerformSearch As DoSearch
Private Sub InvokeFunc(Msg As String)
If PerformSearch IsNot Nothing Then
PerformSearch .Invoke(Msg)
End If
End Sub
Public Sub FuncDisplayMsg(Msg As String)
msg(Msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
PerformSearch = AddressOf Me.FuncDisplayMsg
FrmSecond.InvokeDel = AddressOf Me.InvokeFunc
FrmSecond.Show()
End Sub
Dialog Form
Public Delegate Sub SearchInvoke(Msg As String)
Public InvokeSearch As SearchInvoke
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Me.InvokeSearch IsNot Nothing Then
InvokeSearch .Invoke(Me.txtMsg.Text)
End If
End Sub
How do I pass the values of a control(textbox, combobox & checkbox) from a dialog form to an Active MDI Child Form (assuming many MDI Child is open) like shown in a picture, and perform the search within the MDI Child
You seem to be jumping through a few too many hoops. You don't need two delegates. You only need one. For instance, if your dialog window had code like this:
Public Delegate Sub SearchInvoke(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
Public Property InvokeSearch As SearchInvoke
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Me.InvokeSearch IsNot Nothing Then
InvokeSearch.Invoke(cboSelection.SelectedItem, txtMsg.Text, chkBox1.Checked, chkBox2.Checked)
End If
End Sub
Then you could simply have code in your main form that looked like this:
Public Sub FuncDisplayMsg(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
MessageBox.Show(msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.InvokeSearch = AddressOf Me.FuncDisplayMsg
FrmSecond.Show()
End Sub
Using a Class Instead of Multiple Parameters
Alternatively, you could package all of the data in a single object and then send that one object as a parameter to the delegate. For instance, if you had a class like this:
Public Class DialogData
Public Property Selection As Object
Public Property Msg As String
Public Property Chk1 As Boolean
Public Property Chk2 As Boolean
End Class
Then you could define your delegate and call it from the dialog form like this:
Public Delegate Sub SearchInvoke(data As DialogData)
Public Property InvokeSearch As SearchInvoke
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Me.InvokeSearch IsNot Nothing Then
InvokeSearch.Invoke(New DialogData() With
{
.Selection = cboSelection.SelectedItem,
.Msg = txtMsg.Text,
.Chk1 = chkBox1.Checked,
.Chk2 = chkBox2.Checked
})
End If
End Sub
And you could handle the delegate invocation in your main form like this:
Public Sub FuncDisplayMsg(data As DialogData)
MessageBox.Show(data.Msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.InvokeSearch = AddressOf Me.FuncDisplayMsg
FrmSecond.Show()
End Sub
Using an Event Instead of a Delegate
Technically, an event is just a special kind of delegate, so they effectively work in the same way. However, the VB.NET syntax for working with events is considerably different than working with standard delegates. Since handling events is commonplace, you may find it more "normal" to implement it as an event rather than a standard delegate. To do that properly, you'd want to make an EventArgs class that contains properties to hold the data (similar to the previously discussed DialogData class), for instance:
Public Class SearchSubmittedEventArgs
Inherits EventArgs
Public Property Selection As Object
Public Property Msg As String
Public Property Chk1 As Boolean
Public Property Chk2 As Boolean
End Class
Then, you could declare and raise the event from the dialog form like this:
Public Event SearchSubmitted As EventHandler(Of SearchSubmittedEventArgs)
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
RaiseEvent SearchSubmitted(Me, New SearchSubmittedEventArgs() With
{
.Selection = cboSelection.SelectedItem,
.Msg = txtMsg.Text,
.Chk1 = chkBox1.Checked,
.Chk2 = chkBox2.Checked
})
End Sub
And then you could handle the event on your main form like this:
Private WithEvents _dialog As frmSecondChild
Private Sub _dialog_SearchSubmitted(sender As Object, e As SearchSubmittedEventArgs) Handles _dialog.SearchSubmitted
MessageBox.Show(e.Msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
_dialog = New frmSecondChild()
_dialog.Show()
End Sub
Rather than using the WithEvents and Handles keywords, you could also choose to manually attach the event handler using the AddHandler and keyword. However, if you do that, don't forget to later detach it using RemoveHandler.
Passing a Business Object to the Dialog Instead of a Delegate
Another option would be to forgo having a delegate or event at all, and instead choose to give some business object to the dialog form. The dialog form could then just call a method on that business class to perform the search as needed. For instance, if you created a business class like this:
Public Class SearchBusiness
Public Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
MessageBox.Show(msg)
End Sub
End Class
Then you could just call it, as necessary, from the dialog form like this:
Public Property Business As SearchBusiness
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Business IsNot Nothing Then
Business.PerformSearch(cboSelection.SelectedItem, txtMsg.Text, chkBox1.Checked, chkBox2.Checked)
End If
End Sub
And you could show the dialog form from the parent form like this:
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.Business = New SearchBusiness()
FrmSecond.Show()
End Sub
Although, in that case, unless there are different kinds of search business classes which all inherit from SearchBusiness, it seems silly to have the parent form be the thing creating the business object when the dialog could just create it itself. Which leads me to the next option...
Using an Interface to Make the Business Object Interchangeable
Since having the separate business class being used explicitly by the dialog form is a bit constricting, the preferable method, in my mind, would be to create an interface for the search business, like this:
Public Interface ISearchBusiness
Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
End Interface
Public Class SearchBusiness
Implements ISearchBusiness
Public Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean) Implements ISearchBusiness.PerformSearch
MessageBox.Show(msg)
End Sub
End Class
Then, you could call it from the dialog form like this:
Public Property Business As ISearchBusiness
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Business IsNot Nothing Then
Business.PerformSearch(cboSelection.SelectedItem, txtMsg.Text, chkBox1.Checked, chkBox2.Checked)
End If
End Sub
And you could give the applicable business object to the dialog from your main form, the same way as above, like this:
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.Business = New SearchBusiness()
FrmSecond.Show()
End Sub
Passing the Parent Form to the Dialog Instead of a Separate Business Object
If, due to an unwise limitation in your design, only the parent form is capable of performing the business logic, then you could give the dialog a reference to the parent form rather than to a separate business object. However, in that case, I would definitely stick with using an interface. That way, you could, at a later date, refactor the code to give the dialog a separate business object that implements the same interface rather than the parent form. You wouldn't want to cement that poor design in stone. So, if you had the same interface as above:
Public Interface ISearchBusiness
Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
End Interface
Then you'd still call it from the dialog in the same way, as above:
Public Property Business As ISearchBusiness
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Business IsNot Nothing Then
Business.PerformSearch(cboSelection.SelectedItem, txtMsg.Text, chkBox1.Checked, chkBox2.Checked)
End If
End Sub
Then you could implement the interface in your parent form like this:
Public Class FrmParent
Implements ISearchBusiness
Public Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean) Implements ISearchBusiness.PerformSearch
MessageBox.Show(msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.Business = Me
FrmSecond.Show()
End Sub
End Class
Displaying the Dialog Modally
When a form is displayed modally (i.e. using the ShowDialog method rather than the Show method), that means that execution does not continue in the parent form until the dialog form has been closed. Therefore, if you don't mind the dialog form stealing and holding onto the focus from the user until it is done (which is the typical behavior of a dialog window), then you could just show the dialog form modally and then read its properties once it is closed. For instance, in your main form you could just do something like this:
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.ShowDialog()
MessageBox.Show(FrmSecond.txtMsg.Text)
End Sub
It's not good practice, though, to access controls on another form directly like that. It would be better if the dialog form exposed properties for each datum and then the main form accessed the data through those properties.
This method is by far the simplest. Anywhere you can do it like this, it makes sense. This is, for instance the way the OpenFileDialog, ColorDialog, and other dialogs that are built-in to the .NET framework are designed. This design has one major drawback, though, which can limit its use. If you need to keep the dialog open until the work is complete, then you can't really do it this way. For instance, you may want to display some sort of progress bar on the dialog while the search was being performed. Or, you may want to allow for the fact that some validation error may occur in the business logic at which point you'd want the user to be able to make changes on the dialog and then try again. The latter is of particular concern in cases where the dialog is being used for data entry. For instance, if the dialog was being used to allow the user to submit a new sales order, then you don't want to close the dialog until the sales order has been successfully submitted. If some failure occurs while the data is being saved to the system, then you will likely want to let them fix the problem and then try submitting it again.
I'm relatively new to Windows Forms development and my first real application has reached a point where a lot of code starts to build up in my main Form file, so I decided to restructure my project using the MVC pattern.
One major problem I have is dealing with the different control events of the form. I have several buttons, textfields, comboboxes and also a tabcontroll element which again contains different input elements and so far, every procedure for handling clicks, updates and other changes is defined in my main class.
For example:
Private Sub btnOk_Click(sender As Object, e As EventArgs) Handles btnOk.Click
some code...
End Sub
So my question is: what would be the best way to handle these events outside of my main form? I'm more familiar with building GUIs in Java where you can use ActionListeners to achieve this but I have found nothing similar for my work with Windows Forms.
To subscribe to a Control event outside of your main form class, make your control public, so you can access from another class). This can be done using the Modifier property at design-time. Then, use the AddHandler keyword to subscribe to any event programmatically.
After researching a bit more, I found that there is probably not THE correct answer to this problem but I found 2 approaches which provide a solution in the way I was looking for. In both cases, I use a controller class which is responsible for handling any user interaction from my main form.
The first approach makes use of what DmitryBabich suggested, adding a handler to the object and referencing it to a method of my controller class:
in Form1:
Dim ctrl as new Controller(Me)
AddHandler Button1.Click, AddressOf ctrl.doSomething
Controller class:
Public Class Controller
Private myForm As Form1
Public Sub New(ByVal f As Form1)
myForm = f
End Sub
Public Sub doSomething(sender As Object, e As EventArgs)
MsgBox("Button clicked.")
End Sub
End Class
For an example this simple it is not necessary to pass an instance of Form1 over to the controller but if for example I'd like to access the values of other control elements as well, I can address them by using this instance of Form1.
For example:
Public Sub doSomething(sender As Object, e As EventArgs)
MsgBox("You clicked the button, by the way: The value of TextField1 is " & myForm.TextField1.text)
End Sub
The other approach is almost identical except that here the controller knows all the relevant user control objects of the form and can handle their events directly, meaning that in the main form I have to do nothing more than create an instance of the controller. In the controller however, I have to assign every user control I want to access to its own variable as soon as the main form is loaded:
in Form1:
Dim ctrl as new Controller(Me)
Controller class:
Public Class Controller
WithEvents myForm As Form1
WithEvents button1 As Button
WithEvents button2 As Button
Public Sub New(ByVal f As Form1)
myForm = f
End Sub
Public Sub formLoad() Handles myForm.Load
button1 = myForm.Button1
button2 = myForm.Button2
End Sub
Private Sub b1Click() Handles button1.Click
MsgBox("You clicked button1!")
End Sub
Private Sub b2Click() Handles button2.Click
MsgBox("Button #2 was clicked!")
End Sub
End Class
I have a form declared as s property WithEvents. If I add Handles formServers.FormClosing to a Sub declaration it works fine, but when I want to handle an event of a control within formServers I get the following error -
'Handles' in classes must specify a 'WithEvents' variable.
How do I correctly set this up? Thanks.
Private WithEvents formServers As New formServers
Private Sub txtServers_Closing(ByVal Sender As Object,
ByVal e As EventArgs) Handles formServers.txtServers.LostFocus
Me.SetServers()
If Me.ServersError Then
Dim Ex As New Exception("Error validating Servers.")
Dim ErrorForm = New formError(Ex, 101)
End If
End Sub
The error message is fairly misleading. The Handles keyword has several restrictions, it cannot work across different classes, it needs an object reference. You must use the more universal AddHandler keyword instead.
There are some additional problems in your scenario. Never use the LostFocus event, use Leave instead. And it is very important that you subscribe the event for the specific instance of the form, using As New gets you into trouble when you display the form multiple times, an ObjectDisposedException will be the outcome. Correct code looks like this:
Private formInstance As FormServers
Private Sub DisplayFormServer()
formInstance = new FormServers
AddHandler formInstance.txtServers.Leave, AddressOf txtServers_Closing
AddHandler formInstance.FormClosed, _
Sub()
formInstance = Nothing
End Sub
formInstance.Show()
End Sub
A much more elegant approach is to expose the event explicitly in your FormServers class. Make that look like this:
Public Class FormServers
Public Event ServersLeave As EventHandler
Private Sub txtServers_Leave(sender As Object, e As EventArgs) Handles txtServers.Leave
RaiseEvent ServersLeave(Me, EventArgs.Empty)
End Sub
End Class
The problem is that you do are not specifying WithEvents on the TextBox. Rather, you are specifying WithEvents on the Form. You can only use Handles on variables which you have declared directly with the WithEvents keyword. With the WithEvents being on the form, you will only be able to use Handles to handle events that are raised directly by the form itself. You will not be able to do so for events raised by any of its controls.
You can fix this in one of two ways. Either you can use AddHandler to register your event handler (rather than using the Handles keyword), or you can create a TextBox variable WithEvents and then set it to the appropriate TextBox object on the form, like this.
Private formInstance As New FormServers
Private WithEvents txtServers As TextBox
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
txtServers = formServers.txtServers
End Sub
Private Sub txtServers_LostFocus(Sender As Object, e As EventArgs) Handles txtServers.LostFocus
' ...
End Sub
The advantage of the latter approach, besides the more consistent, and possibly more elegant syntax, is that you don't have to remember to call RemoveHandler.