views:

72

answers:

1

I try to write a class that will draw itself on a control(.NET 2). But this "thing" does not repaint itself properly(does not invalidate the parent as it should).

Here is the usage:

Public Class Form1
  Dim myCadre As New Cadre

  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    For i As Integer = 0 To 100
      myCadre.Location = New Point(myCadre.Location.X + 1, myCadre.Location.Y + 1)
      System.Threading.Thread.Sleep(500)
    Next i
  End Sub

  Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    myCadre.Location = New Point(10, 10)
    myCadre.Size = New Size(60, 90)
    myCadre.Parent = Me
  End Sub
End Class

Here is the code:

Public Class Cadre
  Private _Rectangle As Rectangle
  Private _Parent As Control
  Public Event ParentChanged As EventHandler
  Private _Location As Point

  Public ReadOnly Property DisplayRectangle() As Rectangle
    Get
      Return _Rectangle
    End Get
  End Property

  Public Property Location() As Point
    Get
      Return _Rectangle.Location
    End Get
    Set(ByVal value As Point)
      _Rectangle.Location = value
      DrawInternal()
    End Set
  End Property

  Public Property Size() As Size
    Get
      Return _Rectangle.Size
    End Get
    Set(ByVal value As Size)
      _Rectangle.Size = value
      DrawInternal()
    End Set
  End Property

  Public Property Parent() As Control
    Get
      Return _Parent
    End Get
    Set(ByVal value As Control)
      If _Parent IsNot value Then
        _Parent = value
        OnParentChanged(Me, EventArgs.Empty)
      End If
    End Set
  End Property

  Overridable Sub OnParentChanged(ByVal sender As Object, ByVal e As EventArgs)
    DrawInternal()
    RaiseEvent ParentChanged(sender, e)
  End Sub

  Private Sub DrawInternal()
    If _Parent Is Nothing Then Return
    Dim g As Graphics = _Parent.CreateGraphics()

    g.DrawRectangle(Pens.Black, Me._Rectangle)
  End Sub
End Class
  • What I am doing wrong?
  • What is better: use private Graphic g and set it only once when parent changed, or create g at every DrawInternal?
  • Is it possible to have on it the Click event?

CADRE v2 (after Humberto suggestions):

Public Class Cadre
  Private _FormerRectangle As Rectangle
  Private _Rectangle As Rectangle
  Private WithEvents _Parent As Control
  Public Event ParentChanged As EventHandler
  Private _Location As Point

  Public Sub New()
    _FormerRectangle = New Rectangle
    _Rectangle = _FormerRectangle
  End Sub

  Private Sub DrawInternal(ByVal sender As Object, ByVal e As PaintEventArgs) Handles _Parent.Paint  
    If _FormerRectangle <> _Rectangle Then
      _Parent.Invalidate(_FormerRectangle, False) ' !!! does not work '
    End If

    e.Graphics.DrawRectangle(Pens.Black, Me._Rectangle)
    _FormerRectangle = Me._Rectangle
  End Sub

  Public ReadOnly Property DisplayRectangle() As Rectangle
    Get
      Return _Rectangle
    End Get
  End Property

  Public Property Location() As Point
    Get
      Return _Rectangle.Location
    End Get
    Set(ByVal value As Point)
      _Rectangle.Location = value
    End Set
  End Property

  Public Property Size() As Size
    Get
      Return _Rectangle.Size
    End Get
    Set(ByVal value As Size)
      _Rectangle.Size = value
    End Set
  End Property

  Public Property Parent() As Control
    Get
      Return _Parent
    End Get
    Set(ByVal value As Control)
      If _Parent IsNot value Then
        _Parent = value
        OnParentChanged(Me, EventArgs.Empty)
      End If
    End Set
  End Property

  Overridable Sub OnParentChanged(ByVal sender As Object, ByVal e As EventArgs)
    RaiseEvent ParentChanged(sender, e)
  End Sub  

End Class

Edit3

Location Example:

  Public Property Location() As Point
    Get
      Return _Rectangle.Location
    End Get
    Set(ByVal value As Point)
      If _Parent IsNot Nothing Then
        _Parent.Invalidate(_Rectangle, False) ' 1st Call with former '
      End If
      _Rectangle.Location = value
      If _Parent IsNot Nothing Then
        _Parent.Invalidate(_Rectangle, False) ' 2nd Call with new '
        _Parent.Update()
      End If
    End Set
  End Property

Does not work...

+2  A: 

Hook the Cadre class to the Paint event of the _Parent member:

Public Property Parent() As Control
    Get
      Return _Parent
    End Get

    Set(ByVal value As Control)
      If _Parent IsNot Nothing Then
          RemoveHandler _Parent.Paint, AddressOf Me.Cadre_Paint
      End If

      _Parent = value

      If _Parent IsNot Nothing Then
          AddHandler _Parent.Paint, AddressOf Me.Cadre_Paint
      End If
    End Set
End Property

Private Sub Cadre_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
    Dim g As Graphics = e.Graphics
    g.DrawRectangle(Pens.Black, Me._Rectangle)
End Sub

Refresh() the parent control upon Cadre changes on its location, size, contents etc.

Public Property Size() As Size
    Get
      Return _Rectangle.Size
    End Get
    Set(ByVal value As Size)
      _Rectangle.Size = value

      If _Parent IsNot Nothing Then
          _Parent.Refresh()
      End If
    End Set
End Property

The click event is handled in a similar manner. Add a handler for the _Parent.MouseDown event, and check if the mouse coordinates are inside your Cadre. Check Control.MouseDown for more information.

Humberto
what do you think about: `Private WithEvents _Parent As Control` and `Private Sub DrawInternal(ByVal sender As Object, ByVal e As PaintEventArgs) Handles _Parent.Paint`
serhio
Not sure what to think about these, as I'm no VB man!
Humberto
I believe I should refresh on ly the former zone of the parent, not all the parent. Imagine there a million of Cadres is the parent that does not change... Is there such a posibility?
serhio
Refresh() causes an immediate repaint of the entire control. Invalidate() is asynchronous; it will *mark* the client area as ready for an update -- in fact, Control.Update() will synchronously paint the invalidated areas. For more information, check [Control.Refresh](http://msdn.microsoft.com/en-us/library/system.windows.forms.control.refresh.aspx) and [Control.Invalidate](http://msdn.microsoft.com/en-us/library/598t492a.aspx) on MSDN.
Humberto
In this case, invalidate both the former and the current zone, then Update() the parent. It's more performance-wise.
Humberto
ok, see my update, my idea would be to mark only the former rectangle and then Update(). But as you mention Update will however invalidate all the _Parent?!..
serhio
No. [Update()](http://msdn.microsoft.com/en-us/library/system.windows.forms.control.update.aspx) will trigger the repaint of the invalidated areas only.
Humberto
see my Edit3, consecutive call of Invalidate on 2 region seems do not summ that regions for the Update...
serhio
I think I got the point, I forget the 1 px of rectangle border to include in the invalidating area!.. so, Size+(1,1)
serhio
The last thing: What about the `Click` event?
serhio
@serhio Increase the border, or use FillRectangle() to test. But I see a problem already -- the consecutive Invalidate() calls will draw two rectangles upon Update()! Also, did you add a handler for the _Parent.Paint event?
Humberto
@serhio I updated my answer to cover the Click event.
Humberto