User Defined Controls
Description
The following section gives examples of User Defined Controls (UDCs) on a form. The sample forms and Xbasic scripts can be found in the Samples\Xbasic directory off the directory in which Alpha Anywhere was installed. Open the Xbasic database in this directory.
This approach for creating user defined controls is deprecated. DECLARE and DECLARESTRUCT are deprecated and slated for removal in a future release.
Writing a Slider Control
This example shows a simple slider control -It sets a value in a variable based on the position of the slider.
The OnInit Event
Declares all the external windows API calls used.
' Declare a structure for a rectangle ' Long integer values left, top, right and bottom declarestruct rect L1left, L1top, L1right, L1bottom '-------------------------------------------------------- ' Declare rectangle GDI function - draw a rectangle declare gdi32 Rectangle LLLLLL '-------------------------------------------------------- ' Windows SelectObject function - sets a Pen, Brush, Bitmap etc ' for a windows HDC declare gdi32 SelectObject LLL '-------------------------------------------------------- ' Windows DeleteObject function - deletes a Pen, Brush, Bitmap etc ' It is important to release objects when you are done declare gdi32 DeleteObject LL '-------------------------------------------------------- ' Windows CreateSolidBrush function ' Creates a brush object of the specified color declare gdi32 CreateSolidBrush LL '-------------------------------------------------------- ' Windows InvalidateRect function Used to tell windows ' that a particular window needs to be repainted ' the second argument could be a (rect) i.e. ' InvalidateRect LL(rect)L , but windows wants to see a ' pointer value of 0 if we want to invalidate the entire window, ' so we declare that argument as a long integer (same amount of ' memory as a pointer' so we can pass in a zero declare user32 InvalidateRect LLLL '-------------------------------------------------------- ' Windows GetFocus function Returns the handle of the window ' that has focus declare user32 GetFocus L '-------------------------------------------------------- ' Windows SetCapture function The window handle specified ' gets all the mouse events after SetCapture is called ' (until a ReleaseCapture is done) declare user32 SetCapture LL '-------------------------------------------------------- ' Windows ReleaseCapture function, Inform the system that ' we are done tracking the mouse declare user32 ReleaseCapture L '-------------------------------------------------------- ' Windows DrawEdge function - take a rectangle and border ' style and edge parameters (defined below) ' This function is used to draw a button-style border declare user32 DrawEdge LL(rect)LL '-------------------------------------------------------- ' Windows DrawFocusRect function - takes a rectangle argument ' and is used to draw a broken line 'focus rectangle' declare user32 DrawFocusRect LL(rect) '-------------------------------------------------------- ' shared variables to position and scale the slider dim shared xpos as N dim shared xmax as N '-------------------------------------------------------- ' constants that are used by the DrawEdge function to ' specify the edges to draw. constant BF_LEFT = 1 constant BF_TOP = 2 constant BF_RIGHT = 4 constant BF_BOTTOM = 8 constant BF_ALL = 15 '-------------------------------------------------------- ' constant defined to specify a border style constant BDR_RAISED = 5 '-------------------------------------------------------- ' Initialize the variables xpos = 0 if xmax < = 0 then xmax = 1 end if
The OnDraw Event
Draws the Slider control.
'------------------------------------- ' An XBASIC function that deletes the ' last color brush created FUNCTION cleanup_color_brush( hdc as N ) dim shared color_hbrush as N dim shared oldbrush as N ' if the color brush is defined (nonzero) if color_hbrush <> 0 ' then select the previous brush into the HDC ' it is important to do this BEFORE we delete ' the color brush because the HDC will most likely ' be using the brush we are about to delete SelectObject(hdc, oldbrush) ' and delete the color brush DeleteObject(color_hbrush) end if end FUNCTION '------------------------------------- ' A function that creates a brush of a ' particular color and applies it to a ' windows display context FUNCTION SET_brush_color( hdc as N, color as N ) dim shared color_hbrush as N dim shared oldbrush as N ' cleanup previously defined brush cleanup_color_brush( hdc ) ' Use windows API call the create a brush of the specified color color_hbrush = CreateSolidBrush(color) ' Select the created brush into the display context ' and remember the old brush oldbrush = SelectObject(hdc, color_hbrush) end FUNCTION ' Our shared position and extent variables dim shared xpos as N dim shared xmax as N ' Sizes we calculate dim xthumbwidth as N dim physical_width as N ' shared color brush handle dim shared color_hbrush as N ' color brush = 0 means color brush is undefined color_hbrush = 0 ' do a sanity check on xmax to avoid a divide by zero error if xmax < = 0 then xmax = 1 end if physical_width = a_USER.draw.width ' the width of the 'thumb' we calculate to be 1/8 the ' width of the slider control xthumbwidth = physical_width / 8 ' calculate the width we have to set the thumbnail at as the ' width of the slider minus the width of the thumbnail physical_width = physical_width - xthumbwidth ' We set the brush color to be light grey ' the components of a color are Red, Green and Blue values, ' where 255 is brightest, 0 is darkest - a multiply 256 will ' get us the value of the next byte up. .i.e. ' in a 4 byte long value - if we want to set a byte to a value of ' '7' ' value = 7 ' sets the first byte ' value = 7 * 256 ' sets the second byte ' value = 7 * 256 * 256 ' sets the third byte ' For RGB color values, these are the only bytes that are used SET_brush_color( a_USER.draw.hdc , (((192*256) + 192)*256) + 192 ) ' determine where to draw the thumbnail pxpos = (xpos * physical_width)/xmax ' Initailize the rectangle to be the thumbnail rect.left = pxpos rect.top = a_USER.draw.top rect.right = pxpos + xthumbwidth rect.bottom = a_USER.draw.bottom ' Draw the thumbnail background Rectangle(a_USER.draw.hdc, rect.left, rect.top, rect.right, rect.bottom) ' Draw the thumbnail border DrawEdge(a_USER.draw.hdc , rect , BDR_RAISED , BF_ALL) ' Set the background brush to Grey SET_brush_color( a_USER.draw.hdc , (((128*256) + 128)*256) + 128 ) if a_USER.draw.left < rect.left ' Draw the slider rectangle area before the thumbnail Rectangle(a_USER.draw.hdc, a_USER.draw.left, a_USER.draw.top, rect.left, a_USER.draw.bottom) end if if rect.right < a_USER.draw.right ' Draw the slider rectangle area after the thumbnail Rectangle(a_USER.draw.hdc, rect.right, a_USER.draw.top, a_USER.draw.right, a_USER.draw.bottom) end if if a_USER.draw.hasfocus ' If the control has focus, then draw a focus rectangle DrawFocusRect(a_USER.draw.hdc, a_USER.draw) end if ' Release any brushes we created cleanup_color_brush( a_USER.draw.hdc )
The OnKey Event
When the right and left arrow keys are pressed (when the control has focus) the slider will change. Notice that the slider accelerates slowly if the user holds down the key.
' IS_down_for is a counter dim IS_down_for as N ' If key is Right or Left arrow key if a_USER.key.VALUE = "{Left}" .or. a_USER.key.VALUE = "{Right}" a_USER.key.handled = .T. if a_USER.key.event = "down" ' Keep track of how long we hold down the key IS_down_for = IS_down_for + 1 ' determine the displayed position xpos = (xpos * physical_width)/xmax if a_USER.key.VALUE = "{Left}" ' move left N pixels - note that we ' speed up the number of pixels we move the ' longer we hold down the key xpos = xpos - (IS_down_for + 1)/2 else ' move right N pixels - note that we ' speed up the number of pixels we move the ' longer we hold down the key xpos = xpos + (IS_down_for + 1)/2 end if ' Check that position is in range, if out of ' range, then set to the nearest endpoint if xpos < 0 xpos = 0 else if xpos > = physical_width xpos = physical_width-1 end if ' convert back from displayed to logical value for xpos xpos = (xpos * xmax)/physical_width ' invalidate the rectangle - force a repaint InvalidateRect(a_USER.hwnd, 0, 0) else if a_USER.key.event = "up" ' Reset the 'holding-key-down' counter IS_down_for = 0 end if end if
The OnMouse Event
Allows the user to grab the slider and drag it to a new position.
' Variable to remember that mouse is 'down' dim mouse_down as L dim xthumbwidth as N ' physical_width is initialized by OnDraw event dim physical_width as N dim shared xpos as N dim shared xmax as N xthumbwidth = physical_width / 8 ' If the left mouse button was released if a_USER.mouse.event = "left up" ' and we were tracking it if mouse_down ' stop tracking the mouse mouse_down = .F. ReleaseCapture() end if else if mouse_down .or. a_USER.mouse.event = "left down" ' If we are tracking the mouse, or the mouse button ' was pressed while above our slider control if .not. mouse_down ' If mouse not captured, capture it now mouse_down = .T. SetCapture(a_USER.hwnd) end if ' Sanity check the maximum value if xmax < = 0 then xmax = 1 end if ' move the middle of thumbnail to the current mouse ' 'x' (horizontal displacement) position xpos = a_USER.mouse.x - (xthumbwidth/2) ' range check the position of the thumbnail if xpos < 0 xpos = 0 else if xpos > = physical_width xpos = physical_width-1 end if xpos = (xpos * xmax)/ physical_width ' Force the slider window to repaint InvalidateRect(a_USER.hwnd, 0, 0) end if
Writing a Clock Control
- This example is contained in the form called "Clock" in the Samples\Xbasic directory. This control is implemented using a User Defined Control. It makes use of a lot of GDI functions as well as the timer event.
OnCreate Event
The OnCreate event fires when the window for the control is created. The SetTimer call is a windows API call that is declared in the OnInit event.
When the window is created, we add a timer to that window which goes off every 1, 000 milliseconds (that is once a second) - the ID that we give is is '1' - the '0' that is the last parameter is a NULL callback function. Currently, callback functions are not supported by XBASIC.
' when window is created, create a timer that goes off every second timer_id = SetTimer(a_USER.hwnd, 1, 1000, 0)
OnDestroy Event
The OnDestroy event fires when the window for the control is destroyed. The KillTimer call is also a windows API call (declared in the OnInit event). When we destroy the window for the control, we must also destroy the associated timer.
' when the window is destroyed, throw the timer away KillTimer( a_USER.hwnd , timer_id )
OnDraw Event
This event is called whenever the control is repainted. The OnDraw event shown here uses all sorts of windows API calls (mostly GDI calls - for graphics routines).
' Paint the display - uses temporary bitmaps & caches the background FUNCTION calcx as N(X as N) ' Trignometry to convert a position along the circumference ' of the circle to a 'x' position ranging from -1 to 1 calcx = sinx * 3.141516*2? end FUNCTION FUNCTION calcy as N(Y as N) ' Trignometry to convert a position along the circumference ' of the circle to a 'y' position ranging from -1 to 1 calcy = -cosy * 3.141516*2? end FUNCTION ' Function that draws a hand on the clock FUNCTION drawhand as N(HDC as N, factor as N, sx as N, sy as N, length as N, pivlength as N, cx as N, cy as N, red as N, green as N, blue as N) ' Factor is the value 0-1 that describes the orientation of the ' hand (f represents to 'inverse' or orientation of the other ' facet of the hand) if factor > 0.75 then f = (1.0-factor)/2 fade1 = 0.75 - f fade2 = 0.5 + f else if factor > 0.50 then f = (0.75-factor)/2 fade1 = 0.5 + f fade2 = 0.75 - f else if factor > 0.25 then f = (0.5 - factor)/2 fade1 = 0.5 + f fade2 = 0.75 - f else f = factor/2 fade1 = 0.75 - f fade2 = 0.5 + f end if ' determine the shading of the hands facets given the ' 'pure' color of the hands + the angle the handle is at face1color = (int(blue*fade1)*256) + int(green*fade1?*256) + int(red*fade1) face2color = (int(blue*fade2)*256) + int(green*fade2?*256) + int(red*fade2) ' Draw a polygon for the first facet of the handle ' this involves initializing the array of facets that are ' passed to the polygon function dim pts.points4 as P f = factor - 0.50 pts.points1.x = cx + calcx(f) * pivlength * sx pts.points1.y = cy + calcy(f) * pivlength * sy f = factor - 0.25 pts.points2.x = cx + calcx(f) * pivlength * sx pts.points2.y = cy + calcy(f) * pivlength * sy pts.points3.x = cx + calcx(factor) * length * sx pts.points3.y = cy + calcy(factor) * length * sy pts.points4.x = pts.points1.x pts.points4.y = pts.points1.y hbrush = CreateSolidBrush(face1color) oldbrush = SelectObject(HDC, hbrush) Polygon(HDC, pts, 4) SelectObject(HDC, oldbrush) DeleteObject(hbrush) ' Draw the second facet of the clock hand f = factor + 0.25 pts.points2.x = cx + calcx(f) * pivlength * sx pts.points2.y = cy + calcy(f) * pivlength * sy hbrush = CreateSolidBrush(face2color) oldbrush = SelectObject(HDC, hbrush) Polygon(HDC, pts, 4) SelectObject(HDC, oldbrush) DeleteObject(hbrush) end FUNCTION ' XBASIC Function that draws the clock dial FUNCTION drawdial as N(DRAW as P) hbrush = CreateSolidBrush((((128*256) + 128)*256) + 128) oldbrush = SelectObject(DRAW.HDC, hbrush) ' Clear the controls background rectangle Rectangle( DRAW.HDC , DRAW.LEFT , DRAW.TOP, DRAW.RIGHT , DRAW.BOTTOM ) SelectObject(DRAW.HDC, oldbrush) DeleteObject(hbrush) ' Draw the ellipse of the clock face Ellipse( DRAW.HDC , DRAW.LEFT , DRAW.TOP, DRAW.RIGHT , DRAW.BOTTOM ) ' calculate x and y radius of the ellipse sx = (((DRAW.RIGHT - DRAW.LEFT) + 1) / 2) -1 sy = (((DRAW.BOTTOM - DRAW.TOP) + 1) / 2) -1 ' calculate center point of the ellipse cx = (DRAW.LEFT + DRAW.RIGHT) / 2 cy = (DRAW.TOP + DRAW.BOTTOM) / 2 ' create a dummy 'point' structure pt.dummy = 1 ' Draw the minute/second ticks along the edge for i = 0 to 59 MoveToEx( DRAW.HDC , cx + calcx(i/60) * sx , cy + calcy(i/60) * sy , pt ) Lineto( DRAW.HDC , cx + calcx(i/60) * sx * 0.95 , cy + calcy(i/60) * sy * 0.95 ) next ' Draw the Hour ticks + the hour text along the edge for i = 0 to 11 MoveToEx( DRAW.HDC , cx + calcx(i/12) * sx , cy + calcy(i/12) * sy , pt ) Lineto( DRAW.HDC , cx + calcx(i/12) * sx * 0.9 , cy + calcy(i/12) * sy * 0.9 ) digit = alltrimiif(i = 0, 12, i?) ' Get the size of the text - use size to determine how to ' center the text at a point GetT entPointA( DRAW.HDC , digit , len(digit) , pt ) ' Draw the text for the 'hour' centered on a point inside the ' dial next to the hour 'tick' TextOutA( DRAW.HDC , cx + calcx(i/12) * sx * 0.8 - pt.x/2 , cy + calcy(i/12) * sy * 0.8 - pt.y/2, digit , len(digit) ) next end FUNCTION sizex = ((A_USER.DRAW.RIGHT - A_USER.DRAW.LEFT) + 1) sizey = ((A_USER.DRAW.BOTTOM - A_USER.DRAW.TOP) + 1) ' If no background bitmap defined, define a bitmap that ' is the size of the clock face if backgroundhbitmap = 0 then ' Windows 'createBitmap' function uses the colors ' supported by the HDC, plus a size to create a ' bitmap backgroundhbitmap = CreateCompatibleBitmap(A_USER.DRAW.HDC, sizex, sizey) ' Create a HDC for the bitmap draw.hdc = CreateCompatibleDC(A_USER.DRAW.HDC) ' Select the bitmap into the HDC. This makes any ' calls that use the HDC to draw scribble to the ' bitmap instead of the screen. SelectObject( draw.hdc , backgroundhbitmap ) ' Draw the clock dial into the background bitmap draw.top = 0 draw.left = 0 draw.bottom = A_USER.DRAW.BOTTOM - A_USER.DRAW.TOP draw.right = A_USER.DRAW.RIGHT - A_USER.DRAW.LEFT drawdial(draw) DeleteDC(draw.hdc) end if ' If not defined, Create a bitmap for the 'cached' clock display. ' We write to a bitmap, then write the bitmap to the screen to get ' smooth repaints if hbitmap = 0 then hbitmap = CreateCompatibleBitmap(A_USER.DRAW.HDC, sizex, sizey) end if ' Create a HDC for the background bitmap (to read from) backmemhdc = CreateCompatibleDC(A_USER.DRAW.HDC) ' Create a HDC for the 'cache' bitmap (to write to) memhdc = CreateCompatibleDC(A_USER.DRAW.HDC) SelectObject( backmemhdc , backgroundhbitmap ) SelectObject( memhdc , hbitmap ) ' Copy the background bitmap to the bitmap we are creating ' to show the current time BitBlt( memhdc , 0 , 0 , sizex , sizey , backmemhdc , 0 , 0 , BITMAP_SRC_COPY ) ' set up the x & y center hsx = sizex/2 hsy = sizey/2 ' draw the hours hand to the bitmap drawhand(memhdc, hours/12, hsx, hsy, 1/2, 1/8, hsx, hsy, 255, 255, 255) ' draw the minutes hand to the bitmap drawhand(memhdc, minutes/60, hsx, hsy, 3/4, 1/10, hsx, hsy, 0, 0, 255) ' draw the seconds hand to the bitmap drawhand(memhdc, seconds/60, hsx, hsy, 5/6, 1/16, hsx, hsy, 255, 0, 0) ' Now that the bitmap is drawn, copy it to the screen BitBlt( A_USER.DRAW.HDC , A_USER.DRAW.LEFT , A_USER.DRAW.TOP , sizex , sizey , memhdc , 0 , 0 , BITMAP_SRC_COPY ) ' Cleanup the display contexts that we created DeleteDC(backmemhdc) DeleteDC(memhdc)
OnExit Event
This event is called when the control is deleted. The OnDraw events creates bitmaps to cache the repaints. We delete these in the OnExit method.
' Cleanup bitmaps that were created if hbitmap <> 0 DeleteObject(hbitmap) hbitmap = 0 end if if backgroundhbitmap <> 0 DeleteObject(backgroundhbitmap) backgroundhbitmap = 0 end if
OnInit Event
This event is called when setting up the control. This performs all the declaration and initialization of variables for all the functions we will use.
'Initialize - declare all the API functions that we use, ' init variables '** Declare a windows point structure on long integer X, '** one long integer Y declarestruct point L1X, L1Y '** Declare a windows array that can store up to 100 points declarestruct points point100points '** Declare a windows rectangle declarestruct rect L1left, L1top, L1right, L1bottom '** Declare windows MoveToEx (move to a point) function declare gdi32 MoveToEx LLLL(point) '** Declare windows draw line to a point function declare gdi32 LineTo LLLL '** Declare windows draw ellipse function declare gdi32 Ellipse LLLLLL '** Declare windows draw rectangle function declare gdi32 Rectangle LLLLLL '** Declare windows draw text function declare gdi32 TextOutA LLLLCL '** Declare windows get size of drawn text function declare gdi32 GetT entPointA LLCL(point) '** Declare windows mark window as invalid (repaint) function declare user32 InvalidateRect LLLL '** Declare widows GetFocus function - gets the active window declare user32 GetFocus L '** Declare windows CreateSolidBrush function - create a brush declare gdi32 CreateSolidBrush LL '** Declare windows DeleteObject function delete Brush, Pen, Bitmap declare gdi32 DeleteObject LL '** Declare windows SelectObject function - select brush pen etc '** into a windows display context declare gdi32 SelectObject LLL '---------------------------------------------------------------- '** Declare windows polygon drawing function. Here we pass an ' Array of 100 points, we can use less than 100 points - windows ' polygon function only reads the number of points we report that ' we are passing to it (using the number_of_points parameter) '. If you need to draw polygons with more than 100 points, ' than you must make points dimension (declared above) - ' bigger - i.e. point500points would allow up to 500 ' points to be passed to the polygon function declare gdi32 Polygon LL(points)L '** Declare windows SetTimer function - causes window to get ' called back on an interval with a timer message + timer id declare user32 SetTimer LLLLL '** Declare windows KillTimer function - causes window timer to ' cease declare user32 KillTimer LLL '------------------------------------------------------ ' windows CreateCompatibleBitmap Function - creates a bitmap ' using the attributes of the display context passed in, and ' the width and height provided declare gdi32 CreateCompatibleBitmap LLLL '------------------------------------------------------ ' windows function to create a display context that ' is like another (i.e. same color settings pens brushes etc) declare gdi32 CreateCompatibleDC LL '** declare windows function to delete display context that was ' created using CreateCompatibleDC declare gdi32 DeleteDC LL '** declare windows function Draw pixels from one display context ' into another display context declare gdi32 BitBlt LLLLLLLLLL ' Constant argument to BitBlt for 'copy bits' mode - there are ' several modes (subtracting pixels, logically or-ing or and-ing ' pixels etc) - this is the only BitBlt operation we use in this ' sample program CONSTANT BITMAP_SRC_COPY = 13369376 ' Initialize the hand values, and bitmaps seconds = 0 minutes = 0 hours = 0 hbitmap = 0 backgroundhbitmap = 0
OnSize Event
This event is called when the control is sized. This forces the cached bitmaps to be recalculated (so that on the next draw the image will be resized).
' throw away the bitmaps whenever we resize if hbitmap <> 0 then DeleteObject(hbitmap) hbitmap = 0 end if if backgroundhbitmap <> 0 then DeleteObject(backgroundhbitmap) backgroundhbitmap = 0 end if
OnTimer Event
This event is called whenever a timer event occurs. We track the seconds, minutes and hours here:
' when timer goes off, recalculate the time & cause a repaint seconds = inttoseconds?, 60? minutes = modtime(?/60, 60) hours = modtime(?/(60*60), 12) ?causes the control to repaint InvalidateRect(a_USER.hwnd, 0, 0)