• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 898
  • Last Modified:

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
0
Playertrac
Asked:
Playertrac
  • 4
  • 3
1 Solution
 
planoczCommented:
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
0
 
PlayertracAuthor Commented:
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
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
You like my code planocz?    ;)
http://www.experts-exchange.com/Programming/Programming_Languages/Dot_Net/VB_DOT_NET/Q_21076907.html

Here is an example of how to rubberband in a panel and determine which controls have been selected in it:

Public Class Form1
    Inherits System.Windows.Forms.Form

#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 PictureBox2 As System.Windows.Forms.PictureBox
    Friend WithEvents Panel2 As System.Windows.Forms.Panel
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.Panel1 = New System.Windows.Forms.Panel
        Me.PictureBox1 = New System.Windows.Forms.PictureBox
        Me.PictureBox2 = New System.Windows.Forms.PictureBox
        Me.Panel2 = New System.Windows.Forms.Panel
        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.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
        Me.Panel1.Controls.Add(Me.Panel2)
        Me.Panel1.Controls.Add(Me.PictureBox2)
        Me.Panel1.Controls.Add(Me.PictureBox1)
        Me.Panel1.Location = New System.Drawing.Point(8, 8)
        Me.Panel1.Name = "Panel1"
        Me.Panel1.Size = New System.Drawing.Size(552, 360)
        Me.Panel1.TabIndex = 0
        '
        'PictureBox1
        '
        Me.PictureBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
        Me.PictureBox1.Location = New System.Drawing.Point(72, 88)
        Me.PictureBox1.Name = "PictureBox1"
        Me.PictureBox1.Size = New System.Drawing.Size(72, 56)
        Me.PictureBox1.TabIndex = 0
        Me.PictureBox1.TabStop = False
        '
        'PictureBox2
        '
        Me.PictureBox2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
        Me.PictureBox2.Location = New System.Drawing.Point(192, 200)
        Me.PictureBox2.Name = "PictureBox2"
        Me.PictureBox2.Size = New System.Drawing.Size(56, 48)
        Me.PictureBox2.TabIndex = 1
        Me.PictureBox2.TabStop = False
        '
        'Panel2
        '
        Me.Panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
        Me.Panel2.Location = New System.Drawing.Point(312, 152)
        Me.Panel2.Name = "Panel2"
        Me.Panel2.Size = New System.Drawing.Size(80, 56)
        Me.Panel2.TabIndex = 2
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(568, 374)
        Me.Controls.Add(Me.Panel1)
        Me.Name = "Form1"
        Me.Text = "RubberBanding in a PictureBox"
        Me.Panel1.ResumeLayout(False)
        Me.ResumeLayout(False)

    End Sub

#End Region

    Private startX As Integer
    Private startY As Integer
    Private lastX As Integer
    Private lastY As Integer
    Private rubberbanding As Boolean = False
    Private firstBoxDrawn As Boolean = False

    Private Sub Panel1_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Panel1.MouseDown
        If e.Button = MouseButtons.Left Then
            rubberbanding = True
            firstBoxDrawn = False
            Dim p As Point = Panel1.PointToScreen(New Point(e.X, e.Y))
            startX = p.X
            startY = p.Y
            Cursor.Clip = Panel1.RectangleToScreen(New Rectangle(0, 0, Panel1.Width, Panel1.Height))
        End If
    End Sub

    Private Sub Form1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress
        If rubberbanding And e.KeyChar = Microsoft.VisualBasic.ChrW(27) Then
            rubberbanding = False
            Cursor.Clip = New Rectangle(0, 0, Screen.GetBounds(New Point(0, 0)).Width, Screen.GetBounds(New Point(0, 0)).Height)
            If firstBoxDrawn Then
                ControlPaint.DrawReversibleFrame(New Rectangle(startX, startY, lastX - startX, lastY - startY), Color.Black, FrameStyle.Dashed)
            End If
        End If
    End Sub

    Private Sub Panel1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Panel1.MouseMove
        If rubberbanding Then
            Dim p As Point = Panel1.PointToScreen(New Point(e.X, e.Y))
            If Not firstBoxDrawn Then
                firstBoxDrawn = True
                ControlPaint.DrawReversibleFrame(New Rectangle(startX, startY, p.X - startX, p.Y - 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, p.X - startX, p.Y - startY), Color.Black, FrameStyle.Dashed)
            End If
            lastX = p.X
            lastY = p.Y
        End If
    End Sub

    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(startX, startY, lastX - startX, 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.Contains(c.Left, c.Top) And selectionArea.Contains(c.Left + c.Width, c.Top + c.Height) Then
                MsgBox(c.Name & " has been selected")
            End If
        Next
    End Sub

End Class
0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
PlayertracAuthor Commented:
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
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
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
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
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
0
 
PlayertracAuthor Commented:
Thank you!

I'll give that a try!

Scott
0
 
PlayertracAuthor Commented:
Works Perfect!

Thanks for your help.

Scott
0

Featured Post

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

  • 4
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now