Adding Help-Tips to your programs

If you write AmiBroker programs that are used by others you have undoubtedly found that documenting your program and answering hundreds of emails can be very time consuming. You can save yourself, and those who use your code, a lot of time by adding Help popups (Tips) to your programs. No longer will users have to search though manuals and/or write emails to find basic instructions. Of course you will save time as well since you don’t have to answer all those emails :-) If you don’t program for others, read on, because there are some other applications that may interest you.

When Tips are enabled they will display as a balloon when hovering the cursor over a defined object or area, no clicks are required. Here is a typical Tip:

Tips can be used with gfx Control panels, trading dashboards, charts (function like the interpretation screen and now defunct ToolTip), tabular data, system status displays, gfx menus, caution and alert messages, etc. Your imagination sets the limits. Of course you can change the balloon size, colors, fonts, shape, etc. to your requirements. The code for the trading buttons shown is not provided in this post – that could be another topic.

Tips can display real-time market and system information. In the above example the Last price and a cautionary warning that Transmit is turned ON reflect real-time status.

In this post Tips are placed relative to the mouse’s cursor position. In chart applications you might want to use dynamically placed Tips that point to a critical event or condition on your chart. You would do this by converting price and bars to pixels and replace the MX and MY variables in the code with calculated values. For more on this see How to convert from bar-value to pixel coordinates on the AmiBroker Knowledge Base.

When I write button functions I add Tip information as the last argument to the function, this way the information is right where it is defined by the code. This is also helpful when reading the code. The actual Tip text can be repeated in your manual as a text box so that a clear association exists.

The code in this post demonstrates how the balloon reorients itself depending on in which quadrant of the pane the cursor is located. This is done to keep tips within the pane area, even when hovering over an object close to the edge of the pane. Here is a short video to see how this works.

If, on your computer, the Tip tracks the mouse cursor slower than in the video this is because I have enabled the higher refresh rate of 0.1 second. Tomasz explained this in post 151255 on the main AmiBroker list. Making errors while editing your Registry can cause serious computer problems, if you haven’t done this before, please seek professional help.

All code in this post is solely intended to show you how to develop Tips. They serve no other purpose. To help you experiment I used Param() instead of hard-coding Tip properties:

Have fun!


function ShowTipTipColorTipOutlineColorTipTextColor )
{
global MXMYPxWidthPxHeightTipWidthTipHeightTipsOnTipFontNameTipFontSize;
TipMsg VarGetText"TipMsg" );

if ( TipsOn AND TipMsg != "" )
{
Quadrant = ( MX &ltpxwidth ) + ( MY &ltpxheight ) * 2;
D  30// Rounding Diameter

switch ( Quadrant )
{

case 3:
X1 MX;
Y1 MY 0.75;
X2 MX TipWidth;
Y2 MY TipHeight 0.75;
X3 MX 0.75;
Y3 Y1;
X4 MX 2;
Y4 Y1;
X5 MX;
Y5 MY;
break;

case 2:
X1 MX TipWidth;
Y1 MY 0.75;
X2 MX;
Y2 MY TipHeight 0.75;
X3 MX 0.75;
Y3 Y1;
X4 MX 2;
Y4 Y1;
X5 MX;
Y5 MY;
break;

case 1:
X1 MX;
Y1 MY TipHeight 0.75;
X2 X1 TipWidth;
Y2 Y1 TipHeight;
X3 MX D;
Y3 Y2 1;
X4 MX 0.75;
Y4 Y2 1;
X5 MX;
Y5 y2 0.75;
break;

case 0:
X1 MX TipWidth;
Y1 MY TipHeight 0.75// Rectangle
X2 X1 TipWidth;
Y2 Y1 TipHeight;
X3 X2 D;
Y3 Y2 1// Pointer
X4 X2 0.75;
Y4 Y2 1;
X5 X2;
Y5 y2 0.75;
break;

default:
x1 x2 x3 x4 x5 y1 y2 y3 y4 y5 0;
}

GfxSetBkMode);

