-
1 Attachment(s)
comment control
Hi,
I´m working on a comment control. I mean a control, that has an input area for text and images and a line or an arrow for pointing to something. It should a bit look like the comment control in Adobe Acrobat.
My first idea is to put four standard controls together:
1) a RichTextBox to hold text and images
2) a Panel to hold the RichTextBox and some GrabRectangles around it (the Panel needs to be a bit greater than the RTB for the GrabRectangles)
3) a little Panel (3x3 or 4x4 Pixel) to hold another GrabRectangle for placing and resizing the line (the arrow)
4) a LineShape for pointing (its startpoint on the RTB and endpoint on the little Panel)
I have added a drawing.
My question is: what can I use to put these controls together? Another Panel would probably do a good job. But it would cover a lot of additional area around the contained controls and hide other controls, so that they don´t get mouse clicks. Any other idea?
Is there a better way to create such a control?
Links and articles would also be helpful.
Greetings,
TheTree
-
Re: comment control
Everything is doable except that little panel and an arrow.
I can't think of any way to do that save only to draw them on the parent control. Or it will require having two separate controls that somehow are aware of each other and one of them draws an arrow to another one. I'm practically sure that it can't be done with a single control.
-
Re: comment control
Look at the ControlPaint class for drawing the grab handles, etc. As for the arrow and extranal "panel", you can't really do that with a single control unless they are within the bounds of the control, which would undoubtedly cause problems in some situations. You might be better off to use a provider component, like the ToolTip.
-
Re: comment control
I did a quick test, and I think it is possible, but you may get into hard-to-solve designer problems.
What I did was this:
I created two controls that inherit Panel (MainPanel and LittlePanel). The MainPanel control has a property LittlePanel. This keeps the reference to the LittlePanel, so we can draw an arrow to it.
In the MainPanel, I override the OnParentChanged event. In that event, I hook up the Paint event of the Parent control, so we can paint on the parent when it wants to be repainted (this is where we draw the arrow). IN addition to that, I also create a new LittlePanel instance. I don't create that instance in the usual way (Dim p As New LittlePanel) though. Instead, I use the IDesignerHost service and its CreateComponent method. This way, the form (or parent control really) is aware of the little panel in the designer, and you can move it and set its properties as usual.
http://i42.tinypic.com/2zoi5xd.png
This is the code...
MainPanel:
Code:
Imports System.ComponentModel.Design
Public Class MainPanel
Inherits Panel
' Constructor, called when new instance of this panel is created
Public Sub New()
Me.BackColor = Color.Red
Me.BorderStyle = Windows.Forms.BorderStyle.FixedSingle
Me.CreateLittlePanel()
End Sub
' This method creates a new LittlePanel control using the DesignerHost service
' By using the DesignerHost service, the control is editable during design-time too.
Private Sub CreateLittlePanel()
Dim designerHost = TryCast(Me.GetService(GetType(IDesignerHost)), IDesignerHost)
If designerHost IsNot Nothing Then
Dim panel = TryCast(designerHost.CreateComponent(GetType(LittlePanel)), LittlePanel)
If panel IsNot Nothing Then
Me.LittlePanel = panel
If Me.Parent IsNot Nothing Then
Me.Parent.Controls.Add(panel)
Me.Parent.Invalidate()
End If
End If
End If
End Sub
' This property keeps a reference to the little panel it needs to paint an arrow to
Private _LittlePanel As LittlePanel
Public Property LittlePanel() As LittlePanel
Get
Return _LittlePanel
End Get
Set(ByVal value As LittlePanel)
_LittlePanel = value
End Set
End Property
' This paints the grab handles
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
Dim topLeft As New Rectangle(e.ClipRectangle.X, e.ClipRectangle.Y, 6, 6)
Dim topRight As New Rectangle(e.ClipRectangle.Right - 6, e.ClipRectangle.Y, 6, 6)
Dim bottomLeft As New Rectangle(e.ClipRectangle.X, e.ClipRectangle.Bottom - 6, 6, 6)
Dim bottomRight As New Rectangle(e.ClipRectangle.Right - 6, e.ClipRectangle.Bottom - 6, 6, 6)
ControlPaint.DrawGrabHandle(e.Graphics, topLeft, True, True)
ControlPaint.DrawGrabHandle(e.Graphics, topRight, True, True)
ControlPaint.DrawGrabHandle(e.Graphics, bottomLeft, True, True)
ControlPaint.DrawGrabHandle(e.Graphics, bottomRight, True, True)
End Sub
' When the parent changes, hook up its Paint event so we can draw on it, and add a new LittlePanel
Protected Overrides Sub OnParentChanged(ByVal e As System.EventArgs)
MyBase.OnParentChanged(e)
If Me.Parent IsNot Nothing Then
AddHandler Me.Parent.Paint, AddressOf Parent_Paint
'If Me.LittlePanel IsNot Nothing Then
' If Not Me.Parent.Controls.Contains(Me.LittlePanel) Then
' Me.Parent.Controls.Add(Me.LittlePanel)
' End If
'End If
Me.CreateLittlePanel()
End If
End Sub
' This is called when the parent control is painting, paint arrow here!
Private Sub Parent_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs)
If Me.LittlePanel IsNot Nothing Then
Using p As New Pen(Color.Blue, 2)
e.Graphics.DrawLine(p, Me.Location, Me.LittlePanel.Location)
End Using
End If
End Sub
End Class
LittlePanel:
Code:
Imports System.ComponentModel
<ToolboxItem(False)> _
Public Class LittlePanel
Inherits Panel
Public Sub New()
Me.Size = New Size(12, 12)
Me.BackColor = Color.Red
Me.BorderStyle = Windows.Forms.BorderStyle.FixedSingle
End Sub
Protected Overrides Sub OnSizeChanged(ByVal e As System.EventArgs)
MyBase.OnSizeChanged(e)
' Don't allow size change
Me.Size = New Size(12, 12)
End Sub
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
Dim rect As New Rectangle(e.ClipRectangle.X + 2, e.ClipRectangle.Y + 2, 6, 6)
ControlPaint.DrawGrabHandle(e.Graphics, rect, True, True)
End Sub
End Class
Please note that there are some 'serious' issues with this (I don't have time to look into them now).
The 'arrow' (in my example just a blue line) is still drawn in the designer (not during run-time) even when you remove the control.
Also, every time you re-open the form file (or run the project), a new LittlePanel is created and the old one is 'forgotten' (though it is still on the form).
Finally, the arrow does not repaint when you are moving the little panel (or the main panel), it only repaints once you stop moving it. This can be solved easily though by adding a call to Parent.Invalidate (check that parent is not nothing first) on the OnMove override of both panels.
There's probably other problems too, but it's a start at least.
EDIT
The biggest problem (which I can't see a way around) probably is that the arrow is drawn on the parent background. Any controls on that same parent control will be drawn on top of it, so you cannot see the arrow there.
To get around that, the only way is to paint the arrow on a control that is above all the other controls. But then, of course, that control is blocking the view to all these other controls, unless you make it actually transparent (which is not the same as setting the background color to Transparent!). You'd have to do something really awkward and slow such as taking a snapshot of the background and setting that as the background image, or something. That will probably not work at all.
-
Re: comment control
Hi,
thank you very much for your replies and sorry, that I did not answer earlier. I had to finish some work before.
Reading your answers and thinking about the 'issues' of Nick´s solution I decided to give up the "one control" requirement. My first solution now is a simple class, containing three controls (main panel, little panel, line). It requires to add these controls to the parent control on runtime. That´s acceptable for me.
Here´s the code for the new class:
Code:
Imports System
Imports System.Windows.Forms
Imports System.Drawing
Imports Microsoft.VisualBasic.PowerPacks
Public Class DynamicCommentBoxA
Public WithEvents GrabPanel9 As New Panel
Public WithEvents MainPanel As New Panel
Public WithEvents Line1 As New LineShape
Public WithEvents SC1 As New ShapeContainer
' mouse position when mouse down
Dim oldMouseX As Integer
Dim oldMouseY As Integer
' controls position when mouse down
Dim oldMainPanelLocation As Point
Dim oldGrabPanel9Location As Point
' flags
Dim mMainPanelMouseDown As Boolean = False
Dim mMainPanelDrag As Boolean = False
Dim mGrabPanel9MouseDown As Boolean = False
Dim mGrabPanel9Drag As Boolean = False
Public Sub New()
' initialize little panel
GrabPanel9.Width = 10
GrabPanel9.Height = 10
GrabPanel9.Location = New Point(150, 5)
GrabPanel9.BackColor = Color.Red
GrabPanel9.Cursor = Cursors.Hand
' initialize main panel
MainPanel.Width = 300
MainPanel.Height = 300
MainPanel.Location = New Point(50, 20)
MainPanel.BackColor = Color.GreenYellow
MainPanel.Cursor = Cursors.Hand
' initialize line
Line1.Parent = SC1
Line1.StartPoint = MainPanel.Location
Line1.EndPoint = GrabPanel9.Location
Line1.BorderColor = Color.Aquamarine
Line1.BorderWidth = 5
Line1.BringToFront()
SC1.BringToFront()
End Sub
Private Sub GrabPanel9_MouseDown _
(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles GrabPanel9.MouseDown
Beep()
oldMouseX = e.X
oldMouseY = e.Y
oldGrabPanel9Location = GrabPanel9.Location
mGrabPanel9MouseDown = True
End Sub
Private Sub GrabPanel9_MouseMove _
(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles GrabPanel9.MouseMove
' e.X, e.Y: mouse cursor in client coordinates
' set DragOKFlag to True when mouse leaves little rectangle around
' mouse down point to reduce flickering
If ((e.X < (oldMouseX - 3)) Or _
(e.X > (oldMouseX + 3)) Or _
(e.Y < (oldMouseY - 3)) Or _
(e.Y > (oldMouseY + 3))) And _
mGrabPanel9MouseDown = True Then
mGrabPanel9Drag = True
End If
If mGrabPanel9Drag Then
oldGrabPanel9Location = GrabPanel9.Location
GrabPanel9.Location = New Point(oldGrabPanel9Location.X + e.X - oldMouseX, oldGrabPanel9Location.Y + e.Y - oldMouseY)
' redraw all controls on the parent form to clear traces from Line1
For i As Integer = 0 To GrabPanel9.Parent.Controls.Count - 1
GrabPanel9.Parent.Controls(i).Invalidate()
Next
' redraw Line1
Line1.EndPoint = GrabPanel9.Location
Line1.BringToFront()
End If
' reset drag flag
mGrabPanel9Drag = False
End Sub
Private Sub GrabPanel9_MouseUp _
(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles GrabPanel9.MouseUp
mGrabPanel9MouseDown = False
End Sub
Private Sub MainPanel_MouseDown _
(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MainPanel.MouseDown
oldMouseX = e.X
oldMouseY = e.Y
oldMainPanelLocation = MainPanel.Location
mMainPanelMouseDown = True
End Sub
Private Sub MainPanel_MouseMove _
(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MainPanel.MouseMove
' e.X, e.Y: mouse cursor in client coordinates
' set DragOKFlag to True when mouse leaves little rectangle around
' mouse down point to reduce flickering
If ((e.X < (oldMouseX - 3)) Or _
(e.X > (oldMouseX + 3)) Or _
(e.Y < (oldMouseY - 3)) Or _
(e.Y > (oldMouseY + 3))) And _
mMainPanelMouseDown = True Then
mMainPanelDrag = True
End If
If mMainPanelDrag Then
' store old MainPanel location
oldMainPanelLocation = MainPanel.Location
' compute new MainPanel location
MainPanel.Location = New Point(oldMainPanelLocation.X + e.X - oldMouseX, oldMainPanelLocation.Y + e.Y - oldMouseY)
' redraw all controls on the parent form to clear traces from Line1
For i As Integer = 0 To GrabPanel9.Parent.Controls.Count - 1
GrabPanel9.Parent.Controls(i).Invalidate()
Next
' set start point of Line1
Line1.StartPoint = MainPanel.Location
Line1.BringToFront()
End If
' reset drag flag
mMainPanelDrag = False
End Sub
Private Sub MainPanel_MouseUp _
(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MainPanel.MouseUp
mMainPanelMouseDown = False
End Sub
End Class
Open a new WindowsForms project and put in this code:
Code:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' declare DynamicCommentBoxA1
Dim DynamicCommentBoxA1 As New DynamicCommentBoxA
' add its controls to Form1
Me.Controls.Add(DynamicCommentBoxA1.MainPanel)
Me.Controls.Add(DynamicCommentBoxA1.GrabPanel9)
DynamicCommentBoxA1.SC1.Parent = Me
' add a blue colored panel for test
Dim p1 As New Panel
p1.BackColor = Color.DarkBlue
p1.Size = New Size(300, 200)
p1.Location = New Point(300, 400)
Me.Controls.Add(p1)
' bring controls to front
DynamicCommentBoxA1.MainPanel.BringToFront()
DynamicCommentBoxA1.SC1.BringToFront()
DynamicCommentBoxA1.GrabPanel9.BringToFront()
End Sub
End Class
If you have some additional suggestions, please let me know.
Greetings,
TheTree