How do I get the RectangleF of a control ?

Poppa Mintin

Well-known member
Joined
Jan 4, 2018
Messages
45
Programming Experience
10+
Hi,

I have a Windows form with 24 PictureBoxes.
If the user clicks any one of these I would like to detect when the cursor exits that box.
Now of course I could make 24 subroutines, one for each picturebox, but that seems a little inelegant.

So... I'm trying to find how to get the size and location of the rectangle of the clicked box. Obviously I know which box has been clicked, but can't find how to get it's rectangle on the screen. The PictureBoxes are all in a TableLayoutPanel, and have their Dock set to full, but I can't find how to use that either.

When a box is clicked I will display a label, and then hide the label again once the cursor has left the rectangle. Then I shall face the question of what to do with the application while I'm waiting for the cursor the leave that location. Can I just use a 'While the cursor is within this rectangle' loop and put nothing in the loop ?


Poppa.
 
Note that the Bounds (and Location) property of any control will be relative to its Parent. In your case, that means that the Bounds will be relative to the TableLayoutPanel. If what you want is the Rectangle relative to the form then you can do this:
Dim rect = DirectCast(sender, Control).Bounds

rect = Me.RectangleToClient(myTableLayoutPanel.RectangleToScreen(rect))
 
MouseLeave event

Bounds property
There are 24 PictureBoxes. How can I use the MouseLeave event without a subroutine for every PictureBox?
I don't understand how I can use the Bounds property to detect the mouse leaving a PictureBox.

Thank you jmcilhinney, I can see that you've answered my question. Sadly I couldn't work out how to use 'rect'.

In the meantime I developed a subroutine to accomplish what I wanted to actually do. This is only a test piece so I've not used 'proper' variable names... Sorry.
I know which PictureBox has been clicked, so 'box' is a variable containing the PictureBox number.
VB.NET:
    Private Sub TempMessage(ByVal box As Int32)
        ' bottom, cursor.x, cursor.y, left, right, top.
        Dim b, cx, cy, l, r, t As Int32
        Dim f As Boolean = True     ' flag.
        Dim txt As String = Label1.Text 
        Dim pb As PictureBox = CType(TableLayoutPanel1.Controls _
                       ("PictureBox" & box.ToString), PictureBox)
        Label1.Text = "New message here"
        t = Me.Location.Y + pb.Location.Y    ' top
        b = t + pb.Height                    ' bottom
        l = Me.Location.X + pb.Location.X    ' left
        r = l + pb.Width                     ' right
        While f
            cx = Cursor.Position.X
            cy = Cursor.Position.Y
            If cx < l Or cx > r Then f = False
            If cy < t Or cy > b Then f = False
        End While
        Label1.Text = txt
    End Sub


Poppa.
 
There are 24 PictureBoxes. How can I use the MouseLeave event without a subroutine for every PictureBox?
Select the pictureboxes in designer and use the properties/events window to assign a common event handler, they will add to the Handles list of the handler method. If the boxes are generated you use AddHandler.
I don't understand how I can use the Bounds property to detect the mouse leaving a PictureBox.
You don't need it when using the MouseLeave event.
 
Select the pictureboxes in designer and use the properties/events window to assign a common event handler, they will add to the Handles list of the handler method. If the boxes are generated you use AddHandler.

You don't need it when using the MouseLeave event.
The pictureboxes already have an Addhandler, to detect which of them has been clicked. I didn't know I could use two Addhandlers in the same subroutine, I shall see what I can do with that. It's (only just) occurred to me that it doesn't actually matter which picturebox is being exited, it can only be the one the cursor is in.

OK, tried that...

I needed an extra global boolean to signal that the event has occurred: -
Added an extra line to the picturebox generating part of the initialisation subroutine: -
VB.NET:
Private out As Boolean = False
' and: -
AddHandler picBox.MouseLeave, AddressOf M_Leave
Added the subroutine: M_Leave
Modified the subroutine: TempMessage
VB.NET:
    Private Sub M_Leave(ByVal sender As Object, e As System.EventArgs)
        out = False              ' Ok, mouse has left the box.
    End Sub


    Private Sub TempMessage(ByVal msg As String)
        ' This sub is called when the user clicks a picturebox.
        ' The content of msg is dependent on circumstance.


        Dim txt As String = Label1.Text ' Save original text.
        Label1.Text = msg               ' Substitute temp message.
        out = True
        While out
        End While
        Label1.Text = txt               ' Restore original text.
    End Sub
I'm not really happy about using the While loop like that. I'd like some feedback on that.

Thank you guys, I don't believe I'd've got that without your help.


Poppa.
 