GfxSelectPenTipOutlineColor );
GfxSetTextColorTipTextColor );
GfxSelectFontTipFontnameTipFontSize );
GfxSelectSolidBrushTipColor );
GfxRoundRectX1y1x2y2D);
GfxPolygon(   X3Y3 X4 Y4X5Y5 );     // Pointer
GfxSelectPenTipColor ); // Hide line between RoundRect and Pointer
GfxMoveToX3Y3 );
GfxLineToX4Y4 );
GfxDrawTextTipMsgx1 10y1 3x2 10y2 516 65 );
}
}

function TestObjectLabelX1Y1X2Y2Color // Do-Nothing Demo function only
{
GfxSelectPencolorBlack );
GfxSelectSolidBrushColor );
GfxRectangleX1y1x2y2 );
OverObject MX &gtx1 AND MX &ltx2 AND MY &gty1 AND MY &lty2;

if ( OverObject )
VarSetText"TipMsg""The cursor is now over the " Label +
" quadrant and the shape of the Tip is adjusted accordingly." );

return OverObject;
}

// Global variables and parameters can be hard-coded in the final application
global MXMYPxWidthPxHeightTipWidthTipHeightTipsOnFontNameFontSize;
TipColor          ParamColor"Tip Background Color"colorWhite );
TipOutlineColor ParamColor"Tip Outline Color"colorBlack );
TipTextColor   ParamColor"Tip Text Color"colorBlack );
TipsOn             ParamToggle"Tips On""OFF|ON");
TipFontSize    Param"Font Size"9024);
TipWidth          Param"Tip Width"2205050010 );
TipHeight         Param"Tip Height"805050010 );
TipFontName       ParamStr"Tip Font name""Lucida Console" );

GfxSetBkMode);
GfxSetOverlayMode);
RequestTimedRefresh0.1 );
MX         GetCursorXPosition);
MY         GetCursorYPosition);
PxWidth    Status"pxwidth" );
PxHeight    Status"pxheight" );

// In a real application each colored area could be s small button
TestObject"UpperLeft"00pxwidth 2pxheight 2colorRed );
TestObject"UpperRight"pxwidth 20pxwidthpxheight 2colorBlue );
TestObject"LowerLeft"0pxheight 2pxwidth 2pxheightcolorYellow );
TestObject"Lowerright"pxwidth 2pxheight 2pxwidthpxheightcolorBrightGreen );

ShowTipTipColorTipOutlineColorTipTextColor ); // Place this call at the very endo of your code

Using a GFX Include file

Important note: The AmiBroker 5.09.0 Beta introduced the following new GFX functions:

Status(“pxchartleft”) – returns x-coordinate of top-left corner of chart area
Status(“pxcharttop”) – returns y-coordinate of top-left corner of chart area
Status(“pxchartright”) – returns x-coordinate of bottom-right corner of chart area
Status(“pxchartbottom”) – returns y-coordinate of bottom-right corner of chart area
Status(“pxchartwidth”) – returns width chart area (right-left)
Status(“pxchartheight”) – returns width chart area (bottom-top)

Since this release appeared after this post was published these functions are not used in the examples below. This post has been left unchanged for educational purposes. For examples using the new functions please see the 5.09.0 Read Me file.

=====

While the post Creating GFX Chart-Overlays (v2) may have clarified a few of the more important aspects of using GFX functions, it doesn’t really give you a “quick Start” template to get started. Using a GFXInclude file can remove some of the burden of having to define pixel and charting parameters. The Include file at the bottom of this post contains most definitions as well as these common functions that you may want to call from your GFX application:

GetVisualBarIndex( ); // Returns array containing index for visible bars
gfxPlotHLineYPixelsColor ); // Plots horizontal line at level YPixels
gfxPlotVLineXPixelsColor ); // plots vertical line at level XPixels
GetYPixels); // Convert a vertical price number to the pixel equivalent
GetXPixels); // Convert a horizontal DateTime number value to the pixel equivalent

Of course you can, and should, add additional functions of your own. Here is an example of how to call the above functions to draw a GFX cross-hair cursor (Red in the capture):

