Hit Test example

  • Thread starter Thread starter Brian Henry
  • Start date Start date
Hi Brian,

I think the design is just OK. We use an ArrayList to store the
objects(Provided every bar or other drawing is defined as an object) and
implement their own drawing algorithm(e.g. the method of different shape
will use different drawing method). In the OnPaint event handler we will
call the draw method of every objects in the arraylist.

As for the click event of the control I think we can use the
Cursor.Position to get the mouse's current position and loop the arraylist
to know where is the mouse click on.
e.g.
Private Sub bartest_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles MyBase.Click
Dim pt As Point = PointToClient(Cursor.Position)
Debug.WriteLine(pt.X.ToString() & "," & pt.Y.ToString())
Dim bi As BarItem
For Each bi In barItemList
If bi.rectangle.Contains(pt) Then
MsgBox(bi.orderNumb.ToString() & " is clicked")
Exit For
End If
Next
End Sub

Best regards,

Peter Huang
Microsoft Online Partner Support

Get Secure! - www.microsoft.com/security
This posting is provided "AS IS" with no warranties, and confers no rights.
 
Hi Peter,

I was just thinking about this while testing it some, this seems to be a
high process usage control (about 50% processor usage as you move the mouse
around on the control) is there anyway to reduce this? I have noticed other
controls 3rd parties produced that do similar things but their process usage
never jumps too high (average is 4%)
 
It's because you're redrawing every bar on every mouse move. This is
extremely wasteful.
 
well I've pulled out a couple of the invalidates, and got it down to abougt
25%, what would you suggest could be changed to help? thanks
 
You need to keep track of which bar that has "focus". Then only redraw
it when it loses "focus".


'-- Save the current highighted bar
Dim current as BarItem

Private Sub bartest_MouseMove(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
Dim bi As BarItem

'-- See if we are in the same bar. If so,then there's no need
to draw anything.
If current.rectangle.Contains(e.X,e.Y) then Exit Sub

For Each bi In barItemList
If bi.rectangle.Contains(e.X, e.Y) Then
bi.MouseOver = True
Invalidate()
current = bi
Exit For
Else
bi.MouseOver = False
Invalidate()

End If




Next
End Sub
 
thanks, ill try to rework this some.. this is my 1st attempt at this type of
control if you cant tell :)
 
I made a couple more changes to what you suggested

Private Sub bartest_MouseMove(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove

Dim bi As BarItem

'-- See if we are in the same bar. If so,then there's no need to draw
anything.

If Not current Is Nothing Then

If current.rectangle.Contains(e.X, e.Y) Then Exit Sub

End If



For Each bi In barItemList

If bi.rectangle.Contains(e.X, e.Y) Then

bi.MouseOver = True

Invalidate()

current = bi

Exit For

Else

bi.MouseOver = False

current = Nothing

If Not current Is Nothing Then

Invalidate()

End If

End If



Next

End Sub



that reduced the process usage a ton, if the mouse is over nothing thats a
bar item it ignores redrawing all together
 
Check this out: I overloaded your Render function to accept a Graphics
object to redraw only the highlighted Bar. This is much more efficient
than invalidating and redrawing all the bars. I also improved your mouse
move event.



Public Class bartest
Inherits System.Windows.Forms.UserControl

#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

'UserControl 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.
<System.Diagnostics.DebuggerStepThrough()> Private Sub
InitializeComponent()
'
'bartest
'
Me.Name = "bartest"

End Sub

#End Region

Private barItemList As New ArrayList
Public i_barHeight As Integer = 10
Public i_barSpaceing As Integer = 3


Private Sub bartest_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Me.SetStyle(ControlStyles.UserPaint, True)
Me.SetStyle(ControlStyles.DoubleBuffer, True)
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.ResizeRedraw, True)
Me.UpdateStyles()


End Sub

Public Sub start()
Dim a As Integer
For a = 1 To 10
Dim bi As New BarItem()
bi.Owner = Me
bi.orderNumb = a
bi.SetStart = a * 3.1 * a
bi.SetEnd = bi.SetStart * 2
barItemList.Add(bi)
Next
End Sub

Protected Overrides Sub OnPaint(ByVal e As
System.Windows.Forms.PaintEventArgs)
ControlPaint.DrawBorder3D(e.Graphics, 0, 0, Me.Width,
Me.Height, Border3DStyle.Sunken, Border3DSide.All)

Dim bi As BarItem
For Each bi In barItemList
bi.Render(e)
Next



End Sub
Dim current As BarItem
Private Sub bartest_MouseMove(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
Dim bi As BarItem
If Not current Is Nothing Then
If current.rectangle.Contains(e.X, e.Y) Then
Exit Sub
Else
Dim grfx As Graphics = Me.CreateGraphics
current.MouseOver = False
current.Render(grfx)
grfx.Dispose()
current = Nothing
End If
End If
For Each bi In barItemList
If bi.rectangle.Contains(e.X, e.Y) Then
bi.MouseOver = True
current = bi
Dim grfx As Graphics = Me.CreateGraphics
current.Render(grfx)
grfx.Dispose()
Exit For
End If
Next
End Sub

' Private Sub bartest_MouseHover(ByVal sender As Object, ByVal e
As System.EventArgs) Handles MyBase.MouseHover
' Dim bi As BarItem
' For Each bi In barItemList
' If bi.rectangle.Contains(e.X, e.Y) Then
' bi.MouseOver = True
' Invalidate()
' Exit For
' Else
' bi.MouseOver = False
' Invalidate()

' End If




' Next
' End Sub

