OnMouseMove

This very useful event appears for some of component, where most important are picture and the Form itself. Its handling can be assigned by double-click in the free line (labeled by appropriate function name) in the Object Inspector (see picture on right, this is the case of the new, i.e. empty form). It will move us to the newly created procedure in the program source:

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  begin
  
  end;

The most interesting variables, passed for us by Windows, are the X and Y coordinates. Their meaning is the position of the cursor and they are relative to the object (i.e. Form or Image). The X and Y coordinates in Delphi begins in top left corner, it means, that the Y axis is negative to the standard mathematic denomination. For a Form, it is relative to useable area, which is so called "client" - we have the Form1.ClientWidth and the Form1.ClientHeight properties in the Form structure.

The first task: If we have a coordinates, we can display them. Because we have not yet any Label or Edit, we can write them to the Caption property of the Form (which is more typically used for the program name). To display them, we have to convert them to the text; it could be useful add a text, like "x = ". For joining more text into one string in the Pascal, we can just add them, using the "+" sign:

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  begin
    Form1.Caption := 'x = 'IntToStr(X) + ', y =' + IntToStr(Y);
  end;

Please, test it, check, where is the lowest and the biggest value of coordinates. We need to note, that the video memory starts the same way, lowest bytes in the upper left corner of the screen (in the time of crt displays, any other way would be much more complicated to construct). When we will draw a graph, we will have to use negative Y values to draw lines.

How to use this event? One of possibilities is a button, which is small, but could be easily hit, because it is always on the position of the mouse. For this task, make your Form bigger, and place anywhere (relatively small) button. If its name will be Button1, we can add following handling (to the Form, not to the Button!):

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  begin
    Form1.Caption := IntToStr(X) + ',' + IntToStr(Y);
    Button1.Left := X - Button1.Width div 2; {move cursor to the centre of Button}
    Button1.Top := Y - Button1.Height div 2;
  end;

When we move cursor from the button, the button will jump so that the cursor will be again on the button (till the cursor will be on the region of the form). This kind of a button is really easy to press, so it could be useful for example for a question, if a user like our program.

Sets

Only short introduction into the set commands: In the var chapter we can define a variable of the type "set of", which is defined as list of elements, it can contain. A good example is variable, which contain any day of week; the type should be defined at first, than used for variable:

type
  t1 : (Mo,Tu,We,Th,Fr,Sa,Su);
  t2 : set of (Mo,Tu,We,Th,Fr,Sa,Su);

var
  a : t1;
  b : t2;   {or " set of t1; "}

In this example, the first variable ("a") can has any value from this list, it can be assigned (for example) by command:

a := We;

This is special type of the ordinal variable, and we are not interested about it.

The second variable ("b") is the set; it can contain any combination of values, including none. For the assignment, the values should be written in squared brackets: [ ] :

b:=[We];

or

b:=[Mo,We,Su,Sa];

In the both cases, "a" and "b", it is important to emphasize, that this types are problematic to print - the values we see while programming, are completely lost by translation. The first type will be substituted by integer (with a value like Mo=1, Tu=2, We=3, etc.), while the second (the set) will b substituted by binary number, each element as one bit of variable (Mo=20, Tu=21, We=22,..., and elements will be added together - it means, that there could be any set of elements, each only once). The empty list could be assigned by:

b:=[];

If we have two of these variables, we can sum them, using the plus sign:

b:=[Mo,We,Su,Sa];
b:=b+[We,Fr];

(the result is Mo,We,Su,Sa,Fr). For us, the most important operation is the test, if one of elements is present. For this, we have the in operator:

if We in b then ...

Now, why we are interested in sets:

The Shift variable from the OnMouseMove procedure is a set, which can contain:

ssShift the Shift key was pressed in time of event
ssCtrl the Ctrl key was pressed in time of event
ssAlt the Alt (left) key was pressed in time of event
ssLeft the left mouse button was pressed in time of event
ssDouble the doubleclick was detected just before this event

This list is not complete. The event itself is mouse move, so there is no way to decide, how long the keys was pressed before.

The "ss" stands for ShiftState. In the next example, we will test, if the left mouse button is pressed.

Canvas

There is possible to paint or draw to all objects, which contain Canvas property. Canvas is so called real-time property, so it exists, only while program is running (cannot be set by the Object Inspector or be prepared any way before program starts).