gfxcrosshair.jpg

Here is the code that produced the above image:

GraphXSpace 5// See the AmiBroker help on how to init these variables
GfxSetBkModebkmode );
GfxSetOverlayModemode );
GfxSelectPencolorRed );
 
PlotC""colorBlackstyleLine ); // To define miny and maxy
#include <GFXInclude-001.afl> // Located in your default Include folder
 
// Example to draw cross-hair cursor
Yprice GetCursorYPosition(0);
XIndex SelectedValue(GetVisualBarIndex( ));
gfxPlotHLineGetYPixelsYPrice ), colorRed );
gfxPlotVLineGetXPixelsXIndex ), colorRed );

The include file listed below defines the following variables:

// pxwidth, pxheight, Miny, MinX, YRange, VisBarIndex, NumBarsVisible, pxPaneWidth, pxPaneheight, PixelsPerBar, PixelsPerPrice

You may want to copy the above comment line below the #include statement in your code to refresh your memory. You should copy the Include file to your default AmiBroker Include folder.

// GFXInclude-001.afl copy to default include folder

function gfxPlotHLineYPixelsColor )
{
    global pxwidth;
    GfxSelectPenColor ) ;
    GfxMoveTo0YPixels );
    GfxLineTopxwidthYPixels );
}
 
function gfxPlotVLineXPixelsColor )
{
    global pxheight;
    GfxSelectPenColor ) ;
    GfxMoveToXPixels);
    GfxLineToXPixelspxheight );
}
 
function GetVisualBarIndex( )
{
    lvb Status"lastvisiblebar" );
    fvb Status"firstvisiblebar" );
    bi BarIndex();
    StaticVarSet"NumberbarsVisible"Lvb fvb );
    return bi bi] - fvb;
}

function GetYPixels)
    {
    global PixelsPerPricepxTopAreaMaxY; 
    return (MaxY Y) * PixelsPerPrice pxTopArea;
    }

function GetXPixels)
    {
    global PixelsPerBarpxLeftArea;
    return PixelsPerBar pxLeftArea;
    }

_SECTION_BEGIN("GFX INITIALIZATION");
// These Parameters will change depending on screen layout/fonts
pxRightArea Param"Right Axis Area"930200); // Depends on font
pxDateArea Param"Date Axis Area"110100); // Depends on font
DateaxisOn ParamToggle"Date Axis""HIDE|SHOW");

pxLeftArea 5pxTopArea 5pxBottomArea 5; 
if ( DateaxisOn )
{
    pxBottomArea pxDateArea pxBottomArea;
    SetChartOptions2chartShowDates );
}
else
    SetChartOptions3chartShowDates );

pxwidth Status"pxwidth" );
pxheight Status"pxheight" );

// clalculate charting area width and height
Miny Status"axisminy" );
Maxy Status"axismaxy" );
YRange MaxY MinY;
VisBarIndex =  GetVisualBarIndex( );
NumBarsVisible StaticVarGet"NumberbarsVisible" );

// Calculate Pane width and height
pxPaneWidth pxwidth pxLeftArea pxRightArea;
pxPaneHeight pxHeight pxTopArea pxBottomArea;

// calculate conversion factors
PixelsPerBar     pxPaneWidth NumBarsVisible;
PixelsPerPrice pxPaneHeight YRange;
_SECTION_END();

Edited by Al Venosa.

Creating GFX Chart-Overlays (v3)

Important note: The AmiBroker 5.09.0 Beta introduced the following new GFX functions:

Status(“pxchartleft”) – returns x-coordinate of top-left corner of chart area
Status(“pxcharttop”) – returns y-coordinate of top-left corner of chart area
Status(“pxchartright”) – returns x-coordinate of bottom-right corner of chart area
Status(“pxchartbottom”) – returns y-coordinate of bottom-right corner of chart area
Status(“pxchartwidth”) – returns width chart area (right-left)
Status(“pxchartheight”) – returns width chart area (bottom-top)