I don't really understand why you're using AddHandler if you're not adding the controls at run time, and there's nothing here to indicate that that is the case. The whole reason that VB.NET doesn't support VB6-style control arrays is that they are no longer needed in order to handle events for multiple controls with the same method. You simply use Ctrl+Click or Shift+Click+Drag to select multiple controls in the designer, open the Properties window, click the Events button and then double-click the event you want to handle. That will generate one method with all the controls in its Handles clause. You can also add to the Handles clause of an existing event handler by selecting it from the drop-down for an event instead of double-clicking.

Here's a simple example that will display the tabaular coordinates of a PictureBox that is clicked in a TableLayoutPanel until the cursor leaves that control:
Public Class Form1

    Private Sub PictureBox1_Click(sender As Object, e As EventArgs) Handles PictureBox4.Click,
                                                                            PictureBox3.Click,
                                                                            PictureBox2.Click,
                                                                            PictureBox1.Click
        Dim pb = DirectCast(sender, PictureBox)
        Dim columnIndex = TableLayoutPanel1.GetColumn(pb)
        Dim rowIndex = TableLayoutPanel1.GetRow(pb)

        Label1.Text = $"Current PictureBox: ({columnIndex}, {rowIndex})"
    End Sub

    Private Sub PictureBox1_MouseLeave(sender As Object, e As EventArgs) Handles PictureBox4.MouseLeave,
                                                                                 PictureBox3.MouseLeave,
                                                                                 PictureBox2.MouseLeave,
                                                                                 PictureBox1.MouseLeave
        Label1.ResetText()
    End Sub

End Class

Do you actually need any more than that?
 
Thanks John,

There are 24 pictureboxes, (Original question) so using the code above would be a little inelegant, not to mention cumbersome.

