none
Custom Event RRS feed

  • Question

  • Hi everybody!
    I have a problem...please, can anybody help me?
    I have a custom control which i cover with other custom controls duing the execution of my application(like a menù with some buttons).

    The event Menù.MouseClick doesn't recognise the click on the menù, because if there is a button under the mouse cursor, it doesn't work. It works only if you click the empty surface of the control.

    Can i create a custom event in my control to recognise the click of the mouse in a generic point of the control?
    • Edited by DkSw Sunday, July 20, 2008 1:19 PM grammar mistake
    Sunday, July 20, 2008 1:16 PM

Answers

  • Okay -- maybe this is closer to what you are looking for.  I totally scaled down the classes here but it basically represents a UserControl that contains another custom control class that inherits from Button.  The button has a picturebox and label in it that have their Click and DoubleClick events handled through the button - so the entire button acts as one in terms of clicks and double clicks (you might want to add MouseEnter and Mouse Leave events to light up the button when the mouse is over it - your choice).  Notice that I indicate handling the Click event internally since that is only performing backcolor changes (and changes to the Isselected property which I left out this time).  However I have the DoubleClick event raise a custom event that gets handled in the parent control -- the "sender" argument in the handler would tell which button was double-clicked so its properties would say which form to open.

     
    Public Class UserControl1  
        Private WithEvents btnAccount1 As New AccountButton  
     
        Private Sub UserControl1_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load  
            Me.Controls.Add(btnAccount1)  
     
        End Sub 
     
        Private Sub btnAccount1_ActionRequested(ByVal sende As ObjectByVal e As System.EventArgs) Handles btnAccount1.ActionRequested  
            MessageBox.Show("This is where you would open a form, knowing which button was clicked due to the sender argument")  
        End Sub 
    End Class 
    Public Class AccountButton  
        Inherits Button  
     
        Private WithEvents lbl As New Label  
        Private WithEvents pb As New PictureBox  
        Public Event ActionRequested(ByVal sende As ObjectByVal e As EventArgs)  
     
        Public Sub New()  
            Me.Size = New Size(200, 48)  
            pb.Size = New Size(48, 48)  
            Me.Controls.Add(pb)  
            pb.Top = Me.Height / 2 - (pb.Height / 2)  
            pb.BackColor = Color.Red  
            lbl.Location = New Point(pb.Right + 5, 24 - (lbl.Height / 2))  
            lbl.Text = "Label" 
            Me.Controls.Add(lbl)  
        End Sub 
     
     
        Private Sub AccountButton_Click(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.Click, lbl.Click, pb.Click  
            'Handle selection/deselection here (i.e. back color changing).  
        End Sub 
     
        Private Sub AccountButton_DoubleClick(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.DoubleClick, lbl.DoubleClick, pb.DoubleClick  
            'Handle user activation here.    
            'Alternatively raise custom event to parent control...  
            RaiseEvent ActionRequested(MeNew EventArgs)  
        End Sub 
    End Class 
     



    As far as I can tell this would do what you are asking for -- just you would be adding many more buttons to the custom control and probably be doing it dynamically so the ActionRequested event handlers would be added in code when each new button is created (remember to remove the handler when the button is removed).  What do you think?
    • Edited by Dig-Boy Wednesday, July 23, 2008 2:30 AM forgot the code - D'Oh!
    • Marked as answer by DkSw Wednesday, July 23, 2008 9:23 AM
    Wednesday, July 23, 2008 2:29 AM

All replies

  • The only thing I can think of is to add handlers for all the sub controls.  The following example does this through a recursive loop in the Load event but if you are dynamically adding controls you would have to adjust this appropriately...

     
    Public Class UserControl1  
     
        Private Sub UserControl1_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load      
            AttachClickHandlers(Me)  
        End Sub 
     
        Private Sub AttachClickHandlers(ByVal ctrl As Control)  
            AddHandler ctrl.Click, AddressOf UserControl1_Click  
            For Each c As Control In ctrl.Controls  
                AttachClickHandlers(c)  
            Next 
        End Sub 
     
        Private Sub UserControl1_Click(ByVal sender As ObjectByVal e As System.EventArgs)  
            MessageBox.Show("Click registered")  
        End Sub 
    End Class 
     

    If this doesn't work for you then you might consider posting in this forum...

    http://forums.microsoft.com/MSDN/ShowForum.aspx?ForumID=5&SiteID=1
    Monday, July 21, 2008 12:11 AM
  • Thanks for your answer! It seems work, but not properly...
    I added the AttachClickHandler in the function that i use to redraw the control when i add a control.
    The problem is that if i Click every "button control" only the first change the colour...
    This is the code which i use:

    Private Sub MenùAccount_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick 
            Dim MouseLocationX As Integer = e.X 
            Dim MouseLocationY As Integer = e.Y 
            For Each controllo As Account In Me.Controls 
                controllo.Deselect() 
                If controllo.Location.X < MouseLocationX And (controllo.Location.X + ControlWidth) > MouseLocationX And controllo.Location.Y < MouseLocationY And (controllo.Location.Y + ControlHeight) > MouseLocationY Then 
                    _IDSelectedButtoncontrollo.Select() 
                End If 
            Next 
            Me.Refresh() 
        End Sub 

    It deselect all the buttons and select only the cliccked button. Do i make any mistake?



    Another small question:
    To redraw the "menù control" when i change the size of the form(and so replace the controls in a larger or smaller surface) i use this code.
    The problem is that if i call the Dispose method of each control only once, they won't disappear all.
    I have to call the dispose method three times, why?

    Private Sub MenùAccount_SizeChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.SizeChanged 
            For Each controllo As Account In Me.Controls 
                controllo.Dispose() 
            Next 
            For Each controllo As Account In Me.Controls 
                controllo.Dispose() 
            Next 
            For Each controllo As Account In Me.Controls 
                controllo.Dispose() 
            Next 
            Me.Refresh() 
            Me.Update() 
            DrawControls() 
        End Sub 

    Monday, July 21, 2008 10:20 AM
  • Hi DkSw.

    It appears as though you using the MouseClick event's Location property to find which control was clicked.  this is not necessary when you add teh handlers like in my previous example -- the "sender" argument is the button (or Account) that was clicked, so you just need to use the Click event instead (unless you are doing something more than is shown using the click location).

    What do you mean by only the first change the colour?  I don't see where yu are changing colors here.  However, if you left it out of the displaye code here and you are doing it within the Me.Controls loop then you are probably not using the correct expression to determine which control to change the color on.  You have to show the code for us to help you on that.  Again though, you shoudld not need to loop through controls and deselect them first if you use the sender argument.  Just use a variable to remember the LastSelectedButton and deselect it before selecting sender.  After selecting sender set it to the LastSelectedButton variable  (be sure to check if it equals Nothing because the first time through the method it might throw an exception if the variable has not been set).

    As for Dispose -- you cannot depend on Dispose to remove the control ina timely fashion from your UI.  The garbage collector does not work on any known schedule, and besides Dispose() just tells the GC the object is available for disposal and doesn;t actually do the disposal.

    You'd be better off by calling Hide() first, then Me.Controls.Remove(), then Dispose(), then Refresh() & Update().

    A better question is why are you diposing of the controls with each resize?   Is there no way to resize and reposition the individual controls without having to remake all the controls?  Besides, you have to re-add handlers when you dispose of them.  exaplin you reasoning further -- maybe there is a better solution for you.
    Monday, July 21, 2008 3:20 PM
  • Thanks a lot for the patience.
    To recognise the cliccked button i used that method, becouse before i didn't use the handlers, but it was strange that it doesn't work, the code seems to be correct.
    Anyway now i've just change the code:

    Private Sub MenùAccount_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick 
            Try 
                For Each ctrl As Account In Me.Controls 
                    ctrl.Deseleziona() 
                Next 
                TryCast(sender, Account).Seleziona() 
            Catch ex As Exception 
            End Try 
    End Sub 
    I use the try to prevent errors generated by the click of the menù surface(incorrect cast).

    Is there a way to identify the last selected control in Me.controls without the for each?

    I'm sorry for before but i translated the name of variables and function to be better understood.
    Select()[seleziona() in italian] is a function of the button which change the backcolour.
    And also Deseleziona() change the colour.

    If you need the code, this is the Account Class:
    (The last function, which is commented, is a function i've used to test the work)

    Public Class Account 
        Private _ID As Integer = 0 
        Private _BackColorSelected As ColorColor = Color.SteelBlue 
        Private _BackColorNotSelected As ColorColor = Color.WhiteSmoke 
        Private _IsSelected As Boolean = False 
     
        Public Property ID() As Integer 
            Get 
                Return _ID 
            End Get 
            Set(ByVal value As Integer) 
                _ID = value 
            End Set 
        End Property 
     
        Public Property BackColorSelected() As Color 
            Get 
                Return _BackColorSelected 
            End Get 
            Set(ByVal value As Color) 
                _BackColorSelected = value 
            End Set 
        End Property 
     
        Public Property BackColorNotSelected() As Color 
            Get 
                Return _BackColorNotSelected 
            End Get 
            Set(ByVal value As Color) 
                _BackColorNotSelected = value 
            End Set 
        End Property 
     
        Public Property IsSelected() As Boolean 
            Get 
                Return _IsSelected 
            End Get 
            Set(ByVal value As Boolean) 
                _IsSelected = value 
            End Set 
        End Property 
     
        Public Sub New(ByVal ID As Integer) 
            InitializeComponent() 
            _ID = ID 
        End Sub 
     
        Public Sub New(ByVal ID As Integer, ByVal BackColorSelected As Color, ByVal BackColorNotSelected As Color) 
            InitializeComponent() 
            _ID = ID 
            _BackColorSelected = BackColorSelected 
            _BackColorNotSelected = BackColorNotSelected 
            Me.BackColor = BackColorNotSelected 
        End Sub 
     
        Public Function Seleziona() As Integer 
            _IsSelected = True 
            BackColor = _BackColorSelected 
            Return _ID 
        End Function 
     
        Public Sub Deseleziona() 
            _IsSelected = False 
            BackColor = _BackColorNotSelected 
        End Sub 
     
        'Private Sub Account_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick, ptbCornice.MouseClick, ptbImmagine.MouseClick, lblNomeUtente.MouseClick, lblDescrizione.MouseClick, lblScadenzaPassword.MouseClick 
        '    For Each controllo As Account In Me.Parent.Controls 
        '        controllo.Deseleziona() 
        '    Next 
        '    TryCast(Me.Parent, MenùAccount).IDPulsanteSelezionato = Seleziona() 
        'End Sub 
     
    End Class 


    P.s. You are right about the resize: i put that code temporarily to test the application. In a second time i'll have changed the code to reposition the controls. But i got angry when the code didn't work. And also now with Hide, remove, dispose, refresh and update it doesn't work. !!!ò+ù@][ò!!!
    • Edited by DkSw Monday, July 21, 2008 6:10 PM Grammar Mistake
    Monday, July 21, 2008 6:08 PM
  • Try this for your MouseClick event...

        Private Sub MenùAccount_MouseClick(ByVal sender As ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick  
            'This will remember the last selected Account object so you do not need a loop.  
            Static LastSelectedControl As Account = Nothing 
            Try 
                If LastSelectedControl IsNot Nothing Then 
                    LastSelectedControl.Deseleziona()  
                End If 
                If TryCast(sender, Account).Seleziona() Then 
                    LastSelectedControl = sender  
                End If 
            Catch ex As Exception  
            End Try 
        End Sub 
     

    The MouseClick event will still work -- it's just a little less efficient because the event args are tracking more data.  Unless this is a heavy traffic control it will have a nominal effect on your custom control.

    As for the class code I have a suggestion that may save you some headaches if you're not careful.  Your IsSelected property can conflict with your selection colors and possibly cause them not to work correctly because, along with your two functions you have created two independent ways for the user to think he is changing the selection colors -- and only one works.  If I was usign this class and I saw a property called IsSelected that I could set then I woudl assume that it does exactly that.  But you have not set up your class to do that.  So you should do one of two things to fix this...

     
        Public ReadOnly Property IsSelected() As Boolean 
            Get 
                Return _IsSelected  
            End Get 
        End Property 
     

            - or -

     
        Public Property IsSelected() As Boolean 
            Get 
                Return _IsSelected  
            End Get 
            Set(ByVal value As Boolean)  
                _IsSelected = value  
                If value Then 
                    Seleziona()  
                Else 
                    Deseleziona()  
                End If 
            End Set 
        End Property 
     
     

    and this will ensure that either 1) the IsSelected property is just for reference or; 2) it actually does perform the selection change and have the backcolors change.  Note:  don;t just set the colors either...  if you add more code to the select and deselect function then you want those to be done as well, so just call the functions.

    Hopefully this will solve the annoying color issue.




    Monday, July 21, 2008 7:14 PM
  • Thank you for your advices. They were very helpfull.

    At the beginning with this code all worked fine and fast, but when i had to write the code about the doubleclick in the menù, i didn't know how to do.

    Private Sub Account_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick, ptbCornice.MouseClick, ptbImmagine.MouseClick, lblNomeUtente.MouseClick, lblDescrizione.MouseClick, lblScadenzaPassword.MouseClick  
            For Each controllo As Account In Me.Parent.Controls  
                controllo.Deseleziona()  
            Next  
            TryCast(Me.Parent, MenùAccount).IDPulsanteSelezionato = Seleziona()  
    End Sub  

    The only problem is that now seems work(it has only a little problem, but now are 12:36 Am and i'm going to bed...i will correct it tomorrow).
    But the question is:
    The previous code handles the click over the whole surface of the control(also on Labels and pictureboxs).
    Your ricoursive function should work, but over labels and picturebox doesn't work...any solution?
    Monday, July 21, 2008 10:40 PM
  • Without knowing your project it is difficult to say why the labels and pictureboxes are not working in the recursive function.  However, I'd suggest placing a Debug line in the function to find out...

     
        Private Sub AttachClickHandlers(ByVal ctrl As Control) 
             Debug.Write("Adding handler for " & ctrl.Name) 
            AddHandler ctrl.Click, AddressOf UserControl1_Click  
            For Each c As Control In ctrl.Controls  
                AttachClickHandlers(c)  
            Next 
        End Sub 


    You can see the results in either your Immediate Window or Output window in the code editor after running your project.  You should see all the names of your controls that have handlers added.  If some are missing then we need to look at what could cause that.
    Tuesday, July 22, 2008 2:06 AM
  • Ok i have an idea:

    Private Sub MenùAccount_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick   
            Try  
                If LastSelectedControl IsNot Nothing Then  
                    LastSelectedControl.Deseleziona()   
                End If  
                If TryCast(sender, Account).Seleziona() Then  
                    LastSelectedControl = sender   
                End If  
            Catch ex As Exception   
            End Try  
        End Sub 

    All the handlers are correctly added. I think that the problem is that, if you click over a label, the sender in the TryCast(sender, Account).Seleziona() function is the label...right?

    Now i'm sure. I've just check the type of the sender...if you click over a label the sender is a label(...obviously...), and so it can't be casted into an account control. How can i do?
    Tuesday, July 22, 2008 8:26 AM
  • Yes, I'd say we are confusing your Account types with just plain Controls.  Perhaps you should clarify...  Are you trying to have this MouseClick method do something only if the sender is of type Account?  But are you also trying to have handlers for all your controls?  The reason I ask is because you were trying to capture the parent control's Click so I assumed all clicks were trying to be registered.  I'm going to guess here and asy that your desire to capture all Click events was just so you could test the mouse click position to see if it matched up to one of your Account controls.  Well, if so then all you have to do is adjust the original recursive loop I wrote to only add handlers when the type is of Account...


        Private Sub AttachClickHandlers(ByVal ctrl As Control) 
            If TypeOf(ctrl) Is Account Then
                AddHandler ctrl.Click, AddressOf UserControl1_Click  
            End If
            
            For Each c As Control In ctrl.Controls  
                AttachClickHandlers(c)  
            Next 
        End Sub 

    ...and then in your MouseClick handler you can simplify knowing that only the Account objects are being handled there -- however I also added a TypeOf check for safety as well as placing it all in a Try...Catch

     
        Dim LastSelectedControl As Control = Nothing 
        Private Sub MenùAccount_MouseClick(ByVal sender As ObjectByVal e As System.Windows.Forms.MouseEventArgs)  
     
            Try 
                If TypeOf (sender) Is Account Then 
                    DirectCast(sender, Account).Seleziona()  
                    If LastSelectedControl IsNot Nothing Then 
                        LastSelectedControl.Deseleziona()  
                    End If 
                    LastSelectedControl = sender  
                End If 
     
            Catch ex As Exception 
                ' Handle this? 
            End Try 
        End Sub 
     

    ...So maybe you only ever needed a dynamically set handler for all your Account Click events.




    Tuesday, July 22, 2008 12:12 PM
  • Excuse me for the confusion.
    My idea consisted of creating a menù. The click over any button selected that button, and the double click identified the user connected to that button an opened the proper window.
    For "click over the button" i mean over the whole surface of the button.

    To be better understood, this is how my menù looks like:

    WindowAccountStyle

    With the following function the menù works how i want, but i hoped that you suggest me a better way...my method is not efficient at all !!!
    With this code you will understand what i mean:

    Private Sub MenùAccount_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick 
            Try 
               If UltimoControlloSelezionato IsNot Nothing Then 
                    UltimoControlloSelezionato.Deseleziona() 
                End If 
                If TypeOf (sender) Is Account Then 
                    TryCast(sender, Account).Seleziona() 
                    UltimoControlloSelezionato = sender 
                ElseIf TypeOf (sender) Is Label Then 
                    Dim lbl As Label = TryCast(sender, Label) 
                    Dim acc As Account = TryCast(lbl.Parent, Account) 
                    acc.Seleziona() 
                    UltimoControlloSelezionato = acc 
                ElseIf TypeOf (sender) Is PictureBox Then 
                    Dim ptb As PictureBox = TryCast(sender, PictureBox) 
                    Dim acc As Account = TryCast(ptb.Parent, Account) 
                    acc.Seleziona() 
                    UltimoControlloSelezionato = acc 
                End If 
            Catch ex As Exception 
                UltimoControlloSelezionato = Nothing 
            End Try 
        End Sub 




    Tuesday, July 22, 2008 1:37 PM
  • Oh I see now I think.  You have labels and pictures on your Buttons that you need to transalate Clicks on back down to the button itself.  Well, I suggest doing the same thing I had before with the AddHandler recursion (if you actually have nested controls othersise just do a loop through the Button's Controls collection). and add handlers for those child controls back to the Button's MouseClick handler instead of the Parent container's.  Could that work?
    Tuesday, July 22, 2008 7:15 PM
  • Yeah...i thinked it when you teached me the handlers method...and it should be the solution of the problem, but a button hasn't got any MouseClick method.
    In the begginning i created a MouseClick method which only change the color, and then the function:

    Private Sub Account_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick, ptbCornice.MouseClick, ptbImmagine.MouseClick, lblNomeUtente.MouseClick, lblDescrizione.MouseClick, lblScadenzaPassword.MouseClick   
            For Each controllo As Account In Me.Parent.Controls   
                controllo.Deseleziona()   
            Next   
            TryCast(Me.Parent, MenùAccount).IDPulsanteSelezionato = Seleziona()   
    End Sub 

    but then i deleted it becouse i don't want that a button modify something in the menù.

    I'd like that the button(and also label, pict...) MouseClick event change the color, and i'd also want that the menù recognise "a button mouseclick" and change the menù SelectedButton variable and deselect the previous button. And finally i have to manage the double click of a button(the color has to change and the menù has to recognise the button and launch a form with some data...).

    It's for that reason that i detected the menù mouseclick event and i identified the button with cursor and the button location. But it didn't work...and it's not very efficient...

    Such big troubles for such a simple problem...
    Excuse me, i'm really a ball-breaker...can you help me?
    Tuesday, July 22, 2008 8:42 PM
  • Okay -- maybe this is closer to what you are looking for.  I totally scaled down the classes here but it basically represents a UserControl that contains another custom control class that inherits from Button.  The button has a picturebox and label in it that have their Click and DoubleClick events handled through the button - so the entire button acts as one in terms of clicks and double clicks (you might want to add MouseEnter and Mouse Leave events to light up the button when the mouse is over it - your choice).  Notice that I indicate handling the Click event internally since that is only performing backcolor changes (and changes to the Isselected property which I left out this time).  However I have the DoubleClick event raise a custom event that gets handled in the parent control -- the "sender" argument in the handler would tell which button was double-clicked so its properties would say which form to open.

     
    Public Class UserControl1  
        Private WithEvents btnAccount1 As New AccountButton  
     
        Private Sub UserControl1_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load  
            Me.Controls.Add(btnAccount1)  
     
        End Sub 
     
        Private Sub btnAccount1_ActionRequested(ByVal sende As ObjectByVal e As System.EventArgs) Handles btnAccount1.ActionRequested  
            MessageBox.Show("This is where you would open a form, knowing which button was clicked due to the sender argument")  
        End Sub 
    End Class 
    Public Class AccountButton  
        Inherits Button  
     
        Private WithEvents lbl As New Label  
        Private WithEvents pb As New PictureBox  
        Public Event ActionRequested(ByVal sende As ObjectByVal e As EventArgs)  
     
        Public Sub New()  
            Me.Size = New Size(200, 48)  
            pb.Size = New Size(48, 48)  
            Me.Controls.Add(pb)  
            pb.Top = Me.Height / 2 - (pb.Height / 2)  
            pb.BackColor = Color.Red  
            lbl.Location = New Point(pb.Right + 5, 24 - (lbl.Height / 2))  
            lbl.Text = "Label" 
            Me.Controls.Add(lbl)  
        End Sub 
     
     
        Private Sub AccountButton_Click(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.Click, lbl.Click, pb.Click  
            'Handle selection/deselection here (i.e. back color changing).  
        End Sub 
     
        Private Sub AccountButton_DoubleClick(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.DoubleClick, lbl.DoubleClick, pb.DoubleClick  
            'Handle user activation here.    
            'Alternatively raise custom event to parent control...  
            RaiseEvent ActionRequested(MeNew EventArgs)  
        End Sub 
    End Class 
     



    As far as I can tell this would do what you are asking for -- just you would be adding many more buttons to the custom control and probably be doing it dynamically so the ActionRequested event handlers would be added in code when each new button is created (remember to remove the handler when the button is removed).  What do you think?
    • Edited by Dig-Boy Wednesday, July 23, 2008 2:30 AM forgot the code - D'Oh!
    • Marked as answer by DkSw Wednesday, July 23, 2008 9:23 AM
    Wednesday, July 23, 2008 2:29 AM
  • What do i think? That you are a Saint! Or maybe a genius...Are you the meeting point between Science and religion?

    I'm sorry for all the work i make you doing, but i always work with data, graphics, report, ...,  and i was a bit weak about handlers and events.

    Thanks a lot for your help.
    It is exactly what i want.

    P.s. "forgot the code - D'Oh!" ...Lol!!!
    Wednesday, July 23, 2008 9:23 AM
  • Ha!  No church would have me...  and my genious apparently is selective because I haven't figured out how to make a great salary out of this job yet.

    However, on a serious note I do get a lot of satisfaction in stepping through problem solving with others.  I tend to learn as much as I teach.  I'm glad we got to the bottom of this issue.
    Wednesday, July 23, 2008 11:41 AM