Since this release appeared after this post was published these functions are not used in the examples below. This post has been left unchanged for educational purposes. For examples using the new functions please see the 5.09.0 Read Me file.

=====

Creating an exact chart overlay using GFX functions can be a daunting task for the non-professional programmer. The solutions presented here were derived through experimentation; if there is a better way, please make a comment. Once the pixel layout is fully understood, GFX becomes an extremely powerful tool and may just give you an additional trading edge. The first and most important step in using GFX functions is to understand how pixels make up your display. In AmiBroker the width and height of your charting pane can be retrieved using the following two functions:

pxwidth Status("pxwidth");
pxheight Status("pxheight");

Horizontal pixels count left to right, 1 to pxwidth; vertical pixels count top to bottom, 1 to pxheight. The area covered by these two numbers is shown in Yellow below. For a high resolution monitor this area may cover about 2000 (H) x 1000 (V) pixels. This pixel area includes the areas used by the X and Y axis, and the blank top and bottom margins.

pixelarea.png

Next is the standard charting area, which is the area where your price charts are located. This area excludes the surrounding blank margins and the areas used for axis labeling. If you want to keep your overlay within the standard chart boundaries, you have to place your images within the above boundaries. This area is highlighted in Blue in the image below:

chartingarea.png

The boundaries for this area can be determined by running the example code listed in Finding Pixel Boundaries. Eight parameters must be known to create pixel overlays:

pxwidth Status"pxwidth" );
pxheight Status"pxheight" );
pxLeftArea Param"Left Blank Margin"50100); // Constant
pxRightArea Param"Right Axis Area"930200); // Depends on font
pxTopArea Param"Top Blank Margin"50100); // Constant
pxDateArea Param"Date Axis Area"110100); // Depends on font
pxBottomArea Param"Bottom Blank Margin"50100); // Constant
DateaxisOn ParamToggle"Date Axis""HIDE|SHOW");

Since adding date labels to your DateTime axis changes the size of your pixel plotting area, you need to compensate for this:

if( DateaxisOn ) 
    {
    pxBottomArea pxDateArea pxBottomArea;
    SetChartOptions(2,chartShowDates);
    }
else 
    {
    SetChartOptions(3,chartShowDates);
    }

The pixel plotting area’s width and height can now be calculated:

pxPaneWidth pxwidth pxLeftAreapxRightArea;
pxPaneHeight pxHeight pxTopAreapxBottomArea;

The dimension of the Blue area shown earlier changes when you resize AmiBroker, open additional windows or panes, change the fonts in your axis, or turn On/Off date labels. When this happens you will have to recalibrate the boundaries. To convert prices to pixels, so that you can create an exact overlay, you also need to define the width and height of your regular chart pane. These are expressed in DateTime and Price units. They will change when you zoom your chart. When you have at least one price plot displayed, so that the values for miny and maxy are defined, you can calculate these boundaries as follows:

Miny Status("axisminy");
Maxy Status("axismaxy");
YRange MaxY MinY
BarsVisible Status("BarVisible");
NumBarsVisible Cum(BarsVisible);

You now have all the information needed to calculate the Pixels/Price and Pixels/Bar conversion factors:

PixelsPerBar     pxPaneWidth NumBarsVisible;
PixelsPerPrice pxPaneHeight YRange;

Putting it all together in a demo program (listed at the end of this post) produces the price-chart overlay shown below. The regular price plot is plotted using dots, so that the overlay is clearly visible. When you plot both traces in lines, you will see minor deviations that are probably due to rounding to the nearest pixel. The pixel price plot is shown in Red. The purpose of this exercise is to learn to work with pixels and be able to produce an exact overlay on the price chart. The Param window below the charts shows typical parameters; they will likely be different for your screen layout.

pricechartoverlay1.png

pricechartoverlayparam.png

function gfxPlotHLineYPixelsColor )
{
    global pxwidth;
    GfxSelectPenColor ) ;
    GfxMoveTo0YPixels );
    GfxLineTopxwidthYPixels );
}
 