Canvas use object oriented approach. If you like to do anything with the Canvas, you have to use a set of procedures, which itself are part of the Canvas object. For example, if we like to draw a line on the Canvas, which is a part of the Form1, we can use the procedure Form1.Canvas.Lineto with correct parameters.

We can create very simple drawing program, if we combine the OnMouseMove event and the Canvas property.

Imagine, that we will make a dot on position of the cursor. If we move the cursor, we can create line. Problem is, that we would have to move very slowly to keep line solid. Better solution is to create line between two OnMouseMove events. For making reasonable picture, it is useful to draw the line, only if the left mouse button is pressed. It could be checked by the in condition. If the left mouse button will be pressed, we will draw a line, in other cases we will just a move a cursor:

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  begin
    Form1.Caption := IntToStr(X) + ',' + IntToStr(Y);
    if ssLeft in Shift then Form1.Canvas.LineTo(X,Y)
                       else Form1.Canvas.MoveTo(X,Y);
  end;

It should be emphasized, that before else is not a semicolon (this is completely only one command). So, try, if now you are able to draw a dwarf.

Image

This is not typical to draw on the Form itself and it is not supported by Windows - for example, if a form disappear and is redrawn again, all the Canvas will be cleared. For reasonable work with picture, the best solution is to use the TImage component (from the "Additional" ribbon). Each Image has not only Canvas, but the Picture property as well (in the real structure, this Canvas is only link, Image contains Picture, it contains Bitmap, and the most important part of the Bitmap is this Canvas).

