views:

377

answers:

1

Hello there.

I've got a DateTimePicker which I 'dynamically' assign the Paint-Event, unfortunately as long as this Event is assigned neither the text nor the DropDown-Button are rendered (but are working).

I've written a component which takes another control and draws something on it

Public Sub New(Byval parent As Control)
    Me._parent = parent
    Me._setStyle = Me._parent.GetType().GetMethod("SetStyle", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic)
    Me._setStyle.Invoke(Me._parent, New Object() {ControlStyles.UserPaint, True})
    Me._setStyle.Invoke(Me._parent, New Object() {ControlStyles.OptimizedDoubleBuffer, True})

    AddHandler Me._parent.Paint, AddressOf RemotePaintHandler
End Sub

Private Sub RemotePaintHandler(ByVal sender As Object, ByVal e As PaintEventArgs)
    'draw something here'
End Sub

At disposing of the component I'm removing the handler and resetting the styles. But as long as the Handler is assigned the DateTimePicker renders like a normal Textbox without content, but the button is working and I can also enter values. If I've overwritten the OnPaint() method I'd simply call MyBase.OnPaint() or similar...but this is an eventhandler...I'm totally clueless here right now.

Thanks in advance, Bobby

Edit: I figured something out. As it seems the UserPaint happens within the underlying control and not in the DateTimePicker itself, 'causing it to render faulty. This will happen as long as the Style UserPaint is set.

One workaround is to let it render correct within the Paint-Event...which is not that easy I fear, but possible:

Private Sub RemotePaintHandler(ByVal sender As Object, ByVal e As PaintEventArgs)
    RemoveHandler Me._parent.Paint, AddressOf RemotePaintHandler
    Me._setStyle.Invoke(Me._parent, New Object() {ControlStyles.UserPaint, False})

    Me._parent.Refresh()
    ' now draw something '

    Me._setStyle.Invoke(Me._parent, New Object() {ControlStyles.UserPaint, True})
    AddHandler Me._parent.Paint, AddressOf RemotePaintHandler
End Sub

We remove the Handler and the Style for the Paint-Event, refresh the control ('causing it to render correctly), then drawing our things on top of the newly rendered control, and re-adding the Style and the Paint-Eventhandler. The downside is, that everything we draw disappears while somebody is entering some information (it's there if you use the calendar box), and might not come back until some_one_ refreshes the control. So this seems to be an ugly hack then a real workaround or even a solution[1].

Though, I've only tested this with a DateTimePicker, and have no clue if this also works with other controls (but it seems so).

Bobby

[1] I mean, I know the solution, rewriting the Control within the Framework...

+1  A: 

The DateTimePicker have no OnPaint method to draw itself. It's in fact a wrapper around the windows control SysDateTimePick32, so when you set UserPaint = true, the control (real one) no longer draws itself and there will be only your drawings.

You can inherit from DateTimePicker, override WndProc, respond to WM_PAINT message by executing Paint event subscribers. Here an example:

Public Class DTP
    Inherits DateTimePicker

    ' Events '
    Public Event Paint2 As PaintEventHandler

    ' Methods '
    Protected Overrides Sub WndProc(ByRef m As Message)

        MyBase.WndProc((m))

        If ((m.Msg = &HF) AndAlso (Not Me.Paint2 Is Nothing)) Then

            Dim g As Graphics = MyBase.CreateGraphics
            Me.Paint2.Invoke(Me, New PaintEventArgs( _
                                       g, _
                                       Rectangle.Round(g.VisibleClipBounds) _
                                     ) _
                            )
        End If

    End Sub

End Class

then use this new control instead of the regular DateTimePicker & subscribe to it's Paint2 event to draw what you need:

(you don't need to set UserPaint = True, you're capturing WM_PAINT anyway)

Public Sub New(Byval parent As Control)
    Me._parent = parent
    Me._setStyle = Me._parent.GetType().GetMethod("SetStyle", _
                               Reflection.BindingFlags.Instance _
                               Or Reflection.BindingFlags.NonPublic)
    'Me._setStyle.Invoke(Me._parent, New Object(){ControlStyles.UserPaint,True})'
    Me._setStyle.Invoke(Me._parent,  _
                        New Object() {ControlStyles.OptimizedDoubleBuffer, True})

    AddHandler Me._parent.Paint2, AddressOf RemotePaintHandler
End Sub

Private Sub RemotePaintHandler(ByVal sender As Object, ByVal e As PaintEventArgs)
    'draw something here'
End Sub

hope this helps,

najmeddine
I wanted to avoid creating a new control, because the idea was to attach this drawing method to _any_ control which comes along (but seems like a little impossible). But your answer is a pretty good one, I think to know this will be very helpful, +1.
Bobby