function gfxPlotVLineXPixelsColor )
{
    global pxheight;
    GfxSelectPenColor ) ;
    GfxMoveToXPixels);
    GfxLineToXPixelspxheight );
}
 
function GetVisualBarIndex( )
{
    lvb Status"lastvisiblebar" );
    fvb Status"firstvisiblebar" );
    bi BarIndex();
    StaticVarSet"NumberbarsVisible"Lvb fvb );
    return bi bi] - fvb;
}

function GetYPixels)
    {
    global PixelsPerPricepxTopAreaMaxY; 
    return (MaxY Y) * PixelsPerPrice pxTopArea;
    }

function GetXPixels)
    {
    global PixelsPerBarpxLeftArea;
    return PixelsPerBar pxLeftArea;
    }
 
GraphXSpace 5;
SetChartOptions0chartHideQuoteMarker );

pxwidth Status"pxwidth" );
pxheight Status"pxheight" );

// These Parameters will change depending on screen layout/fonts
pxRightArea Param"Right Axis Area"930200); // Depends on font
pxDateArea Param"Date Axis Area"110100); // Depends on font
DateaxisOn ParamToggle"Date Axis""HIDE|SHOW");

// These Parameters appear constant and can probably be hardcoded
pxLeftArea Param"Left Blank Margin"50100); // Constant
pxTopArea Param"Top Blank Margin"50100); // Constant
pxBottomArea Param"Bottom Blank Margin"50100); // Constant
 
if ( DateaxisOn // Size of bottom boundary depends on whether dates are shown
{
    pxBottomArea pxDateArea pxBottomArea;
    SetChartOptions2chartShowDates );
}
else
    SetChartOptions3chartShowDates );
 
// Test Plots to help line up boundary lines
Color colorWhite;
gfxPlotVLinepxLeftAreacolor );
gfxPlotVLinepxwidth pxRightAreacolor );
gfxPlotHLinepxTopAreacolor );
gfxPlotHLinepxHeight pxBottomAreacolor );
 
// Calculate Pane width and height
pxPaneWidth pxwidth pxLeftArea pxRightArea;
pxPaneHeight pxHeight pxTopArea pxBottomArea;
 
// clalculate charting area width and height
PlotC""1styleDots );
Miny Status"axisminy" );
Maxy Status"axismaxy" );
YRange MaxY MinY;
VisBarIndex =  GetVisualBarIndex( );
NumBarsVisible StaticVarGet"NumberbarsVisible" );

// calculate conversion factors
PixelsPerBar     pxPaneWidth NumBarsVisible;
PixelsPerPrice pxPaneHeight YRange;

// For verification: Overlay pixel on price plot
FVB Status"firstvisiblebar" );
LVB Status"lastvisiblebar" );
GfxSelectPencolorRed );
for ( FVB 1<= LVB AND BarCountb++ )
{
    PrevPixelY GetYPixelsC[b-1] );
    PixelY GetYPixelsC[b] );
 
    PrevPixelX GetXPixelsVisBarIndex[b-1] );
    PixelX GetXPixelsVisBarIndex[b] );
 
    GfxMoveToPrevPixelXPrevPixelY );
    GfxLineToPixelXPixelY );
}

Edited by Al Venosa.

Finding Pixel Boundaries

Important note: The AmiBroker 5.09.0 Beta introduced the following new GFX functions:

Status(“pxchartleft”) – returns x-coordinate of top-left corner of chart area
Status(“pxcharttop”) – returns y-coordinate of top-left corner of chart area
Status(“pxchartright”) – returns x-coordinate of bottom-right corner of chart area
Status(“pxchartbottom”) – returns y-coordinate of bottom-right corner of chart area
Status(“pxchartwidth”) – returns width chart area (right-left)
Status(“pxchartheight”) – returns width chart area (bottom-top)

Since this release appeared after this post was published these functions are not used in the examples below. This post has been left unchanged for educational purposes. For examples using the new functions please see the 5.09.0 Read Me file.

=====