Private Sub bartest_MouseDown(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
Dim bi As BarItem
For Each bi In barItemList
If bi.rectangle.Contains(e.X, e.Y) Then
bi.MouseOver = True
bi.MouseClick = True
Invalidate()
Exit For
Else
bi.MouseOver = False
bi.MouseClick = False
Invalidate()

End If




Next

End Sub
End Class


Public Class BarItem
Implements IMouseCommands

Private i_startNum As Integer
Private i_endNum As Integer
Private bt_owner As bartest
Private r_rectangle As rectangle
Private i_orderNum As Integer = 1

Private b_mouseover As Boolean = False
Private b_mouseclick As Boolean = False


Public Property orderNumb() As Integer
Get
Return i_orderNum
End Get
Set(ByVal Value As Integer)
i_orderNum = Value
End Set
End Property
Public Property SetStart() As Integer
Get
Return i_startNum
End Get
Set(ByVal Value As Integer)
i_startNum = Value
End Set
End Property
Public Property SetEnd() As Integer
Get
Return i_endNum
End Get
Set(ByVal Value As Integer)
i_endNum = Value
End Set
End Property
Public Property rectangle() As rectangle
Get
Return r_rectangle
End Get
Set(ByVal value As rectangle)
r_rectangle = value
End Set
End Property
Public Property Owner() As bartest
Get
Return bt_owner
End Get
Set(ByVal Value As bartest)
bt_owner = Value
End Set
End Property

Public Sub Render(ByVal e As PaintEventArgs)
Dim startColor As Drawing.Color
Dim endColor As Drawing.Color
If b_mouseover = False Then
startColor = Color.LightBlue
endColor = Color.Blue
Else
startColor = Color.Yellow
endColor = Color.YellowGreen
End If


Dim rect As New Rectangle(i_startNum, (i_orderNum *
bt_owner.i_barHeight) + (i_orderNum * bt_owner.i_barSpaceing), i_endNum,
bt_owner.i_barHeight)
Dim rectshad As New Rectangle(i_startNum + 2, (i_orderNum *
bt_owner.i_barHeight) + (i_orderNum * bt_owner.i_barSpaceing) + 2,
i_endNum, bt_owner.i_barHeight)
r_rectangle = rect



Dim br As New Drawing2D.LinearGradientBrush(rect, startColor,
endColor, Drawing2D.LinearGradientMode.Vertical)
e.Graphics.FillRectangle(New SolidBrush(Color.FromArgb(100, 0,
0, 0)), rectshad)
e.Graphics.FillRectangle(br, rect)


If b_mouseover = True Then
e.Graphics.DrawRectangle(New Pen(Color.White), rect)
End If
br.Dispose()

' do boarder


End Sub

Public Sub Render(ByVal e As Graphics)
Dim startColor As Drawing.Color
Dim endColor As Drawing.Color
If b_mouseover = False Then
startColor = Color.LightBlue
endColor = Color.Blue
Else
startColor = Color.Yellow
endColor = Color.YellowGreen
End If


Dim rect As New Rectangle(i_startNum, (i_orderNum *
bt_owner.i_barHeight) + (i_orderNum * bt_owner.i_barSpaceing), i_endNum,
bt_owner.i_barHeight)
Dim rectshad As New Rectangle(i_startNum + 2, (i_orderNum *
bt_owner.i_barHeight) + (i_orderNum * bt_owner.i_barSpaceing) + 2,
i_endNum, bt_owner.i_barHeight)
r_rectangle = rect



Dim br As New Drawing2D.LinearGradientBrush(rect, startColor,
endColor, Drawing2D.LinearGradientMode.Vertical)
e.FillRectangle(New SolidBrush(Color.FromArgb(100, 0, 0, 0)),
rectshad)
e.FillRectangle(br, rect)


If b_mouseover = True Then
e.DrawRectangle(New Pen(Color.White), rect)
End If
br.Dispose()

' do boarder


End Sub

Public Property MouseClick() As Boolean Implements
IMouseCommands.MouseClick
Get
Return b_mouseclick

End Get
Set(ByVal Value As Boolean)
b_mouseclick = Value
If Value = True Then
MessageBox.Show(Me.orderNumb)
End If


End Set
End Property

Public Property MouseOver() As Boolean Implements
IMouseCommands.MouseOver
Get
Return b_mouseover

End Get
Set(ByVal Value As Boolean)
b_mouseover = Value

End Set
End Property


End Class

' mouse commands interface
' this is not complete! just for an example
Public Interface IMouseCommands
Property MouseOver() As Boolean
Property MouseClick() As Boolean

End Interface
 
thanks so much, it's nice to have someone that can optimize code to learn
from :) you have been a big help
 
You can make the code even better but I'll leave that as an exercise for
you. Here are some things you should look at.


1: bartest_MouseDown (hint: Most of the code isn't needed.)
2: Render Method (hint: You only need one a version and not two.)
3: Render Method (hint: You keep recomputing the same rectangles.)
4: OnPaint (hint: Only paint what you need? Look into IntersectsWith.)
5: Start Method (hint: You can make this more concise with a Sub New.)
6: UserControl (hint: Look into a Windows control class instead.)

I've already fixed your code. Let me know if you need more help.
 
thanks. You've been a great help! and thanks for not posting all the code,
it's better to figure it out on my own :)
 
I just wrote this in about 5 minutes, can someone have a look at it and leme
know it this is a good way to do things like this? each item on the control
is a object which has its own render method.. here's a link to my project
source code.

http://www.cyber-arts.com/download/hittestexample.zip

please have a look at it if you have time and give me some suggestions.
thanks :) this is the first time i tried to do this.. im suprised my idea
worked as it is :)
 
Back
Top