Search code examples
.netwinformsgdipaintclipping

How can I reliably call Control.InvokePaint with the correct clipping and offset?


I am attempting to force the drawing of hidden areas of controls caused by other controls with a higher z-order belonging in the same ControlCollection of a common parent control. The idea is that for two overlapping controls, on painting the higher of the two controls, it would call its own InvokePaint and InvokePaintBackground methods on the lower controls while passing in its own graphics, prepared for the intersected drawing operation.

Requested question clarification: More generically, I am trying to draw a subset of one control to the graphics surface of another, based on how the second overlaps the first.

Visual of problem, left to right, top to bottom

I cannot seem to find a permutation of transforms and Clipping regions that will draw the region I am interested in. I suspect the problem is with VisibleClippingBounds that seems to internally reference an original size or make some inaccessible call to establish visibility and declares the underlying region hidden therefore not required to be drawn, collapsing the VisibleClipBounds to a rectangle of zero edge length.

This is the code I feel like should work correctly, but does not:

Dim translation As Point = Control.RelationTo(Me)
Dim intersectionClip As New Region(Control.RelativeClientRectangle(Me))
intersectionClip.Intersect(siblingClip)
pevent.Graphics.Transform = New Drawing2D.Matrix(1, 0, 0, 1, translation.X, translation.Y)
pevent.Graphics.Clip = intersectionClip

InvokePaintBackground(Control, pevent)
InvokePaint(Control, pevent) 'controlPaintEventArgs)

The theory:

1) Establish relative location of lower control (Control) to this control (Me). This point represents a vector originating at (0, 0) on this control and ending at (0, 0) on the lower control, giving the relative location.

 Dim translation As Point = Control.RelationTo(Me)

2) Start with a region of the lower control's rectangle in this control's coordinate system. The rectangle returned is located at the point returned from the RelationTo call and is of the size of the lower control.

Dim intersectionClip As New Region(Control.RelativeClientRectangle(Me))

3) Intersect the region with a pre-established region of then total known area of underlying controls, to find the relevant drawing area of this control, in this control's coordinates.

intersectionClip.Intersect(siblingClip)

4) Apply a translation transform to the graphics object in the direction of the relative control offset.

pevent.Graphics.Transform = New Drawing2D.Matrix(1, 0, 0, 1, translation.X, translation.Y)

5) Apply the clip region previously established, importantly after setting the transform, to ensure it is not modified when the transform is changed.

pevent.Graphics.Clip = intersectionClip

6) Raise the paint events on the lower control, using this control's PaintEventArgs object, where the Graphics held within have been prepared for drawing.

InvokePaintBackground(Control, pevent)
InvokePaint(Control, pevent) 'controlPaintEventArgs)

The result is a black void where nothing at all is painted, with a hint that the VisibleClipBounds have collapsed to zero area.


Solution

  • It appears the solution was to more closely look at the sequence of events and pay closer attention to the setting and resetting of the transform object.

    Sequence-wise, this strategy was perfect save that setting the Clip before the transform is the solution, so long as the initial transform is correct.

    The application of the solution pertains to another question on this forum, with the answer posted with some context: What is a general solution to creating a transparent control?.