Link to home
Start Free TrialLog in
Avatar of Playertrac
Playertrac

asked on

Draw rectangle to select controls on panel

I have panel that has multiple pictureboxes which are added dynamically at runtime.  The pictureboxes can be moved around the panel individually or as a user defined group.  I want the user to be able to drag a mouse across the screen to "define" which pictureboxes are selected.  

Basically I want to duplicate the VB6 or VS.Net design environment with regards to controls on a design time form.  You can select multiple controls by dragging a rectangle around them then you can move the selected controls by either dragging them of using arrow keys.  Of course my program will be doing this at runtime instead of design time.

I have every part working except Drawing the rectangle and based on that rectangle determining which picture boxes to select.

MOST IMPORT (and essential for full credit)
Tell me how to draw the dotted line rectangle so that is shows on top of the controls on the form while it is being drawn (mouse drag).  It MUST be transparent so that you can see which pictureboxes will be selected.

SECONDLY
Tell me any neat tricks you know for determing which controls to select based on the rectangle drawn.

Thanks in advance!

Scott
Avatar of Howard Cantrell
Howard Cantrell
Flag of United States of America image

Sample of rubberband code...

Public Class Form1
    Inherits System.Windows.Forms.Form
    'Here is a complete screen capture demo I have written.
    'It draws a rubberband box directly on the desktop and
    'uses very fast API calls to copy the selected area to
    ' a picturebox on the form.  To select an area position
    ' your mouse where you want to start the selection and
    ' press F9.  Now move your mouse to select an area and
    ' push F9 again.  There are no mouse clicks involved in
    ' the process.  I registered F9 as a system-wide hotkey
    ' so the app doesn't need to be in focus for this to work.
    'If you set the Icon property of the NotifyIcon1 control
    ' to a valid icon then the application will minimize to
    'the tray instead of the taskbar.
 
