How can I draw a rectangle onto an image of a ScrollBox? lazarus / 39-Graphics ---------------------------------------------------------------------- Tomi · 2024-04-08 13:34 Hello! I have a scrollbox with one or more images on it in my program. I would like to draw a rectangle onto image on place of mouse click. I tried achieve this directly in OnPaint event of the scrollbox and then of the image, but I failed with my attempts: I could not see the rectangle. So, I have the scrollbox, and some images: image[0], image[1], etc. on it. When I clicking one of these images, the program performs the Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i tile[i].invalidate;command and in the OnPaint event of the images I try draw a rectangle with simply the DrawFocusRect() function or so: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i therectangl e:=TImage.Create(tile[actualtileimage]); therectangle.Left:=tile[actua ltileimage].Left+4; //As a test, now I add the upper left corner coordinates of the image, therectangle.Top:=tile[actualtileimage].Top+4; //but I want to change this to mouse coordinates.  therectangle.Width:=tilewidth; therectangle.Height:=tileheight;          therectangle.canvas.drawfocusrect(Rect(therectangle.Left,therecta ngle.Top,therectangle.Left+tilewidth,therectangle.Top+tileheight)); but nothing happened. Maybe I add wrong coordinates or the scrollbox or the images are always cover the rectangle? How can I solve this problem, if it possible? ---------------------------------------------------------------------- TRon · 2024-04-08 18:41 My apologies upfront in case I've read this wrong but I do not see a scrollbox canvas to which you draw onto ? You seem to draw to the rectangle canvas but the code that draws the image to the scrollbox or the image placed on the scrollbox seems missing ? ---------------------------------------------------------------------- Tomi · 2024-04-09 12:23 Hello TRon! Originally I tried draw directly onto the canvas of the ScrollBox, but nothing happened, therefore I tried with that code I wrote in the first post. So, I tried these in the OnPaint event of the ScrollBox: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i canvas.draw focusrect(Rect(imgscrollbox.Left+20,imgscrollbox.Top+20,imgscrollbox.L eft+20+tilewidth,imgscrollbox.Top+20+tileheight)); //As a test value, I tried draw at its corners +20and the other: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i canvas.draw focusrect(Rect(ScreenToClient(mouse.cursorpos).X+imgscrollbox.HorzScro llBar.Position,ScreenToClient(mouse.cursorpos).Y+imgscrollbox.VertScro llBar.Position-imgscrollbox.Top,ScreenToClient(mouse.cursorpos).X+imgs crollbox.HorzScrollBar.Position+tilewidth,ScreenToClient(mouse.cursorp os).Y+imgscrollbox.VertScrollBar.Position- imgscrollbox.Top+tileheight)); ---------------------------------------------------------------------- Handoko · 2024-04-09 14:56 If you have a scrollbox and 2 images inside it, you need to correctly decide which canvas you want to draw on. Try this code below. You can't see a rectangle if you do not put the coordinates correctly. So I use lines instead. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, Forms, Controls, Graphics, ExtCtrls, StdCtrls; type   { TForm1 }   TForm1 = class(TForm)    chkScrollBox: TCheckBox;    chkImage: TCheckBox;    Image1: TImage;    ImageList1: TImageList;    ScrollBox1: TScrollBox;    procedure CheckBoxChange(Sender: TObject);    procedure Image1Paint(Sender: TObject);    procedure ScrollBox1Paint(Sender: TObject);  private    procedure DrawLines(C: TCanvas);  end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.ScrollBox1Paint(Sender: TObject);begin  if chkScrollBox.Checked then    DrawLines(ScrollBox1.Canvas);end; procedure TForm1.Image1Paint(Sender: TObject);begin  if chkImage.Checked then  DrawLines(Image1.Canvas);end; procedure TForm1.CheckBoxChange(Sender: TObject);begin  ScrollBox1.Invalidate;end; procedure TForm1.DrawLines(C: TCanvas);const  len = 1000; // length of the linevar  r: Single; // angle in radianbegin  r := 0;  repeat    C.Line(0, 0, Round(Cos(r)*len), Round(Sin(r)*len));    r := r + 0.02;  until r > Pi/2;end; end. ---------------------------------------------------------------------- TRon · 2024-04-09 19:10 Thank you very much for the excellent example Handoko. Quote from: Tomi on April 09, 2024, 02:23:24 pmSo, I tried these in the OnPaint event of the ScrollBox: As noted by Handoko, make sure the coordinates makes sense. So far I can see that you seem to use profound way of calculating the coordinates of a rectangle but they make no sense as they are coordinates related to other components (or even the screen). The coordinates of a rectangle inside a scrollbox/image are absolute to that same scrollbox/image. So left/top coordinate 0,0 means either 0,0 of the scrollbox canvas or the image canvas. Any coordinate/pixel that does not fit onto the canvas will be clipped. So i suspect that your rectangle isn't visible because you draw it outside the visible area of your image/scrollbox canvas. Note how Handoko's example draw lines with a length of 1000 pixels but that the lines are 'cut-off' (because the pixels of the lines that are situated outside the (available) area of the canvas are clipped). edit: For example the onpaint event from Handoko's example can be adjusted to read: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Image1Paint(Sender: TObject);begin  if chkImage.Checked then  DrawLines(Image1.Canvas);  Image1.Canvas.DrawFocusRect(Image1.Canvas.ClipRect);end; To draw a focused rectangle to the image canvas. BTW: if you speak of images did you actually mean a TImage component or an image of a (bitmap) picture that you manually draw to the canvas of the scrolbox ? attached a picture of a scrollbox withouth using components (just drawing). ---------------------------------------------------------------------- Tomi · 2024-04-10 12:53 Thank you for your answer, Handoko and TRon! Maybe I confused with using of coordinates of the components. So, I would like draw only on the images of the ScrollBox and I need to know the coordinates of the mouse on the images when I clicking on it. Most recently I tried this solution: in the mouse clicking event: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i actualtileimage:=i;tileimage[i].invalidate; and in the OnPaint event of the images: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i rbx:=tileim age[actualtileimage].ScreenToClient(mouse.cursorpos).X+tilelist.HorzSc rollBar.Position;rby:=(tileimage[actualtileimage].ScreenToClient(mouse .cursorpos).Y+tilelist.VertScrollBar.Position)- tilelist.Top;tileimage[actualtileimage].canvas.DrawFocusRect(Rect(rbx, rby,rbx+tilewidth,rby+tileheight)); Where tileimage[actualtileimage] is the actual image, on which clicking happened, and tilelist is the name of the ScrollBox. Now, the rectangle is visible in some places, but still not everywhere I clicked. But now I see Handoko's example, too... ---------------------------------------------------------------------- TRon · 2024-04-10 13:09 Quote from: Tomi on April 10, 2024, 02:53:04 pmSo, I would like draw only on the images of the ScrollBox and I need to know the coordinates of the mouse on the images when I clicking on it. Perhaps I still do not understand you correctly because if you work with TImage components that are placed on the scrollbox then my first thought would be to use the onmouseenter and onmouseleave event of the TImage component. And if you so want to then can also use Onmousemove which should provide mouse-coordinates that are "inside" the TImage component itself. But... it is still unclear to me that whenever you refer to an image if you meant a) a TImage component or b) just a drawing of an image (also referred to as a bitmap. Do you understand the difference between these two separate things ? Let me try ask in another way: if you read this in the context of your own code Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  x : ????; // <-- what type is this ?  index: integer = 0;begin  x := tileimage[index]end; then what type is x ? ---------------------------------------------------------------------- Tomi · 2024-04-10 14:25 Quote from: TRon on April 10, 2024, 03:09:51 pmthen what type is x ? I declare the tile images as follows: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type Ttileimages = class(TCustomimage)  private  publicend;(...)tileimage: array of Ttileimages; ---------------------------------------------------------------------- TRon · 2024-04-10 16:37 Quote from: Tomi on April 10, 2024, 04:25:09 pmI declare the tile images as follows: Ok, thank you. I just tested with TImage components and that works as I suspected/wrote in my previous reply. There are too many variables in your code to just show an example but, the basics are: - create the images at runtime, owner and parent being the scrollbox and after that draw something to your image(s) - then assign the (shared) TImage events: OnMouseEnter, OnMouseLeave and OnPaint - Then, if so wanted, reposition your Images on the scrollbox. I used a custom procedure that positioned all the images inside the scrollbox based on a row and column count (only placing the left and top coordinates, using a static tile width and height). - optionally set the scrollbar ranges (I did that based on the number of rows and columns icw the tile width and height but, it could be anything you want to as long as the image components themselves are positioned in the scrollbox as you want them placed. The scrollbox component itself has some provision for placement of child components as well. reposition: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure Reposition(aTileArray: array of TImage; aScrollBox: TScrollBox; aRows, aColumns: integer);const  TileWidth  = 32;  TileHeight = 32;var  Tile : TImage;  RowIdx : integer = 0;  ColIdx : integer = 0;begin  // for every tile  for Tile in aTileArray do  begin    // set bounds    Tile.SetBounds(ColIdx * TileWidth, RowIdx * TileHeight, TileWidth, TileHeight);     // update row/col indices    inc(ColIdx);    if ColIdx >= aColumns then    begin      inc(RowIdx);      ColIdx := 0;  end;  end;   // adjust ScrollBox ranges  aScrollBox.HorzScrollBar.Range := aColumns * TileWidth;  aScrollBox.VertScrollBar.Range := aRows    * TileHeight;   // invalidate ScrollBox to force a visual update  aScrollBox.Invalidate;end;  The events: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.TileMouseEnter(Sender: TObject);begin  ActiveImage := Sender as TImage;  ActiveImage.Invalidate;end; procedure TForm1.TileMouseLeave(Sender: TObject);begin  ActiveImage := nil;  (Sender as TImage).Invalidate;end; procedure TForm1.TilePaint(Sender: TObject);var  aTile : TImage absolute Sender;begin  if not assigned(Sender) then exit;  if aTile = ActiveImage    then aTile.Canvas.DrawFocusRect(aTile.ClientRect);end; Where activeImage is a variable on the form. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  TForm1 = class(TForm)  ...   public    ActiveImage : TImage;  ...  end;  That will more or less produce the same image/result as I posted in one of my previous posts. ---------------------------------------------------------------------- Handoko · 2024-04-10 17:24 I saw tile in the code, is Tomi trying to write a game map editor? I have a very basic code showing how to do it. It's lack of many important features, but it is very lightweight. Because instead of using TImage, mine uses TBitmap. https://forum.lazarus.freepascal.org/index.php/topic,43999.msg309134.h tml#msg309134 ---------------------------------------------------------------------- TRon · 2024-04-10 17:35 Quote from: Handoko on April 10, 2024, 07:24:59 pmI saw tile in the code, is Tomi trying to write a game map editor? Indeed Tomi's code seem to suggest something like that, though it could just as well be something like a frame selector or thumbnail viewer. Your code in the link looks nice btw Handoko but, as I sidenote, I personally prefer to do things like that using a separate TileManager class/object in order to not clutter the Form too much with custom methods. As a bonus a separate class can be re-used (if not implemented too specific for a one trick pony job). ---------------------------------------------------------------------- Handoko · 2024-04-10 17:49 Your suggestion is good. But my main focus when writing the code was, to make the code as simple as possible so everyone could understand it easily. ---------------------------------------------------------------------- TRon · 2024-04-10 22:45 Quote from: Handoko on April 10, 2024, 07:49:27 pmBut my main focus when writing the code was, to make the code as simple as possible so everyone could understand it easily. That is more than fair enough. It is always difficult to know when and where and to stop. @Tomi: I have attached a small (bad) example. If that isn't enough to help you out then please show more of your code. ---------------------------------------------------------------------- Tomi · 2024-04-11 12:42 Quote from: Handoko on April 10, 2024, 07:24:59 pmis Tomi trying to write a game map editor? Exactly, Handoko! I would like make a small application which can help me create a nice background image from small various images. Besides this application must be able to create a "terrain map" based on this background image. My plan with this as follows: the background image can be assembled from tiles (it's pretty much done) and the user can save this as a common BMP image. This background has an array and its elements represent the tiles with values 0 or 1, depend on the user which value set on it with mouse click from a menu list. This is the "terrain map", which can save as a common TXT file with the background BMP. Now, I working on making the selected tiles visible on the source images with a rectangle, when the user clicking on it. So, the expected result the big BMP background and a TXT file, which contains the terrain map, for example: 0,0,0,1,0,0,0,0 0,0,0,1,0,0,0,0 0,0,1,1,0,0,0,0 0,1,1,0,0,0,0,0 0,1,0,0,0,0,0,0 0,1,0,0,0,0,0,0 0,1,1,0,0,0,0,0 0,0,1,0,0,0,0,0 where the 1 numbers represent e.g. a river, which are obstacles in the game. So far I made my games from set of numbers like that, but to build up on a terrain and the background in a game the basis of it gives a rather ugly result, because the river looks like an artificial canal because of the square bends. But now, I will make a nice backround image separated from this terrain map. But I will also check your code, Handoko; maybe your application does a similar thing. And special thanks to you too, TRon, for your example. I will download it now. ---------------------------------------------------------------------- Tomi · 2024-04-11 14:35 Quote from: TRon on April 11, 2024, 12:45:00 am(...) show more of your code. Here is my piece of code about the essence of my application: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i sourcetiles: TScrollBox;(...)type Ttiles = class(TCustomimage)  private  publicend;(...)tile: array of Ttiles; First, we open the source image(s): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i if fileOpener.Execute then   begin     tilesourceimg:=TPicture.Create;   tileosurceimg.LoadFromFile(fileOpener.FileName);     //     setlength(tile,sourcetilenumber+1);     tile[sourcetilenumber]:=Ttiles.Create(sourcetiles);     tile[sourcetilenumber].Parent:=sourcetiles;     tile[sourcetilenumber].OnMouseUp:=@tilemouseclick;     tile[sourcetilenumber].OnPaint:=@tilePaint;     if sourcetilenumber>0 then tile[sourcetilenumber].Top:=tile[sourcetilenumber- 1].Top+tile[sourcetilenumber-1].height+2;     tile[sourcetilenumber].width:=tileosurceimg.width;     tile[sourcetilenumber].height:=tileosurceimg.height;     tile[sourcet ilenumber].canvas.copyrect(Rect(0,0,tile[sourcetilenumber].width,tile[ sourcetilenumber].height),tileosurceimg.bitmap.canvas,Rect(0,0,tileosu rceimg.width,tileosurceimg.height));     inc(sourcetilenumber);     freeandnil(tileosurceimg);   end; Second, we select the source image: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.tilemouseclick(Sender: TObject;  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);var i: byte;begin  if sourcetilenumber>0 then  begin    for i:=0 to sourcetilenumber-1 do    begin         if (X>=tile[i].Left) and (X<=tile[i].Left+tile[i].width) and ((ScreenToCl ient(mouse.cursorpos).Y+sourcetiles.VertScrollBar.Position)- sourcetiles.Top>=tile[i].Top) and ((ScreenToClient(mouse.cursorpos).Y+ sourcetiles.VertScrollBar.Position)- sourcetiles.Top<=tile[i].Top+tile[i].height) then         begin          if tiletoanother=true then            begin                FreeAndNil(tiletoanothercanvas);                tiletoanother:=false;            end;            tiletoanothercanvas:=Tbitmap.Create;          tiletoanothercanvas.width:=tilewidth;            tiletoanothercanvas.height:=tileheight;            tiletoanothercanvas .canvas.copyrect(Rect(0,0,tilewidth,tileheight),tile[i].canvas,Rect(X, Y,X+tilewidth,Y+tileheight));            tiletoanother:=true;          actualtileimage:=i;            tile[i].invalidate;            Break;         end;    end;  end;end; Finally, we try to draw rectangle on selected image at the mouse coordinates: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.tilePaint(Sender: TObject);begin  if tiletoanother=true then ti le[actualtileimage].canvas.drawfocusrect(Rect((mouse.cursorpos).X+sour cetiles.HorzScrollBar.Position,((mouse.cursorpos).Y+sourcetiles.VertSc rollBar.Position)- sourcetiles.Top,(mouse.cursorpos).X+sourcetiles.HorzScrollBar.Position +tilewidth,((mouse.cursorpos).Y+sourcetiles.VertScrollBar.Position)- sourcetiles.Top+tileheight));end; But the problem is that the rectangle always can be seen only on the first source image and if we have more than one tile, we can't see it on the another tiles when we clicking on it. ---------------------------------------------------------------------- TRon · 2024-04-11 22:30 QuoteHere is my piece of code about the essence of my application: Though highly appreciated there is still a lot missing so probably have some errors in my reply because I did not comprehend fully what your code does. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i tilesourcei mg:=TPicture.Create;tileosurceimg.LoadFromFile(fileOpener.FileName);.. .freeandnil(tileosurceimg) The use of tilesourceimg to load the picture(s) seem redundant to me because the image you created (TCustomImage) harbours/exposes the same loading functionality. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i tile[source tilenumber].canvas.copyrect(Rect(0,0,tile[sourcetilenumber].width,tile [sourcetilenumber].height),tileosurceimg.bitmap.canvas,Rect(0,0,tileos urceimg.width,tileosurceimg.height)); That line seems redundant to me since the image is already create with the scrollbox (sourcetiles) as owner. It is suffice to set the left and top coordinates to place it at the location that you want the tile to appear in the scrollbox. The only exception to that would be if your images need to be drawn scaled in the scrollbox but there are better solutions for that. QuoteSecond, we select the source image: Almost all of that code seems redundant to me. When you click the mouse on a tile then the sender of that method is the tile so you already know which tile is 'pressed'. I understand that you want to know the index of the image so that you can use that later on in your code but (and I forgot to make use of that in my own example as well) you can (ab)use the Tag property of the (custom) image component at creation time and set it to the current of your tiles array. And to make sure you understand, you should not (have to) paint anything in the OnMouseUp event. You can make use of the OnMouseEnter and onMouseLeave events to set/unset the active index in your tile array. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     procedure TForm1.tilePaint(Sender: TObject);    begin      if tiletoanother=true then tile[actualtileimage].canvas.drawfocusrect(Rec t((mouse.cursorpos).X+sourcetiles.HorzScrollBar.Position,((mouse.curso rpos).Y+sourcetiles.VertScrollBar.Position)- sourcetiles.Top,(mouse.cursorpos).X+sourcetiles.HorzScrollBar.Position +tilewidth,((mouse.cursorpos).Y+sourcetiles.VertScrollBar.Position)- sourcetiles.Top+tileheight));    end; If you make use of a global variable for/to the index to your tile array then it is possible to use that variable to get the 'active' tile, and use it's own canvas to draw (a focused rectangle) on top the existing image. It will only do that for the image that is active as long as you check for that. There is absolutely no use for using the mouse-coordinates there and that is even not counting that the global mouse variable uses screen coordinates (instead of relative component coordinates). Besides that you also need to take the scrollbar range (both horizontal and vertical) into account in order to retrieve the absolute coordinates of the image on the scrollbox. In my example code I used: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TTileManager.DoTilePaint(Sender: TObject);var  Item: TTileManager.TTile absolute Sender;begin  if not assigned(Sender)    then exit;   if Item = ActiveTile // <-- this item (tile) is active so we want to draw a rectangle    then Item.Canvas.DrawFocusRect(Item.Canvas.ClipRect); // <-- use the image's own canvas and dimensionsend;  I would really advise to study my example a bit longer and play with things. You can easily do that by adding a TMemo component to the form (form, not the scrollbox) align it to a side of the form (leave/set the scrollbox to client (again)) and use multiple memo1.append('some message') at different places to see/identify what the LCL actually does behind the scenes. QuoteBut the problem is that the rectangle always can be seen only on the first source image and if we have more than one tile, we can't see it on the another tiles when we clicking on it. Most likely the coordinates are a bit off but you also seem to be drawing at the wrong location(s) at the wrong time(s). imho your image-click-draw related code seems much too complicated and can be simplified (a lot). Please don't be too upset with my comments, as they are only meant to try make you code better/cleaner. In my experience it works a lot quicker when not beating things around the bush. ---------------------------------------------------------------------- Tomi · 2024-04-12 12:32 TRon: So, I need to remove some unnecessary lines from the code and rearrange slightly. Yes, I tend to overcomplicate things sometimes. Maybe it stems from my lack of knowledge. But I'm starting to understand what you wrote, I just don't know how most of the controls of Lazarus work yet. E.g. the ScrollBox. Jamie: I didn't know TDrawGrid, but I'll look into it now. I don't know much about possibilities of Lazarus yet... ---------------------------------------------------------------------- TRon · 2024-04-12 16:36 Quote from: Tomi on April 12, 2024, 02:32:32 pmTRon: So, I need to remove some unnecessary lines from the code and rearrange slightly. Well, it is not a requirement of course but to me it seems that you do not fully grasp the concept of placing components on a scrollbox. My example has the intention to show two different methods: 1 - using the scrollbox's own automated solution to contain multiple components (timage in our example) 2 - pretend that there are components on the scrollbox by only drawing them (the actual bitmap of the timage) to the surface of the canvas. My example used tfakeimage for that. QuoteYes, I tend to overcomplicate things sometimes. Maybe it stems from my lack of knowledge. Which is not a problem. The only difference is that whenever I do not know about a component I create s small self contained example and start playing with the component sometime just twiddling with the properties in order to see what happens. That is also why I do not know details about all components that are available simply because I have not been able to play with them yet as that takes an enormous amount of time and by the time you are up to date about a component then the component gets a new update where it is possible things work differently or behaviour changed. QuoteBut I'm starting to understand what you wrote, I just don't know how most of the controls of Lazarus work yet. E.g. the ScrollBox. See above 2 answered paragraphs... at least I hope that will be able to shed a light. And in case that was not enough then ask. I do not know a lot about tscrollbox either (for example I do not know much about the behaviour of the (auto) arrangement of child components). I learn as I go along or someone asks a question or shows an example. Just keep in mind that it is very difficult to look into someone else his personal code and line of thought by only using some small snippets and not having the full source-code in front. That is why it is usually faster to play with a small example that can be shared. ---------------------------------------------------------------------- KodeZwerg · 2024-04-12 21:54 Very good answers so far, I personal would replace TImage with TPanel (remove default borderstyle) and draw whatever on it, utilise TPanels click/mouse over/etc event to simplify the progress. ---------------------------------------------------------------------- Tomi · 2024-04-13 08:54 Okay, I start to understand! As TRon mentioned ("The coordinates of a rectangle inside a scrollbox/image are absolute to that same scrollbox/image. So left/top coordinate 0,0 means either 0,0 of the scrollbox canvas or the image canvas. Any coordinate/pixel that does not fit onto the canvas will be clipped. So i suspect that your rectangle isn't visible because you draw it outside the visible area of your image/scrollbox canvas.") I used wrong coordinates, because I didn't take it into account. So, when I click into a tile image in the ScrollBox, the coordinates begin to Left and Top corner of the image and that is the 0,0 point and when I want to draw e.g. in this corner, I have to add this code: Rect(0,0,tilewidth,tileheight). On the basis of it, this is the solution: Code: [Select]procedure TForm1.tilePaint(Sender: TObject); begin   if tiletoanother=true then      tile[actualtileimage].canvas.drawfocusrect(Rect(ScreenToClient(mo use.cursorpos).X+sourcetiles.HorzScrollBar.Position,tile[actualtileima ge].ScreenToClient(mouse.cursorpos).Y,ScreenToClient(mouse.cursorpos). X+sourcetiles.HorzScrollBar.Position+tilewidth,tile[actualtileimage].S creenToClient(mouse.cursorpos).Y+tileheight)); end; ---------------------------------------------------------------------- Handoko · 2024-04-13 09:42 Quote from: Tomi on April 13, 2024, 10:54:55 amOkay, I start to understand! .... So left/top coordinate 0,0 means either 0,0 of the scrollbox canvas or the image canvas. Correct! And that's the reason why my example used multiple lines that point to the (0, 0) and have length = 1000 pixels so you can know where is the origin coordinate. Unfortunately, you didn't get it. ---------------------------------------------------------------------- Tomi · 2024-04-14 08:04 The bumpy road of programming... It was a bit difficult for me to understand the characteristics of a modern development tool with some Turbo Pascal background behind me, but I learned a lot again - thanks for it! ---------------------------------------------------------------------- TRon · 2024-04-14 17:44 Quote from: Tomi on April 13, 2024, 10:54:55 amOkay, I start to understand! As TRon mentioned ("The coordinates of a rectangle inside a scrollbox/image are absolute to that same scrollbox/image. So left/top coordinate 0,0 means either 0,0 of the scrollbox canvas or the image canvas. Any coordinate/pixel that does not fit onto the canvas will be clipped. So i suspect that your rectangle isn't visible because you draw it outside the visible area of your image/scrollbox canvas.") Indeed and as pointed out by Handoko. Things depend on how you implement them and for that it is advisable to use the road of the least resistance (you seem to ignore that road) :-) Thus, whenever you use mouseevents such as OnMouseUp, OnMouseDown and/or OnMouseMove the x and y coordinates that are passed along those events are those coordinates that belong to the component which "fired" the event. The same goes for OnMouseEnter and OnMouseLeve in that the events are fired when the corresponding component fires the event(s). So the x and Y coordinates of a OnMouseClick for a TImage correspond to the x and y coordinates within that component, top-left corresponding to x,y of 0,0. At the same time when that event is fired you also know which image was clicked because the image that was clicked sens a pointer of itself by using the sender parameter. Thus as long as you attach a handler to the OnMouseClick event you do not even have to check for mouse coordinates whether or not they belong to a particular Image component because you already know which image was clicked. The same logic can be applied to TScrollbox. These mouseevents are only fired when the mouse event happens inside the scrollbox and also here the x/y coordinates correspond to the left top of the component itself, thus 0,0 being left top onside the scrollbox. But, there is one small thing differs in comparison to other components and that is that it is a container with a scrollable view. The x and y coordinates passed in these mouse events are those of the visible area of the scrollbox (the part of the scrollbox where you are looking at at that exact moment). The issue there is that the (left-top) coordinates of the components themselves that are placed inside the scrollbox are not corresponding to their real coordinates inside the scrollbox because the possibility arise that the current view is 'shifted' from the original 0,0 top left coordinates of the client-area of the scrollbox. Ergo you need to compensate for that using the scollbar range and only when you want to 'calculate' the placement of a particular component that is situated inside that scrollbox. As you can see for yourself the latter only applies when you do not physically placed the components inside the scrollbox (whether done manually by using the visual designer or at runtime) but in case you only draw something to the surface (client area) of the scrollbox. Then there would be need to do manually check for mouse coordinates. So I believe that you still do not fully grasp the concept because the last solution shown is doing all kinds of calculations and there is absolutely no need for any calculation whatsoever. The event happened, and it happened inside the clicked component thus as long as every timage placed on the client area of the scrollbox  has that event assigned then it becomes easy. This is also why I opted to create a small test project to tinker with because when a scrollbox is placed onto a form, and the visual designer is used to place several image components on the client area of the scrollbox, then assign a (single) mouse event to/for all these images then you can are able to come up with the same conclusion in about 2 seconds. Just saying ---------------------------------------------------------------------- Handoko · 2024-04-15 06:01 Quote from: Tomi on April 14, 2024, 10:04:38 amIt was a bit difficult for me to understand the characteristics of a modern development tool with some Turbo Pascal background behind me, but I learned a lot again - thanks for it! I downloaded and tested TRon's code, it should offer what you need to know. But I think that is the disadvantage of Object Oriented Programming, OOP often makes simple thing hard to understand especially in the eyes of non-skilled programmers. Here I wrote a super simple demo showing how do draw focus rectangle on clicked item using TScrollBox + TImage: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, ExtCtrls; const  ColumnCount = 5;  RowCount    = 4;  ColumnWidth = 100;  RowHeight   = 70; type   { TForm1 }   TForm1 = class(TForm)    Image1: TImage;    ImageList1: TImageList;    ScrollBox1: TScrollBox;    procedure FormCreate(Sender: TObject);    procedure ItemClick(Sender: TObject);    procedure ItemPaint(Sender: TObject);  private    FGrid:         array [0..RowCount-1, 0..ColumnWidth-1] of TImage;    FSelectedItem: TImage;  end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject);var  NewImage: TImage;  i, j:     Integer;begin  ScrollBox1.Anchors := [akLeft, akRight, akTop, akBottom];  FSelectedItem      := nil;  for i := 0 to RowCount-1 do  for j := 0 to ColumnCount-1 do    begin      NewImage            := TImage.Create(ScrollBox1);      NewImage.Parent     := ScrollBox1;    NewImage.Left       := j * ColumnWidth;      NewImage.Top        := i * RowHeight;      NewImage.Width      := ColumnWidth;      NewImage.Height     := RowHeight;      NewImage.Images     := ImageList1;      NewImage.ImageIndex := 0;      NewImage.OnClick    := @ItemClick;      NewImage.OnPaint    := @ItemPaint;      FGrid[i, j]       := NewImage;    end;end; procedure TForm1.ItemClick(Sender: TObject);begin  if not(Sender is TImage) then Exit;  FSelectedItem := TImage(Sender);  ScrollBox1.Invalidate;end; procedure TForm1.ItemPaint(Sender: TObject);var  aRect: TRect;begin  if not(Sender is TImage) then Exit;  if not(Sender = FSelectedItem) then Exit;  aRect := FSelectedItem.ClientRect;  aRect.Inflate(-2, -2);  FSelectedItem.Canvas.DrawFocusRect(aRect);end; end. Quote from: Tomi on April 12, 2024, 02:32:32 pmI tend to overcomplicate things sometimes. Maybe it stems from my lack of knowledge. There are usually more than one ways solving the same problem in programming. Your case can be done using TBitmap, TDrawGrid, TPanel, TScrollBox or whatever. It's hard to say which one is better, just choose the one you like. ---------------------------------------------------------------------- jcmontherock · 2024-04-15 14:00 Answer to the first question: ---------------------------------------------------------------------- Handoko · 2024-04-15 15:43 Nice! I downloaded and test your ScrollPaint.zip but I believed you provided the wrong code. ---------------------------------------------------------------------- TRon · 2024-04-15 16:20 Quote from: Handoko on April 15, 2024, 08:01:12 amI downloaded and tested TRon's code, it should offer what you need to know. But I think that is the disadvantage of Object Oriented Programming, OOP often makes simple thing hard to understand especially in the eyes of non- skilled programmers. It does offer that and perhaps you are right that OOP can be difficult for someone unfamiliar. I my defense at the time I created the example I did not know whether TS was only painting on the scrollbox or actually placing components on it (which makes the code even more difficult to understand because I tried to make generic solution that was able to do both). QuoteHere I wrote a super simple demo showing how do draw focus rectangle on clicked item using TScrollBox + TImage: Thank you for that example Handoko  It is what I had in mind (though my code uses hovering over an image to draw the focused rectangle) and it can't get any more simpler than that Quote from: Handoko on April 15, 2024, 05:43:47 pmI downloaded and test your ScrollPaint.zip but I believed you provided the wrong code. Indeed that looks a whole of a lot like the first example code that you posted Handoko... @jcmontherock: you might perhaps want to (double) check that. ---------------------------------------------------------------------- Tomi · 2024-04-16 08:59 Woa... thanks for deep explanation, experts! Handoko, the problem was in my case that there was one (or more) big source image on which I had to draw a certain size rectangle to indicate the clipping area. So, not the exact client rect, but a smaller, flexible area, depend on the size of tiles used for assembly the other image, which is the terrain BMP. But the important thing is I have to add the name of the component not only at the draw command, but also at the coordinates of drawing, as you did in your example: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i aRect := FSelectedItem.ClientRect;where FSelectedItem is the name of the TImage component. But I will publish my application if it will be done and you will see better its structure. ---------------------------------------------------------------------- jcmontherock · 2024-04-16 09:08 Yes, sorry... ---------------------------------------------------------------------- Handoko · 2024-04-23 05:30 Quote from: Tomi on April 16, 2024, 10:59:57 amBut I will publish my application if it will be done and you will see better its structure. Any news about the application? I am curious to see what you're currently working on. Some screenshots maybe.