The first requirement when designing chart overlays and/or control panels is to know the exact dimensions of your charting pane. These are expressed in pixels. The numerical values where the margins (the four edges of your charting pane) are located depends on the resolution of your monitor, the size of the chart pane, and the fonts used to label your axis.

Currently the only way to obtain the values is, as far as I know, to determine the pane boundaries manually. The code below plots four lines that you can position using sliders in the Param window. When the lines fall exactly on the edges of your charting pane you can read the pixel value in the Param window and in the chart Title. See the image below and note how the blue lines hide the regular black chart frame:

clip_image002

Try changing the size of your charting pane, the overall AmiBroker window, and the font for the axis, to see how the pixel values change. Be aware that opening multiple chart panes and/or windows will also change pixel values. Also, probably to ensure that the first bar shows, the left edge of your charting pane is located at the third pixel, while actual bars start plotting at the fifth pixel. This means that trying to overlay the Blue line to determine the left boundary will be offset to the left by two pixels. To use the code below copy it to a new Indicator formula window and click Apply.

function gfxPlotHLineYPixelsColor )
{
    global pxwidth;
    GfxSelectPenColor ) ;
    GfxMoveTo0YPixels );
    GfxLineTopxwidthYPixels );
}

function gfxPlotVLineXPixelsColor )
{
    global pxheight;
    GfxSelectPenColor ) ;
    GfxMoveToXPixels);
    GfxLineToXPixelspxheight );
}

GraphXSpace 0;
SetChartOptions2chartHideQuoteMarker );

pxwidth Status"pxwidth" );
pxheight Status"pxheight" );

BottomMargin Param"Bottom Margin"140100 );
gfxPlotHLinepxheight BottomMargin);

RightMargin Param"Right Margin"770100 );
gfxPlotVLinepxwidth RightMargin);

LeftMargin Param"Left margin"30100 );
gfxPlotVLineLeftMargin);

TopMargin Param"Top Margin"50100 );
gfxPlotHLineTopMargin);

Title="\n"+
"   Pixel width: "+NumToStr(pxwidth,1.0,False)+"\n"+
"  Pixel height: "+NumToStr(pxheight,1.0,False)+"\n"+
"   Top marging: "+NumToStr(TopMargin,1.0,False)+"\n"+
" Right marging: "+NumToStr(RightMargin,1.0,False)+"\n"+
"Bottom marging: "+NumToStr(BottomMargin,1.0,False)+"\n"+
"  Left marging: "+NumToStr(LeftMargin,1.0,False);

Moving Low Level Graphics (GFX) Objects on your Charts

When drawing gfx objects on your charts, you can move them around by applying an X and Y offset to the coordinates of the object. The following code shows a simple example of how you can move a square object around your chart.

function DrawButtonTextx1y1x2y2BackColor )
{
    GfxSetOverlayMode);
    GfxSelectFont"Tahoma"12800 );
    GfxSelectPencolorBlack );
    GfxSetBkMode);
    GfxSelectSolidBrushBackColor );
    GfxSetBkColorBackColor );
    GfxSetTextColor);
    GfxRectanglex1y1x2y2 );
    GfxDrawTextTextx1y1x2y232 );
}
 
Yoffset Param"Button Row Offset (px)"5002000);
Xoffset    Param"Button Column Offset (px)"5002000);
CellHeight Param("Cell Height",20,5,200,5);   
CellWidth Param("Cell Width",120,5,200,5);  
DrawButton"TEST"XoffsetyoffsetXoffset CellWidthyOffset CellHeightcolorBlue );

If you only need to adjust the position of your object once in awhile, this simple method may serve best. However, when you want to move an object on the fly, without having to open the Param window each time, you can do this by registering the coordinates of your first click on the object and then move the object to the location of your next mouse click. The program below shows how to do this. It also shows you how to detect whether your click is within the object area (see the CursorInField variable).

To facilitate readability, the code has not been optimized. You can see in the captures below the code only adds about 4 microseconds to your execution time. It is doubtful that code optimization will have much effect on the overall execution time of your code. To test this code you Apply it to an indicator. At First Apply, it will display the Yellow Square with the word TEST in it:

clip_image002

When you click within the yellow square with your Left mouse button, it will turn Red:

clip_image004

The red color indicates that a move is in progress and that the next click will determine the location to which the object will be moved. After the move is completed, the object will return to its default yellow color:

clip_image006

In a real application, you may want to add some protective code that disables other mouse click activated actions while a move is in progress. Such code would be disabled while the object is Red and the variable MoveInProgress is true.

While the code could be (and initially was) written to move the object by holding down the Left Mouse button and dragging the object in the conventional way, the one-second minimum chart refresh rate makes this extremely awkward and slow to work with. For diagnostic purposes, a Reset button in provided in the Param window. Clicking this button will move the yellow square to its default coordinates.

function DrawButtonTextx1y1x2y2BackColor )
{
    GfxSetOverlayMode);
    GfxSelectFont"Tahoma"12800 );
    GfxSelectPencolorBlack );
    GfxSetBkMode);
    GfxSelectSolidBrushBackColor );
    GfxSetBkColorBackColor );
    GfxSetTextColor);
    GfxRectanglex1y1x2y2 );
    GfxDrawTextTextx1y1x2y232 );
}


ParamSqrSize    100;
Reset ParamTrigger"Reset Coordinates""RESET" );
xOffset  StaticVarGet"xOffset" );
YOffset  StaticVarGet"YOffset" );

if ( IsNullxOffset ) OR IsNullyOffset ) OR Reset )
{
    StaticVarSet"xOffset"20 );
    StaticVarSet"yOffset"20 );
    X1 XOffset;
    Y1 YOffset;
    X2 XOffset ParamSqrSize;
    Y2 YOffset ParamSqrSize;
    StaticVarSet"X1"X1 );
    StaticVarSet"X2"X2 );
    StaticVarSet"Y1"Y1 );
    StaticVarSet"Y2"Y2 );
    StaticVarSet"MoveinProgress"False );
}

X1 NzStaticVarGet"X1" ) );

X2 NzStaticVarGet"X2" ) );
Y1 NzStaticVarGet"Y1" ) );
Y2 NzStaticVarGet"Y2" ) );
LButtonTrigger    GetCursorMouseButtons() == 9;
MousePx  NzGetCursorXPosition) );
MousePy  NzGetCursorYPosition) );

CursorInField MousePx X1 AND MousePx X2 AND MousePy Y1 AND MousePy Y2;

MoveInProgress NzStaticVarGet"MoveInProgress" ) );

BackColor colorYellow;

if ( NOT MoveInProgress )
{
    if ( LButtonTrigger AND CursorInField )
    {
        StaticVarSet"BackColor"colorRed );
        StaticVarSet"DownPx1"MousePx );
        StaticVarSet"DownPy1"MousePy );
        StaticVarSet"MoveinProgress"True );

    }
}
else
    if ( LButtonTrigger )
    {
        StaticVarSet"BackColor"colorYellow );
        DownPx1 StaticVarGet"DownPx1" );
        DownPy1 StaticVarGet"DownPy1" );
        xMove MousePx DownPx1;
        yMove MousePy downPy1;
        PrevxOffset StaticVarGet"xOffset" );
        PrevYOffset StaticVarGet"YOffset" );
        xOffset PrevxOffset xMove;
        yOffset PrevYOffset yMove;
        StaticVarSet"xOffset"xOffset );
        StaticVarSet"yOffset"yOffset );
        StaticVarSet"MoveinProgress"False );
    }


BackColor NzStaticVarGet"BackColor" ), colorYellow );

xOffset StaticVarGet"xOffset" );
YOffset StaticVarGet"YOffset" );
X1 XOffset;
Y1 YOffset;
X2 XOffset ParamSqrSize;
Y2 YOffset ParamSqrSize;
StaticVarSet"X1"X1 );
StaticVarSet"X2"X2 );
StaticVarSet"Y1"Y1 );
StaticVarSet"Y2"Y2 );
DrawButton"TEST"x1y1x2y2BackColor );

Edited by Al Venosa.