#Region " Windows Form Designer generated code "

        Public Sub New()
            MyBase.New()

            'This call is required by the Windows Form Designer.
            InitializeComponent()

            'Add any initialization after the InitializeComponent() call

        End Sub

        'Form overrides dispose to clean up the component list.
        Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
            If disposing Then
                If Not (components Is Nothing) Then
                    components.Dispose()
                End If
            End If
            MyBase.Dispose(disposing)
        End Sub

        'Required by the Windows Form Designer
        Private components As System.ComponentModel.IContainer

        'NOTE: The following procedure is required by the Windows Form Designer
        'It can be modified using the Windows Form Designer.  
        'Do not modify it using the code editor.
        Friend WithEvents Panel1 As System.Windows.Forms.Panel
        Friend WithEvents PictureBox1 As System.Windows.Forms.PictureBox
        Friend WithEvents Label1 As System.Windows.Forms.Label
        Friend WithEvents Timer1 As System.Windows.Forms.Timer
        Friend WithEvents NotifyIcon1 As System.Windows.Forms.NotifyIcon
        <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
            Me.components = New System.ComponentModel.Container
            Me.Panel1 = New System.Windows.Forms.Panel
            Me.PictureBox1 = New System.Windows.Forms.PictureBox
            Me.Label1 = New System.Windows.Forms.Label
            Me.Timer1 = New System.Windows.Forms.Timer(Me.components)
            Me.NotifyIcon1 = New System.Windows.Forms.NotifyIcon(Me.components)
            Me.Panel1.SuspendLayout()
            Me.SuspendLayout()
            '
            'Panel1
            '
            Me.Panel1.Anchor = CType((((System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom) _
                        Or System.Windows.Forms.AnchorStyles.Left) _
                        Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
            Me.Panel1.AutoScroll = True
            Me.Panel1.BackColor = System.Drawing.Color.LightPink
            Me.Panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
            Me.Panel1.Controls.Add(Me.PictureBox1)
            Me.Panel1.Location = New System.Drawing.Point(8, 40)
            Me.Panel1.Name = "Panel1"
            Me.Panel1.Size = New System.Drawing.Size(292, 276)
            Me.Panel1.TabIndex = 1
            '
            'PictureBox1
            '
            Me.PictureBox1.Location = New System.Drawing.Point(0, 0)
            Me.PictureBox1.Name = "PictureBox1"
            Me.PictureBox1.Size = New System.Drawing.Size(184, 128)
            Me.PictureBox1.TabIndex = 0
            Me.PictureBox1.TabStop = False
            '
            'Label1
            '
            Me.Label1.Location = New System.Drawing.Point(8, 8)
            Me.Label1.Name = "Label1"
            Me.Label1.Size = New System.Drawing.Size(296, 24)
            Me.Label1.TabIndex = 2
            Me.Label1.Text = "Label1"
            Me.Label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter
            '
            'Timer1
            '
            Me.Timer1.Interval = 25
            '
            'NotifyIcon1
            '
            Me.NotifyIcon1.Text = "RubberBand Screen Capture Demo"
            '
            'Form1
            '
            Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
            Me.ClientSize = New System.Drawing.Size(312, 326)
            Me.Controls.Add(Me.Label1)
            Me.Controls.Add(Me.Panel1)
            Me.Name = "Form1"
            Me.Text = "RubberBand Screen Capture Demo"
            Me.Panel1.ResumeLayout(False)
            Me.ResumeLayout(False)

        End Sub

#End Region

        Private Enum HotKeyModifiers
            None = &H0
            Alt = &H1
            Control = &H2
            Shift = &H4
            Windows = &H8
        End Enum
        Private Const SRCCOPY = &HCC0020
        Private Declare Function CreateDC Lib "gdi32" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, ByVal lpInitData As String) As Integer
        Private Declare Function CreateCompatibleDC Lib "GDI32" (ByVal hDC As Integer) As Integer
        Private Declare Function CreateCompatibleBitmap Lib "GDI32" (ByVal hDC As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer) As Integer
        Private Declare Function SelectObject Lib "GDI32" (ByVal hDC As Integer, ByVal hObject As Integer) As Integer
        Private Declare Function BitBlt Lib "GDI32" (ByVal hDestDC As Integer, ByVal X As Integer, ByVal Y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As Integer, ByVal SrcX As Integer, ByVal SrcY As Integer, ByVal Rop As Integer) As Integer
        Private Declare Function DeleteObject Lib "GDI32" (ByVal hObj As Integer) As Integer
        Private Declare Function DeleteDC Lib "GDI32" (ByVal hDC As Integer) As Integer
        Private Declare Function RegisterHotKey Lib "user32" (ByVal hWnd As IntPtr, ByVal id As Integer, ByVal fsModifiers As Integer, ByVal vk As Integer) As Boolean
        Private Declare Function UnregisterHotKey Lib "user32" (ByVal hWnd As IntPtr, ByVal id As Integer) As Boolean

        Private Declare Function ExtractIcon Lib "shell32.dll" Alias "ExtractIconA" (ByVal hWnd As IntPtr, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As IntPtr
        Private Declare Function GetSystemDirectory Lib "kernel32.dll" Alias "GetSystemDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Integer

        Private startX As Integer
        Private startY As Integer
        Private lastX As Integer
        Private lastY As Integer
        Private curX As Integer
        Private curY As Integer
        Private boxDrawn As Boolean = False
        Private hotKeySet As Boolean = False
        Private capturing As Boolean = False

        Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
            RegisterHotKey(HotKeyModifiers.None, Keys.F9)
            If Not hotKeySet Then
                Label1.Text = "Unable to Set Hotkey F9"
                MsgBox("Unable to Set Hotkey F9")
            Else
                Label1.Text = "Press F9 to Start/Stop area selection"
            End If
        End Sub

        Private Function GetScreen(ByVal X As Integer, ByVal Y As Integer, ByVal Width As Integer, ByVal Height As Integer) As Bitmap
            Dim hDeskDC As Integer
            Dim hTempDC As Integer
            Dim hBitmap As Integer
            Dim hTempBmp As Integer
            Dim desktopArea As Bitmap

            hDeskDC = CreateDC("DISPLAY", "", "", "")
            If hDeskDC Then
                hTempDC = CreateCompatibleDC(hDeskDC)
                If hTempDC Then
                    hBitmap = CreateCompatibleBitmap(hDeskDC, Width, Height)
                    If hBitmap Then
                        hTempBmp = SelectObject(hTempDC, hBitmap)
                        BitBlt(hTempDC, 0, 0, Width, Height, hDeskDC, X, Y, SRCCOPY)
                        desktopArea = Bitmap.FromHbitmap(New IntPtr(hBitmap))
                        DeleteObject(SelectObject(hTempDC, hTempBmp))
                    End If
                    DeleteDC(hTempDC)
                End If
                DeleteDC(hDeskDC)
            End If
            Return desktopArea
        End Function

        Public Sub RegisterHotKey(ByVal mods As Integer, ByVal key As Integer)
            If hotKeySet Then
                UnregisterHotKey()
            End If
            hotKeySet = RegisterHotKey(Me.Handle, 0, mods, key)
        End Sub

        Public Sub UnRegisterHotkey()
            If hotKeySet Then
                hotKeySet = Not UnregisterHotKey(Me.Handle, 0)
            End If
        End Sub

        Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
            Const WM_HOTKEY As Integer = &H312

            Select Case m.Msg
                Case WM_HOTKEY
                    hotKeyPressed()

            End Select
            MyBase.WndProc(m)
        End Sub

        Private Sub hotKeyPressed()
            If Not capturing Then
                capturing = True
                boxDrawn = False
                startX = Cursor.Position.X
                startY = Cursor.Position.Y
                lastX = startX
                lastY = startY
                Timer1.Enabled = True
            Else
                capturing = False
                Timer1.Enabled = False
                If boxDrawn Then
                    ControlPaint.DrawReversibleFrame(New Rectangle(startX, startY, lastX - startX, lastY - startY), Color.Black, FrameStyle.Dashed)
                    PictureBox1.Image = GetScreen(Math.Min(startX, curX), Math.Min(startY, curY), Math.Abs(curX - startX), Math.Abs(curY - startY))
                    PictureBox1.Width = PictureBox1.Image.Width
                    PictureBox1.Height = PictureBox1.Image.Height
                End If
            End If
        End Sub

        Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
            If capturing Then
                curX = Cursor.Position.X
                curY = Cursor.Position.Y
                If curX <> lastX Or curY <> lastY Then
                    If Not boxDrawn Then
                        boxDrawn = True
                        ControlPaint.DrawReversibleFrame(New Rectangle(startX, startY, curX - startX, curY - startY), Color.Black, FrameStyle.Dashed)
                    Else
                        ControlPaint.DrawReversibleFrame(New Rectangle(startX, startY, lastX - startX, lastY - startY), Color.Black, FrameStyle.Dashed)
                        ControlPaint.DrawReversibleFrame(New Rectangle(startX, startY, curX - startX, curY - startY), Color.Black, FrameStyle.Dashed)
                    End If
                    lastX = curX
                    lastY = curY
                End If
            End If
        End Sub

        Private Sub Form1_SizeChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.SizeChanged
            If Me.WindowState = FormWindowState.Minimized Then
                If Not (NotifyIcon1.Icon Is Nothing) Then
                    Me.Hide()
                    NotifyIcon1.Visible = True
                End If
            End If
        End Sub

        Private Sub NotifyIcon1_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles NotifyIcon1.DoubleClick
            NotifyIcon1.Visible = False
            Me.Show()
            Me.WindowState = FormWindowState.Normal
        End Sub

        Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
            UnregisterHotKey()
        End Sub

    End Class
Avatar of Playertrac
Playertrac

ASKER

planocz

Thanks for the quick reply.  

I would rather not use an API call if possible.  If I'm not mistaken the program you outlined lets the user draw a rectangle anywhere on the screen instead of limiting it to the confines of the panel.  Also, it doesn't work using the mouse drag which is the way I need my program to work (Just like in VS.Net).  Lastly it forces the program to determine the panel's position on the screen and convert the cordinates to corispond with those of the panel so that it can determine which controls to select inside the panel.  These issues could probably be programmed around but I am hoping for a cleaner solution which better meets the criteria outlined above.  

Thanks again!

Scott
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
planocz

Excellent!

That is what I was looking for.  There is only one more small item that would make it Perfect.

To work exactly like VS.net the code needs to select any picturebox that even touches any side of the rubberband not only those controls that are completely inside the rubberband.

Could you post that one small change if you get a chance?

Thanks a bunch!

Scott
Just change the If statement from And to Or:

    If selectionArea.Contains(c.Left, c.Top) Or selectionArea.Contains(c.Left + c.Width, c.Top + c.Height) Then
        MsgBox(c.Name & " has been selected")
    End If

~IM
Scratch that last comment...

Two changes:

1) Normalize the selection rectangle so it works no matter which direction you drag in.
2) Use IntersectsWith() instead of Contains().

    Private Sub Panel1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Panel1.MouseUp
        If rubberbanding And e.Button = MouseButtons.Left Then
            Cursor.Clip = New Rectangle(0, 0, Screen.GetBounds(New Point(0, 0)).Width, Screen.GetBounds(New Point(0, 0)).Height)
            rubberbanding = False
            If firstBoxDrawn Then
                Dim r As Rectangle = New Rectangle(Math.Min(startX, lastX), Math.Min(startY, lastY), Math.Abs(lastX - startX), Math.Abs(lastY - startY))
                ControlPaint.DrawReversibleFrame(r, Color.Black, FrameStyle.Dashed)
                r = Panel1.RectangleToClient(r)
                RectangleSelected(r)
            End If
        End If
    End Sub

    Private Sub RectangleSelected(ByVal selectionArea As Rectangle)
        Dim c As Control
        For Each c In Panel1.Controls
            If selectionArea.IntersectsWith(New Rectangle(c.Left, c.Top, c.Width, c.Height)) Then
                MsgBox(c.Name & " has been selected")
            End If
        Next
    End Sub
Thank you!

I'll give that a try!

Scott
Works Perfect!

Thanks for your help.

Scott