There are two important methods implemented in the Picture property, LoadFromFile and SaveToFile, which allow us to load picture from a file and save it (only the BMP format is supported, for work with a jpeg and a gif picture is appropriate unit needed. For the file loading and saving, it is useful to use the OpenDialog and the SaveDialog component again.

In the firs example, please add an OpenDialog, Savedialog and a Image component to your Form, set the correct filter for those dialogs (for the *.BMP type), add two buttons (preferably with a Caption like "Load" and "Save") and use it in your program.

If you didn't rename your components from the default, you can use among your lines this command:

if OpenDialog1.Execute then Image1.Picture.LoadFromFile(OpenDialog1.Filename);

The save dialog will be used in the same way. By the way, you can try to use the LoadPictureDialog and compare it.

Another important property of the Image is the Align with the default value alNone. It could be used, if the picture should change its size with the Form window. If you use the alClient, it will fill the "Client" area in the Form permanently. Buttons in this case can be over the Image, or in separate (the second) Form.

Line color and thickness

Another important property of the Canvas tells, how the line will look: the Pen. Most important Pen properties are Color and Width. If you like to paint shapes, the same function has the Brush property.

As the first example, we will set the pen thickness. For setting small integer values is very useful the SpinEdit component (ribbon "Samples"):. After place to the Form, set (using Object Inspector) the minimal and maximum value, and the initial Value itself (minimal and initial as 1, maximum about 255). Using doubleclick on the component, we can generate event handling procedure:

procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
  Image1.Canvas.Pen.Width := SpinEdit1.Value;
end;

For color setting, we can use the ColorDialog component from the Dialogs ribbon. For use it, we have to add another button and than we can use the following phrase:

procedure TForm1.Button2Click(Sender: TObject);
begin
  if ColorDialog1.Execute
  then Image1.Canvas.Pen.Color := ColorDialog1.Color;
end;

If we set the Brush property, we can paint for example a rectangle; the "Rectangle(10,20,m,mno);" command will draw a rectangle from point x=10, y=20, to point x=m and y=mno (if you like to use variables or functions, the result has to be integer type - in other cases, the round or the trunc function should be used.

If you like to clear a drawing area, you should use the rectangle as well. In the following example, the Brush Style is set to complete fill use Brush Color (this type is bsSolid) and the color to white. Note, that the border of the rectangle will be in white color as well (rectangles painted by this function has border line, which could be different color):

procedure TForm1.Button3Click(Sender: TObject);
begin
  Image1.Canvas.Pen.Color := clWhite;
  Image1.Canvas.Brush.Color := clWhite;
  Image1.Canvas.Brush.Style := bsSolid;
  Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height);
end;

If you do not like to write "Image.Canvas." on each line (if your Image object is on other form, then even like Form2.Image1.Canvas."), you can use the with directive (this is directive for the translator, translated code in Delphi will be the same in both cases). This directive means, that the compiler will look for every element in this context as the first, but if it is not there, it will look to other places as well. There is no reason to use it for only one (following) command, so it is usually used together with the begin - end brackets. Using this, we can rewrite the previous command to the form:

procedure TForm1.Button3Click(Sender: TObject);
begin
  with Image1.Canvas do begin
    Pen.Color := clWhite;
    Brush.Color := clWhite;
    Brush.Style := bsSolid;
    Rectangle(0, 0, Image1.Width, Image1.Height);
  end;
end;

In this case, there will be probably better to use Ctrl+C and Ctrl+V for the program writing, but sometimes it could create program more readable. Please note, that the with directive could be nested.

TRect

When you are reading the method list of a Canvas in the help you will see two functions - for an empty and a filled rectangle, the FrameRect and the FillRect, both required to assign the upper left (x1,y1) and the bottom right (x2,y2) corner as a single structure using special data type, the "TRect":
procedure FrameRect(const Rect: TRect);
But there are no problem to create it, you don't need even to define a variable of this type, because there is a specific converse function, "Rect", dedicated to convert four integer numbers into this structure, so we can just write (i.e.):

Image1.Canvas.FrameRect(Rect(33,35,75,77));

... this will draw a one-pixel wide frame using this coordinates. For drawing the rectangle by wider line you need to set the Canvas.Pen.Width property before drawing the rectangle; if you are like to draw only border of the rectangle, I could recommend you to use the Rectangle procedure and set the Canvas.Brush.Style property to bsClear, when the inside remains untouched (both are possible).

Pixels

The Pixels are one of key features of the Canvas. It contains each single point of the Canvas and -obviously- is accessible only after program is executed (run-time only, cannot be changed using the Object Inspector). It is not accessible for all of this kind of object, but it works for an Image and we can try it.

The Pixels is an array of the Canvas dimensions, each element is TColor type, it means - contains color of this point. We can change each particular point as we like. For example, the next procedure, invoked by the Button7 pressing, will create inverted (negative) picture, because of the xor function with a "-1" second argument (11111111 11111111 11111111 binary) will invert each pixel separately (in real, it is not a true negative, because in this case each color of each point should be subtracted from 255, creating binary complement - the xor function will create so called "unary" complement).

procedure TForm1.Button7Click(Sender: TObject);
  var
    i,j : integer;
  begin
    for i:=0 to Image1.Width do
      for j:=0 to Image1.Height do
        Image1.Canvas.Pixels[i,j]:=
          Image1.Canvas.Pixels[i,j] xor -1;
  end;

If we like to work with each color separately, we have to convert the TColor type to its component first, as in the next example:

procedure TForm1.Button7Click(Sender: TObject);
  var
    i,j,c : integer;      {4 bytes}
    r,g,b : byte;
  begin
    for i:=0 to Image1.Width do
      for j:=0 to Image1.Height do begin
        c:=Image1.Canvas.Pixels[i,j];
        b:=c and $FF;
        c:=c shr 8;
        g:=c and $FF;
        c:=c shr 8;
        r:=c and $FF;
        r:=128 + r div 2;    {any operation with r,g,b could be placed here}
        c:=r*$10000+g*$100+b;
        Image1.Canvas.Pixels[i,j]:=c;
      end;
  end;

For color component separation, there are used masking and the shift function. In real, the color is in the integer value saved by component this way:

rrrrrrrr-gggggggg-bbbbbbbb

, each letter represents one bit (r-red. g-green, b-blue). Easiest way to get the blue component os the masking - the and function will leave the "ones" only in position, where are 1 in the second parameter (here called "mask"). The "$" sign in the Pascal means, that the following parameter will be written in hex; $FF means 11111111 binary. If we mask the rest of bites (using "0" in the second parameter of and), we will separate the blue command. Then we will shift all number by eight bits (bits on the right will be lost by the shr shift) and we can mask the next color (green). To complete the recalculated color components back to number, multiplication is used. In the Borland Turbo Pascal and its ancestors, there are no difference between multiplication by constant, which could be written as power of 2, and the left shift - in the both cases, after translation, the left shift will be used (the shift operation is very fast for the computer, mainly in compare with the typically very slow multiplication).

The last note about the canvas of the Form component - the pixels are saved only in the video memory. If you will read the pixels from this memory and you will have for example only 8 or 16 bits per pixel (256 or 64K colors), the rest information will be lost after writing to this memory (and thus cannot be read).