# 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.
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 ICustomControl.Paint
You are passed a Canvas object that offers the following methods:
Canvas.Width As Long [Propert-Get]
Canvas.Height As Long [Propert-Get]
Canvas.Dpi As Long [Propert-Get]
Canvas.DpiScaleFactor As Double [Propert-Get]
Canvas.AddElement(Descriptor As ElementDescriptor)
Canvas.Width
and Canvas.Height
are the absolute pixel sizes that your control is drawing to. Unlike your controls Width/Height properties that are not DPI-scaled, the Canvas.Width
and Canvas.Height
values are DPI-scaled.
The Canvas.Dpi
property represents 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 the Canvas.Dpi
property will be 144.
The Canvas.DpiScaleFactor
property gives 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 the Canvas.DpiScaleFactor
property will be 1.5.
The Canvas.AddElement
method is used for adding elements 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 AddElement, meaning that the last element added will have the highest z-order.
# AddElement(ElementDescriptor)
The AddElement method 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 TextRendering ' options to customize text rendering (object)
BackgroundFill As Fill ' options to customize back fill rendering (object)
Corners As Corners ' options to customize corner rendering (object)
Borders As 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/Canvas.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 MyEvent
which 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
...
Private Sub MyClickEvent(ByRef EventInfo As MouseEvent)
MsgBox "You clicked me!"
End Sub
Private Sub OnPaint(ByVal Canvas As CustomControls.Canvas) _
Implements ICustomControl.Paint
Dim MyDescriptor As ElementDescriptor
MyDescriptor.OnClick = AddressOf MyClickEvent
End Sub
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 Canvas.AddElement, 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.