Painting / Drawing to Your Control
The ICustomControl.Paint method
This is by far the most important method of a CustomControl. It tells the form engine exactly how you want it to render your control. See the ICustomControl.Paint reference for the host-side contract.
Tip
It is highly advisable to look at and experiment with the sample project provided with twinBASIC before trying to implement your own CustomControl.
Private Sub OnPaint(ByVal Canvas As CustomControls.Canvas) _
Implements CustomControls.ICustomControl.Paint
' Draws a horizontal line across the middle of the control, as an element
' one pixel tall at 100% scaling. LineFill is a solid-colour
' CustomControlsPackage.Fill that the control creates once, in Initialize.
Dim LineElement As CustomControlsPackage.ElementDescriptor
LineElement.Width = Canvas.RuntimeUICCGetWidth()
LineElement.Height = CLng(Canvas.RuntimeUICCGetDpiScaleFactor())
LineElement.Top = (Canvas.RuntimeUICCGetHeight() - LineElement.Height) \ 2
Set LineElement.BackgroundFill = LineFill
Canvas.RuntimeUICCCanvasAddElement LineElement
End Sub
You are passed a Canvas object that offers the following methods:
Canvas.RuntimeUICCGetWidth() As Long
Canvas.RuntimeUICCGetHeight() As Long
Canvas.RuntimeUICCGetDpi() As Long
Canvas.RuntimeUICCGetDpiScaleFactor() As Double
Canvas.RuntimeUICCCanvasAddElement(Descriptor As ElementDescriptor)
RuntimeUICCGetWidth and RuntimeUICCGetHeight return the absolute pixel sizes that your control is drawing to. Unlike your control’s Width/Height properties, which are not DPI-scaled, these values are DPI-scaled.
RuntimeUICCGetDpi returns the DPI setting in Windows. If no DPI scaling is in effect, this value is 96. For example, if you have scaling set at 150% on your monitor, then RuntimeUICCGetDpi returns 144.
RuntimeUICCGetDpiScaleFactor returns a floating point value representing the DPI scaling percentage. A value of 1 indicates no scaling. For example, if you have scaling set at 150% on your monitor, then RuntimeUICCGetDpiScaleFactor returns 1.5.
RuntimeUICCCanvasAddElement adds an element to your control. An element is considered to be something that the form-engine will render for you. For example, you might have a grid control that displays 100 cells at a time. Each of those cells would be an element. Elements can overlap each over (allowing for opacity/transparency). The form engine draws them in the order that you call RuntimeUICCCanvasAddElement, meaning that the last element added will have the highest z-order.
RuntimeUICCCanvasAddElement(ElementDescriptor)
RuntimeUICCCanvasAddElement takes a single argument, an ElementDescriptor. ElementDescriptor is a UDT that defines exactly how the element will be drawn and how it reacts to events like mouse clicks.
Public Type ElementDescriptor
OnClick As LongPtr ' event function callback pointer
OnDblClick As LongPtr ' event function callback pointer
OnMouseDown As LongPtr ' event function callback pointer
OnMouseUp As LongPtr ' event function callback pointer
OnMouseEnter As LongPtr ' event function callback pointer
OnMouseLeave As LongPtr ' event function callback pointer
OnMouseMove As LongPtr ' event function callback pointer
OnScrollH As LongPtr ' event function callback pointer
OnScrollV As LongPtr ' event function callback pointer
Left As Long ' pixel offset (control relative, DPI scaled)
Top As Long ' pixel offset (control relative, DPI scaled)
Width As Long ' pixel width (DPI scaled)
Height As Long ' pixel width (DPI scaled)
Cursor As MousePointerConstants ' cursor/pointer icon
TrackingIdX As LongLong ' for tracking this element, passed to events
TrackingIdY As LongLong ' for tracking this element, passed to events
Text As String ' the text to render
TextRenderingOptions As CustomControlsPackage.TextRendering ' options to customize text rendering (object)
BackgroundFill As CustomControlsPackage.Fill ' options to customize back fill rendering (object)
Corners As CustomControlsPackage.Corners ' options to customize corner rendering (object)
Borders As CustomControlsPackage.Borders ' options to customize border rendering (object)
End Type
Tips
-
Each time your OnPaint method is called, you start with a blank canvas.
-
Left/Top/Width/Height can legitimately be outside of the canvas area. For example, negative Left/Top, or a Width/Height past the canvas width or height has no ill-effects. The form engine will clip everything appropriately for you, allowing for much simpler designing of your control.
-
You should put thought into making the Paint routine efficient. Try not to instantiate COM objects, and when drawing multiple similar elements, try to re-use ElementDescriptors by setting up common properties outside of loops (see WaynesGrid for examples of this)
-
TrackingIdX and TrackingIdY are important when you have multiple elements within a control. The two values, when combined, should uniquely represent the element, and must be maintained if your Paint routine is called again. This is needed for supporting events. For example, in a grid control, each cell would have a TrackingIdX / TrackingIdY value associated with it, given the X/Y co-ordinates of the cell.
-
Currently, only mouse events are provided, but focus events are coming soon, as well as keyboard events.
-
You can use class-based event handlers by simply using the
AddressOf MyEventwhich is now possible to use even on class members. You can see this used frequently in the samples, such as WaynesGrid. All mouse events have the following format:
Class MyCustomControl
Implements CustomControls.ICustomControl
' ...
Private Sub OnInitialize(ByVal Context As CustomControls.CustomControlContext) _
Implements CustomControls.ICustomControl.Initialize
' ...
End Sub
Private Sub OnDestroy() _
Implements CustomControls.ICustomControl.Destroy
' ...
End Sub
Private Sub MyClickEvent(ByRef EventInfo As CustomControlsPackage.MouseEvent)
MsgBox "You clicked me!"
End Sub
Private Sub OnPaint(ByVal Canvas As CustomControls.Canvas) _
Implements CustomControls.ICustomControl.Paint
' One element covering the whole control, so a click anywhere on it
' calls MyClickEvent.
Dim MyDescriptor As CustomControlsPackage.ElementDescriptor
MyDescriptor.Width = Canvas.RuntimeUICCGetWidth()
MyDescriptor.Height = Canvas.RuntimeUICCGetHeight()
MyDescriptor.OnClick = AddressOf MyClickEvent
Canvas.RuntimeUICCCanvasAddElement MyDescriptor
End Sub
End Class
EventInfo (MouseEvent) provides mouse information such as the relative X/Y position of the mouse, plus the TrackingX/Y values discussed earlier.
- When you call
RuntimeUICCCanvasAddElement, your element goes into a render pipeline. It is not immediately painted to the screen. The render pipeline is compared to the previous render pipeline that was provided by you in the last OnPaint call, and the tB form engine will only redraw areas of the control that have changed. This allows for efficient painting of controls whilst not needing to be concerned about the finer details of how to do partial repainting.
See also
ICustomControl– the interface every custom control implementsCanvas– the drawing surface passed to Paint- Style helpers used by the
BackgroundFill/Borders/Corners/TextRenderingOptionsfields of anElementDescriptor:Fill,Borders,Corners,TextRendering - CustomControls package reference – overview of the framework and the built-in
Waynes…controls (a number of which —WaynesGrid,WaynesButton, … — are exactly the worked examples mentioned above)