The pictureboxes are generated (post #6... 'Added an extra line... etc.) so discovering that I can have more than one Addhandler in a subroutine made the job much easier.


Poppa.
 
Last edited:
There are 24 pictureboxes, (Original question) so using the code above would be a little inelegant, not to mention cumbersome

No it wouldn't. The one and only difference would be that you would have more events in the Handles clause. Other than that, the code would be exactly the same. What exactly is inelegant or cumbersome about that?

The pictureboxes are generated

Why? Do you know at design time what you need and where? If not then creating them at run time is fair enough. The fact that you say that you have 24 PictureBoxes in a TableLayoutPanel seems to suggest that the number and location doesn't vary though, so why would you not add them in the designer? Anything you know at design time should be done in the designer.
 
I think that this: -
VB.NET:
  Private Sub M_Leave(ByVal sender As Object, e As System.EventArgs)
        out = False              ' Ok, mouse has left the box.
    End Sub

  Private Sub TempMessage(ByVal msg As String)
        Dim txt As String = Label1.Text ' Save original text.
        Label1.Text = msg               ' Substitute temp message.
        out = True
        While out
        End While
        Label1.Text = txt               ' Restore original text.
    End Sub
...is much easier to read than this: -
VB.NET:
Public Class Form1
 
    Private Sub PictureBox1_Click(sender As Object, e As EventArgs) Handles PictureBox24.Click,
                                                                            PictureBox23.Click,
                                                                            PictureBox22.Click,
                                                                            PictureBox21.Click,
                                                                            PictureBox20.Click,
                                                                            PictureBox19.Click,
                                                                            PictureBox18.Click,
                                                                            PictureBox17.Click,
                                                                            PictureBox16.Click,
                                                                            PictureBox15.Click,
                                                                            PictureBox14.Click,
                                                                            PictureBox13.Click,
                                                                            PictureBox12.Click,
                                                                            PictureBox11.Click,
                                                                            PictureBox10.Click,
                                                                            PictureBox9.Click,
                                                                            PictureBox8.Click,
                                                                            PictureBox7.Click,
                                                                            PictureBox6.Click,
                                                                            PictureBox5.Click,
                                                                            PictureBox4.Click,
                                                                            PictureBox3.Click,
                                                                            PictureBox2.Click,
                                                                            PictureBox1.Click
        Dim pb = DirectCast(sender, PictureBox)
        Dim columnIndex = TableLayoutPanel1.GetColumn(pb)
        Dim rowIndex = TableLayoutPanel1.GetRow(pb)
 
        Label1.Text = $"Current PictureBox: ({columnIndex}, {rowIndex})"
    End Sub
 
    Private Sub PictureBox1_MouseLeave(sender As Object, e As EventArgs) Handles PictureBox24.MouseLeave,
                                                                                 PictureBox23.MouseLeave,
                                                                                 PictureBox22.MouseLeave,
                                                                                 PictureBox21.MouseLeave,
                                                                                 PictureBox20.MouseLeave,
                                                                                 PictureBox19.MouseLeave,
                                                                                 PictureBox18.MouseLeave,
                                                                                 PictureBox17.MouseLeave,
                                                                                 PictureBox16.MouseLeave,
                                                                                 PictureBox15.MouseLeave,
                                                                                 PictureBox14.MouseLeave,
                                                                                 PictureBox13.MouseLeave,
                                                                                 PictureBox12.MouseLeave,
                                                                                 PictureBox11.MouseLeave,
                                                                                 PictureBox10.MouseLeave,
                                                                                 PictureBox9.MouseLeave,
                                                                                 PictureBox8.MouseLeave,
                                                                                 PictureBox7.MouseLeave,
                                                                                 PictureBox6.MouseLeave,
                                                                                 PictureBox5.MouseLeave,
                                                                                 PictureBox4.MouseLeave,
                                                                                 PictureBox3.MouseLeave,
                                                                                 PictureBox2.MouseLeave,
                                                                                 PictureBox1.MouseLeave
        Label1.ResetText()
    End Sub
 
End Class
...it's certainly easier for those of us that don't have typing skills to write. You might notice also that you don't have to scroll down to read it all!

The pictureboxes are in a TableLayoutPanel so that when the app. is running on a machine with a different screen resolution to the one it was written on, all the pictureboxes are in the same relative position, and the whole form is sized so that all the pictureboxes are all exactly square, I'm not sure I could do that any other way.

I was expecting a lot of adverse comment regarding the use of the While loop to wait for the cursor to leave the picturebox but there's been no comment of any sort.


Poppa.
 
Last edited:
it's certainly easier for those of us that don't have typing skills to write. You might notice also that you don't have to scroll down to read it all!
You don't need any typing skills. Did you miss the part where I said that you use the Properties window to generate the event handler? You actually need to type less that way. It will also not put all the events in the Handles clause on separate lines, so you don't have to scroll by default. I prefer to align them all because I find that clearer but if scrolling is a bit too onerous for you then don't do that.
The pictureboxes are in a TableLayoutPanel so that when the app. is running on a machine with a different screen resolution to the one it was written on, all the pictureboxes are in the same relative position, and the whole form is sized so that all the pictureboxes are all exactly square, I'm not sure I could do that any other way.
That's all fine but there's nothing about that that suggests that doing it all in code is a good idea. Everything you just said can be done in the designer.
I was expecting a lot of adverse comment regarding the use of the While loop to wait for the cursor to leave the picturebox but there's been no comment of any sort.
That's because handling the MouseLeave event makes that redundant, so discussing it becomes irrelevant. For future reference though, it is a very bad idea and you should never do anything like that again. If you think you need to, rest assured that there is a better way.
 
That's because handling the MouseLeave event makes that redundant, so discussing it becomes irrelevant. For future reference though, it is a very bad idea and you should never do anything like that again. If you think you need to, rest assured that there is a better way.
I don't see how the MouseLeave event makes it redundant, it's still there in the code, I'm still using it.


Poppa.
 
Did you miss the part where I said that you use the Properties window to generate the event handler?
I didn't exactly miss it, I couldn't see the relevance of doing that, I only need to call the temporary label text once in a while if the user clicks a wrong box, and the actual text is dependent upon the reason why it's a wrong box. I already handle the button click, at the time of asking the question I wanted to revert back to the original label text when the user moved the cursor.
I have an answer and I'm happy with the way I'm doing it, this is no commercial enterprise where every i has to be dotted and every t crossed, it's something I do for recreation. I don't have to do everything 'by the book' nor do I have to impress anyone by using all the bells and whistles of the language just because they're there.
There are times however when I can't achieve what I'm trying to do and I ask for help. Sometimes too, I find an answer but I'm not really happy with it... it just seems wrong, so I ask for help.


Poppa.
 
I don't see how the MouseLeave event makes it redundant, it's still there in the code, I'm still using it.

Well, you shouldn't be. I guess we just assumed that you'd know that you were supposed to get rid of it. Why would you need a loop to keep checking whether the cursor is still within the control when you know for a fact that the cursor MUST still be within the control if the MouseLeave event hasn't been raised? That's the whole point of the MouseLeave event: it is raised when the mouse leaves the control.

Consider this: let's say that you have a house that contains a number of rooms and inside each room is a button that makes an alarm sound and there are also detectors on each door that make a different alarm sound when someone walks through them. If you heard the alarm that indicated that someone was within a particular room, you would know for a fact that they were within that room, right? As long as you don't hear any further alarms, you know for a fact that they are still in that room. You don't have to keep looking in the room to see. If they had left the room then you'd have heard another alarm, so as long as you don;t hear another alarm, you know they haven't left the room. Your situation is exactly analogous. The Click and MouseLeave events are the alarms. When the Click event is raised by a control, you know for a fact that the cursor is within that control. Until the MouseLeave event is raised, you know for a fact that the cursor is still within that control. There's no need at all for a loop because it does nothing useful. I sugest that you test the code I provided to see that for yourself.
 
Ah... Ok, I get it... yes, I'll try that out. Sounds good, I'd already realised the cursor could only come out of the one box, didn't think to use that fact to actually change the label text. As I said, I wasn't happy with the While loop.


Poppa.
 
Back
Top