|
|
I love this article. I have modified it little to allow smooth drag.
I am aware that this post is approx. 2 years old...but still I hope it may help someone.
Once again thanks to Jesse
Public Class ResizeableControl
Private WithEvents mControl As Control
Private mMouseDown As Boolean = False
Private mEdge As EdgeEnum = EdgeEnum.None
Private mWidth As Integer = 4
Private mOutlineDrawn As Boolean = False
Private beginX, beginY As Integer
Public Enum EdgeEnum
None = 0
Right = 1
Left = 2
Top = 4
Bottom = 8
TopLeft = 16
'added
All = TopLeft Or Left Or Right Or Top Or Bottom
ResizeAnchorTopLeft = Right Or Bottom
OnlyMove = TopLeft
'end added
End Enum
Public Property AllowEdges() As EdgeEnum
Get
Return _AllowEdges
End Get
Set(ByVal value As EdgeEnum)
_AllowEdges = value
End Set
End Property
Friend _AllowEdges As EdgeEnum = EdgeEnum.All 'Default Behavior
Public Property HighlightColor() As Drawing.Color
Get
Return _HighlightColor
End Get
Set(ByVal value As Drawing.Color)
_HighlightColor = value
End Set
End Property
Private _HighlightColor As Drawing.Color = Color.Fuchsia
Public Sub New(ByVal Control As Control)
mControl = Control
End Sub
'added
Public Sub New(ByVal Control As Control, ByVal AllowedEdges As EdgeEnum)
mControl = Control
_AllowEdges = AllowedEdges
End Sub
'end added
Private Sub mControl_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left Then mMouseDown = True
beginX = e.X
beginY = e.Y
End Sub
Private Sub mControl_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseUp
mMouseDown = False
End Sub
'added
Private mpLast_Location As Point = New Point(0, 0)
'end added
Private Sub mControl_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseMove
Dim c As Control = CType(sender, Control)
Dim g As Graphics = c.CreateGraphics
Dim B As New System.Drawing.SolidBrush(_HighlightColor)
'added 'Moved from Select Case mEdge : Case EdgeEnum.None
If mOutlineDrawn Then
c.Refresh()
mOutlineDrawn = False
End If
'end added
Select Case mEdge
Case EdgeEnum.TopLeft
g.FillRectangle(B, 0, 0, mWidth * 4, mWidth * 4)
mOutlineDrawn = True
Case EdgeEnum.Left
g.FillRectangle(B, 0, 0, mWidth, c.Height)
mOutlineDrawn = True
Case EdgeEnum.Right
g.FillRectangle(B, c.Width - mWidth, 0, c.Width, c.Height)
mOutlineDrawn = True
Case EdgeEnum.Top
g.FillRectangle(B, 0, 0, c.Width, mWidth)
mOutlineDrawn = True
Case EdgeEnum.Bottom
g.FillRectangle(B, 0, c.Height - mWidth, c.Width, mWidth)
mOutlineDrawn = True
'Case EdgeEnum.None 'Moved before Select Case
' If mOutlineDrawn Then
' c.Refresh()
' mOutlineDrawn = False
' End If
End Select
If mMouseDown And mEdge <> EdgeEnum.None Then
c.SuspendLayout()
Select Case mEdge
Case EdgeEnum.TopLeft
c.Location = New Point(c.Location.X + e.X - beginX, c.Location.Y + e.Y - beginY)
c.Parent.Refresh()
''added
'Dim iX_Delta As Integer = e.X
'Dim iY_Delta As Integer = e.Y
'If Not (mpLast_Location = New Point(0, 0)) Then
' iX_Delta -= mpLast_Location.X
' iY_Delta -= mpLast_Location.Y
'End If
'c.SetBounds(c.Left + iX_Delta, c.Top + iY_Delta, c.Width, c.Height)
''end added
''c.SetBounds(c.Left + e.X, c.Top + e.Y, c.Width, c.Height) 'Original
'RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Left
c.SetBounds(c.Left + e.X, c.Top, c.Width - e.X, c.Height)
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Right
c.SetBounds(c.Left, c.Top, c.Width - (c.Width - e.X), c.Height)
RaiseEvent ResizeOccurred(c)
'added
mpLast_Location = e.Location
'end added
Case EdgeEnum.Top
c.SetBounds(c.Left, c.Top + e.Y, c.Width, c.Height - e.Y)
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Bottom
c.SetBounds(c.Left, c.Top, c.Width, c.Height - (c.Height - e.Y))
RaiseEvent ResizeOccurred(c)
End Select
c.ResumeLayout()
Else
Select Case True
Case e.X <= (mWidth * 4) And e.Y <= (mWidth * 4) 'top left corner
c.Cursor = Cursors.SizeAll
mEdge = EdgeEnum.TopLeft
Case e.X <= mWidth 'left edge
c.Cursor = Cursors.VSplit
mEdge = EdgeEnum.Left
Case e.X > c.Width - (mWidth + 1) 'right edge
c.Cursor = Cursors.VSplit
mEdge = EdgeEnum.Right
Case e.Y <= mWidth 'top edge
c.Cursor = Cursors.HSplit
mEdge = EdgeEnum.Top
Case e.Y > c.Height - (mWidth + 1) 'bottom edge
c.Cursor = Cursors.HSplit
mEdge = EdgeEnum.Bottom
Case Else 'no edge
c.Cursor = Cursors.Default
mEdge = EdgeEnum.None
End Select
mEdge = mEdge And _AllowEdges
If mEdge = EdgeEnum.None Then c.Cursor = Cursors.Default
End If
End Sub
Private Sub mControl_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles mControl.MouseLeave
Dim c As Control = CType(sender, Control)
c.Cursor = Cursors.Default
mEdge = EdgeEnum.None
c.Refresh()
End Sub
Public Event ResizeOccurred(ByRef c As System.Windows.Forms.Control)
End Class
|
|
|
|
|
*Fixed the line drawing problem
*Fixed the TopLeft (movement error)
*Added a new Constructor that allow to set the AllowedEdges
*Added Developer-Friendly Allow-Edges Enums: All, ResizeAnchorTopLeft, OnlyMove
Public Class ResizeableControl
Private WithEvents mControl As Control
Private mMouseDown As Boolean = False
Private mEdge As EdgeEnum = EdgeEnum.None
Private mWidth As Integer = 4
Private mOutlineDrawn As Boolean = False
Public Enum EdgeEnum
None = 0
Right = 1
Left = 2
Top = 4
Bottom = 8
TopLeft = 16
'added
All = TopLeft Or Left Or Right Or Top Or Bottom
ResizeAnchorTopLeft = Right Or Bottom
OnlyMove = TopLeft
'end added
End Enum
Public Property AllowEdges() As EdgeEnum
Get
Return _AllowEdges
End Get
Set(ByVal value As EdgeEnum)
_AllowEdges = value
End Set
End Property
Friend _AllowEdges As EdgeEnum = EdgeEnum.All 'Default Behavior
Public Property HighlightColor() As Drawing.Color
Get
Return _HighlightColor
End Get
Set(ByVal value As Drawing.Color)
_HighlightColor = value
End Set
End Property
Private _HighlightColor As Drawing.Color = Color.Fuchsia
Public Sub New(ByVal Control As Control)
mControl = Control
End Sub
'added
Public Sub New(ByVal Control As Control, ByVal AllowedEdges As EdgeEnum)
mControl = Control
_AllowEdges = AllowedEdges
End Sub
'end added
Private Sub mControl_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left Then mMouseDown = True
End Sub
Private Sub mControl_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseUp
mMouseDown = False
End Sub
'added
Private mpLast_Location As Point = New Point(0, 0)
'end added
Private Sub mControl_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseMove
Dim c As Control = CType(sender, Control)
Dim g As Graphics = c.CreateGraphics
Dim B As New System.Drawing.SolidBrush(_HighlightColor)
'added 'Moved from Select Case mEdge : Case EdgeEnum.None
If mOutlineDrawn Then
c.Refresh()
mOutlineDrawn = False
End If
'end added
Select Case mEdge
Case EdgeEnum.TopLeft
g.FillRectangle(B, 0, 0, mWidth * 4, mWidth * 4)
mOutlineDrawn = True
Case EdgeEnum.Left
g.FillRectangle(B, 0, 0, mWidth, c.Height)
mOutlineDrawn = True
Case EdgeEnum.Right
g.FillRectangle(B, c.Width - mWidth, 0, c.Width, c.Height)
mOutlineDrawn = True
Case EdgeEnum.Top
g.FillRectangle(B, 0, 0, c.Width, mWidth)
mOutlineDrawn = True
Case EdgeEnum.Bottom
g.FillRectangle(B, 0, c.Height - mWidth, c.Width, mWidth)
mOutlineDrawn = True
'Case EdgeEnum.None 'Moved before Select Case
' If mOutlineDrawn Then
' c.Refresh()
' mOutlineDrawn = False
' End If
End Select
If mMouseDown And mEdge <> EdgeEnum.None Then
c.SuspendLayout()
Select Case mEdge
Case EdgeEnum.TopLeft
'added
Dim iX_Delta As Integer = e.X
Dim iY_Delta As Integer = e.Y
If Not (mpLast_Location = New Point(0, 0)) Then
iX_Delta -= mpLast_Location.X
iY_Delta -= mpLast_Location.Y
End If
c.SetBounds(c.Left + iX_Delta, c.Top + iY_Delta, c.Width, c.Height)
'end added
'c.SetBounds(c.Left + e.X, c.Top + e.Y, c.Width, c.Height) 'Original
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Left
c.SetBounds(c.Left + e.X, c.Top, c.Width - e.X, c.Height)
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Right
c.SetBounds(c.Left, c.Top, c.Width - (c.Width - e.X), c.Height)
RaiseEvent ResizeOccurred(c)
'added
mpLast_Location = e.Location
'end added
Case EdgeEnum.Top
c.SetBounds(c.Left, c.Top + e.Y, c.Width, c.Height - e.Y)
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Bottom
c.SetBounds(c.Left, c.Top, c.Width, c.Height - (c.Height - e.Y))
RaiseEvent ResizeOccurred(c)
End Select
c.ResumeLayout()
Else
Select Case True
Case e.X <= (mWidth * 4) And e.Y <= (mWidth * 4) 'top left corner
c.Cursor = Cursors.SizeAll
mEdge = EdgeEnum.TopLeft
Case e.X <= mWidth 'left edge
c.Cursor = Cursors.VSplit
mEdge = EdgeEnum.Left
Case e.X > c.Width - (mWidth + 1) 'right edge
c.Cursor = Cursors.VSplit
mEdge = EdgeEnum.Right
Case e.Y <= mWidth 'top edge
c.Cursor = Cursors.HSplit
mEdge = EdgeEnum.Top
Case e.Y > c.Height - (mWidth + 1) 'bottom edge
c.Cursor = Cursors.HSplit
mEdge = EdgeEnum.Bottom
Case Else 'no edge
c.Cursor = Cursors.Default
mEdge = EdgeEnum.None
End Select
mEdge = mEdge And _AllowEdges
If mEdge = EdgeEnum.None Then c.Cursor = Cursors.Default
End If
End Sub
Private Sub mControl_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles mControl.MouseLeave
Dim c As Control = CType(sender, Control)
c.Cursor = Cursors.Default
mEdge = EdgeEnum.None
c.Refresh()
End Sub
Public Event ResizeOccurred(ByRef c As System.Windows.Forms.Control)
End Class
|
|
|
|
|
Been looking for something like this for a while. I code in C++/CLR so I had to do some translating from VB to that language. But I got it working. Thanks
|
|
|
|
|
I've come across what I think is a slight problem/snag... If you try to set a resizable control to a control with child controls, getting the mouse on the border is tricky. For example, a Panel control that contains an embedded TextBox. The TextBox's bottom is at the bottom of the Panel (or it's docked to the bottom). When the mouse is over the bottom of the Panel, it'll need to be on the 1-pixel wide bottom border of the Panel for the highlighting/sizing to work. Otherwise the mouse is over the TextBox, the resizing events don't fire, and the resizing cursor doesn't appear.
Does this make sense? Not sure what, if any, workaround there is for this.
Steve
|
|
|
|
|
This class is way cool. It worked perfectly for what I needed -- a series of Infragistics UltraExpandableGroupBox controls docked TOP one on top of another (basically forming a vertical 'table' of group boxes. I wanted each of the group boxes to be resizable.
I did make a few tweaks (new code below). I added AllowEdges and HighlightColor properties. I also added values to the enumeration. This, along with the AllowEdges property enable you to specify which edges can be resized. I needed the bottom only, so this worked well. The HighlightColor property lets you set what color the edge highlight is -- fuschia just didn't do it for me!
Finally, I added a ResizeOccurred event that is thrown during the resizing. I handle it to make sure the control isn't resized past a minimum threshold.
I realize that my implementation of these "extras" is pretty quick and simplistic. Just wanted to share these as I thought you might want to add them (or something like them) to your original class.
Steve
Public Class Form1
Dim rc As ResizeableControl
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
rc = New ResizeableControl(pbDemo)
rc.HighlightColor = Color.Orange
rc.AllowEdges = ResizeableControl.EdgeEnum.Bottom
AddHandler rc.ResizeOccurred, AddressOf HandleResize
End Sub
Private Sub HandleResize(ByRef sender As Windows.Forms.Control)
If sender.Height < 50 Then sender.Height = 50
End Sub
Public Class ResizeableControl
Private WithEvents mControl As Control
Private mMouseDown As Boolean = False
Private mEdge As EdgeEnum = EdgeEnum.None
Private mWidth As Integer = 4
Private mOutlineDrawn As Boolean = False
Public Property AllowEdges() As EdgeEnum
Get
Return _AllowEdges
End Get
Set(ByVal value As EdgeEnum)
_AllowEdges = value
End Set
End Property
Friend _AllowEdges As EdgeEnum = EdgeEnum.Bottom Or EdgeEnum.Left Or EdgeEnum.Right Or EdgeEnum.Top Or EdgeEnum.TopLeft
Public Property HighlightColor() As Drawing.Color
Get
Return _HighlightColor
End Get
Set(ByVal value As Drawing.Color)
_HighlightColor = value
End Set
End Property
Private _HighlightColor As Drawing.Color = Color.Fuchsia
Public Enum EdgeEnum
None = 0
Right = 1
Left = 2
Top = 4
Bottom = 8
TopLeft = 16
End Enum
Public Sub New(ByVal Control As Control)
mControl = Control
End Sub
Private Sub mControl_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseDown
If e.Button = Windows.Forms.MouseButtons.Left Then
mMouseDown = True
End If
End Sub
Private Sub mControl_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseUp
mMouseDown = False
End Sub
Private Sub mControl_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles mControl.MouseMove
Dim c As Control = CType(sender, Control)
Dim g As Graphics = c.CreateGraphics
Dim B As New System.Drawing.SolidBrush(_HighlightColor)
Select Case mEdge
Case EdgeEnum.TopLeft
g.FillRectangle(B, 0, 0, mWidth * 4, mWidth * 4)
mOutlineDrawn = True
Case EdgeEnum.Left
g.FillRectangle(B, 0, 0, mWidth, c.Height)
mOutlineDrawn = True
Case EdgeEnum.Right
g.FillRectangle(B, c.Width - mWidth, 0, c.Width, c.Height)
mOutlineDrawn = True
Case EdgeEnum.Top
g.FillRectangle(B, 0, 0, c.Width, mWidth)
mOutlineDrawn = True
Case EdgeEnum.Bottom
g.FillRectangle(B, 0, c.Height - mWidth, c.Width, mWidth)
mOutlineDrawn = True
Case EdgeEnum.None
If mOutlineDrawn Then
c.Refresh()
mOutlineDrawn = False
End If
End Select
If mMouseDown And mEdge <> EdgeEnum.None Then
c.SuspendLayout()
Select Case mEdge
Case EdgeEnum.TopLeft
c.SetBounds(c.Left + e.X, c.Top + e.Y, c.Width, c.Height)
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Left
c.SetBounds(c.Left + e.X, c.Top, c.Width - e.X, c.Height)
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Right
c.SetBounds(c.Left, c.Top, c.Width - (c.Width - e.X), c.Height)
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Top
c.SetBounds(c.Left, c.Top + e.Y, c.Width, c.Height - e.Y)
RaiseEvent ResizeOccurred(c)
Case EdgeEnum.Bottom
c.SetBounds(c.Left, c.Top, c.Width, c.Height - (c.Height - e.Y))
RaiseEvent ResizeOccurred(c)
End Select
c.ResumeLayout()
Else
Select Case True
Case e.X <= (mWidth * 4) And e.Y <= (mWidth * 4) 'top left corner
c.Cursor = Cursors.SizeAll
mEdge = EdgeEnum.TopLeft
Case e.X <= mWidth 'left edge
c.Cursor = Cursors.VSplit
mEdge = EdgeEnum.Left
Case e.X > c.Width - (mWidth + 1) 'right edge
c.Cursor = Cursors.VSplit
mEdge = EdgeEnum.Right
Case e.Y <= mWidth 'top edge
c.Cursor = Cursors.HSplit
mEdge = EdgeEnum.Top
Case e.Y > c.Height - (mWidth + 1) 'bottom edge
c.Cursor = Cursors.HSplit
mEdge = EdgeEnum.Bottom
Case Else 'no edge
c.Cursor = Cursors.Default
mEdge = EdgeEnum.None
End Select
mEdge = mEdge And _AllowEdges
If mEdge = EdgeEnum.None Then c.Cursor = Cursors.Default
End If
End Sub
Private Sub mControl_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles mControl.MouseLeave
Dim c As Control = CType(sender, Control)
mEdge = EdgeEnum.None
c.Refresh()
End Sub
Public Event ResizeOccurred(ByRef c As System.Windows.Forms.Control)
End Class
End Class
|
|
|
|
|
Hi all,
This is such a good control to have. Thanks Jesse for this. I have just made a slight amendment so that when you move the control around, it doesn't put the corner of the control under the mouse control.
What you need:
1) Need private property, e.g. private Point mpLast_Location = new Point(0,0);
2) Modify mControl_MouseMove as follow:
...
if (mMouseDown & mEdge != EdgeEnum.None)
{
c.SuspendLayout();
switch (mEdge)
{
case EdgeEnum.TopLeft:
int iX_Delta = e.X;
int iY_Delta = e.Y;
if (mpLast_Location != new Point(0, 0))
{
iX_Delta -= mpLast_Location.X;
iY_Delta -= mpLast_Location.Y;
}
c.SetBounds(c.Left + iX_Delta, c.Top + iY_Delta, c.Width, c.Height);
break;
case EdgeEnum.Left:
c.SetBounds(c.Left + e.X, c.Top, c.Width - e.X, c.Height);
break;
case EdgeEnum.Right:
...
mpLast_Location = e.Location;
}
} //End of function
Hope this helps
Alan
|
|
|
|
|
|
Hey hey,
The control is quite impressive..
Really a very good work...it should solve many of my problems..
|
|
|
|
|
Thanks, good mathematical work was done.
That is good solution for PictureBox (and others non-interactive controls) but what about finishing this one for a button or grids?
As you are using Decorator[^] pattern you have to implement all virtual methods of Control class in your class. That is easy but some routine work...
P.S. There is 'virtual transparent proxy' technology that can help (using Reflection, of course) in general: to 'implement' all of not implemented methods automatically.
Does anybody know some .Net articles about that?
|
|
|
|
|
AikinX wrote: That is good solution for PictureBox (and others non-interactive controls) but what about finishing this one for a button or grids?
This code works fine for a button or grid (DataGridView was specifically tested). Add a button to the form called cmdMyButton and a grid called grdMyGrid, and then simply change this line:
rc = New ResizeableControl(pbDemo)
To this:
rc = New ResizeableControl(cmdMyButton)
Or this:
rc = New ResizeableControl(grdMyGrid)
This code should work for any control that inherits from "Control", with some exceptions for controls that have special functionality around sizing (such as the ListBox, which is problematic).
It is not the intent of this solution for the programmer to use the "rc" variable to access the control. The programmer would still use the control itself just as always. Of course, you can use this as a starting point and enhance the implementation any way you'd like! That was the intent.
Regards,
Jesse Chunn
Standard answer: It can be done in two weeks, provided nothing unexpected happens.
|
|
|
|
|
Don't you use WM_NCHITTEST because of possible porting to Mono? Otherwise, it can be not so bad way to use the system message instead of manual resizing, because there are no any artefacts with redrawing and the message gives more capabilities.
|
|
|
|
|
This thing is awesome.
If you don't do it, then I will turn it into an ExtenerProvider. The result would mean that instead of the few lines of code, you can just drag the provider on a form and provide the functionality.
@Rudolf some good suggestions, but he says they would take a lot of work. I don't think they would take very much work. But they do need to be done.
|
|
|
|
|
Nice solution. I also suggest you to look at the DX XtraLayoutControl.![Big Grin | :-D](https://codeproject.global.ssl.fastly.net/script/Forums/Images/smiley_biggrin.gif)
|
|
|
|
|
Of course your users would like to save the preferred control sizes in the registry or in a settings file. You also should provide for a mechanism to restore the default size and postion (Some users make a mess of it). I think this may be a lot of extra work to get this really foll proof.
Rudolf Heijink
|
|
|
|
|
I'll give it a five.
A few suggestions for next version:
1) Resizing both width and height at the same time by dragging the corners.
2) Aspect ration locking
3) Max and min size
Cool code!
Cheers,
Johnny J.
|
|
|
|
|