Scrolling TImage horizontally or vertically - delphi

I am writing 2-3 Trees application in Lazarus for my school project.
Everything's done, now playing with GUI (I get the same number of points for good GUI as I do for a good etc. Insert function, which is weird but nvm).
When I have like 10+ nodes in the tree, my 300*200 image size just isn't large enough.
I would like to have an TImage component which would be like 300*200 on the TForm, but it would be like 10000 * 10000 really and you could scroll in it.
Is it even possible?
Thanks
EDIT TO MAKE THE QUESTION CLEARER
A 2-3 Tree is a data structure. When drawn on a paper to see how it works, it looks like this http://www.cosc.canterbury.ac.nz/research/RG/alg/tree23.gif
As a real noobie in lazarus/delphi (have to do it in lazarus) a use this code to draw it (even if I doubt u need it to answer my question):
procedure TStrom.Paint(Image: TImage);
var
C: TCanvas;
procedure Paint1(V: TNode; Width, X, Y: integer);
begin
if V.L <> nil then //left child
begin
C.MoveTo(X, Y);
C.LineTo(X - Width div 3, Y + 50);
Paint1(V.L, Width div 3, X - Width div 3, Y + 50);
end;
if V.S <> nil then //middle child
begin
C.MoveTo(X, Y);
C.LineTo(X + Width div 3, Y + 50);
Paint1(V.S, Width div 3, X + Width div 3, Y + 50);
end;
if V.P <> nil then //right child
begin
C.MoveTo(X, Y);
C.LineTo(X + Width div 3 + Width div 3, Y + 50);
Paint1(V.P, Width div 3, X + Width div 3 + Sirka div 3, Y + 50);
end;
if V.isLeaf then
begin
C.Ellipse(X - 15, Y - 15, X + 15, Y + 15);
C.TextOut(X - 3, Y - 8, IntToStr(V.Info1));
end
else
begin
C.Rectangle(X - 15, Y - 15, X + 15, Y + 15);
C.TextOut(X - 7, Y - 8, IntToStr(V.Info1));
C.Rectangle(X + 15, Y - 15, X + 50, Y + 15);
if V.Info2 <> 0 then
C.TextOut(X + 27, Y - 8, IntToStr(V.Info2));
end;
The draw function works well, but some (most) of the nodes at the height of 3+ are drawn on other nodes, so it looks bad. The node is sitting on another node and is not 20 pixels next to it.
I thought I'd make the image where the tree is painted real big, but it would be in a small "panel". Like this: the TImage would really be 1000*1000, but in the form you could see only a little part of it. In this part there'd be horizontal and vertical scrollbars, so you could scroll through the image and see what's painted in the sections. (Like when you scroll through a web browser to see the bottom of the page :) )
We are not permitted to use any other code, just built in lazarus components. (nor are we permitted to create new components -> have no idea why)
While I'm still curious about how this could be done, it's no longer necessary for my application ( installed a second monitor to see if it'd help and it wouldn't, so I guess I'd dig through my paint method a bit :-) )

Your edit makes it more clear that you want to draw a schematic like the last tree in the example you link to.
From your code I understand that you are drawing all nodes, childs and leaves, onto a single canvas resulting in one large image/bitmap. Now, how to display only part of that large image with scroll bars next to it?
The obvious choice would be to place a TPaintBox on a TScrollBox. I do not know the default suite of components in Lazarus, but I expect both of them to be present. Give the paint box a size equal to the bounds of your visual tree and you're set: scroll bars will be shown automatically.
The paint box has an OnPaint event in which you do your paint work by drawing to PaintBox.Canvas. To optimize this, you could limit your drawing to PaintBox.Canvas.ClipRect (the part of the paint box that is visible within the scroll box), but I suspect that to be difficult, since you already have difficulty with computing the right distance between adjacent nodes. (About that: I expect the maximum width and height of the total tree to be a function of the total depth, but I could easily be mistaken.)
If Lazarus does not have a TPaintBox, then use a TImage which also has a Canvas property. Downside is that TImage "stores" all drawing operations in one big internal bitmap, which could raise memory or resource issues when your tree expands too much.

Could all images be combined to one large image? If so, then maybe this component answers your question.
It is a descendant of TGraphicControl, capable of animated zooming. Zoom in on a part of the graphic by dragging a selection rectangle, zoom out the whole graphic by double clicking it. Shoot if you need help to update it to be able to perform pan operations.

Related

How to calculate the height of a TCategoryButtons at run-time?

In a Delphi 10.4.2 VCL Application in Windows 10, how can I calculate the height of a TCategoryButtons object at run-time, i.e. the sum of all its button heights and its Category items, as this height could vary depending on its font size?
Measuring the pixel heights at run-time, I have noticed that all buttons have the same height and that the buttons have a different height as the Category items.
Also, note that the buttons do not have a published Height property in the Object Inspector.
But shouldn't it be possible to calculate the sum of all its button heights and its Category items with some low-level methods?
This is a control entirely implemented in Pascal, in Vcl.CategoryButtons.pas.
Therefore, you can see exactly how it is implemented. For instance, in TCategoryButtons.Paint you see its complete drawing code. Similarly, you can investigate the hit testing done in MouseMove (or MouseDown or MouseUp).
Consequently, if nothing else, you can create your own modified version of TCategoryButtons using this code. Your version can save the total height when it has been determined (for instance, certainly after painting).
However, after a quick look, it seems like TButtonCategory.Bounds might be interesting. If you are lucky, this returns the on-screen rect of a category. The Bottom of the last category's rect should be the (effectively used) height of the entire control.
It seems to work for me:
Here I draw a red bar of the same height as the control.
procedure TForm5.FormPaint(Sender: TObject);
begin
var y := CategoryButtons1.Categories[
CategoryButtons1.Categories.Count - 1
].Bounds.Bottom;
Canvas.Brush.Color := clRed;
Canvas.FillRect(Rect(0, 0, ClientWidth, y))
end;

Need guidance on drawing PNGs into a Delphi XE6 TDrawGrid

I'm using a drawgrid to display a "dungeon" map. I have an array filled with data describing what types of physical features are located in each cell. The basic layout of rooms and corridors, etc. There's a PNG for each permutation.
I'm trying to "draw" the map by drawing the appropriate PNG into each cell of the drawgrid. I can do it more or less directly using DrawGrid1.Canvas.Draw(x,y,pngImage), but figuring out the exact x,y in pixels is frustrating because of the gridlines (at least, I'm finding it frustrating), and I'm not sure what other issues I'll run into down the road.
I also tried pngImage.Draw(DrawGrid1.Canvas,Rect), but once again, I have to calculate the Rect, which really seems unnecessary as the cells and PNGs are all 40x40 pixels.
Reading related articles and examples, OnDrawCell seems to be a better way, because given ARow and ACol, the Rect for the given cell is apparently precalculated (unless I'm misinterpreting what I've read in several places). But none of the examples I've found actually show how OnDrawCell is triggered. The easy answer "when something gets drawn into a cell" doesn't really explain it much better. That's what I'm trying to do.
(I've often found this to be the case with Delphi's documentation: How is explained (not always exactly for your given use case), but when or why is left shrouded in mystery..)
Of course, there are a few other things to figure out as well, like controlling the drawgrid so it doesn't erase the PNG when a cell is clicked..
Any relevant suggestions will be greatly appreciated. TFRM
Why is figuring out exact X and Y in pixels so hard? It is actually pretty simple.
In order to get left border of your rectangle you simply multiply width of you column plus width of your gridline by number of the column. And if you have gridline on the very left of the first column then add the width of your gridline.
And in order to get right border of your rectangle simply add width of the column to the previous result.
And for calculating Y you just use height instead of width
So the code would look something like this (written from my mind and untested)
const
CellWidth = 40;
CellHeight = 40;
GridLineThickness = 1;
procedure DrawCell(Row, Column: Integer; Image: Bitmap);
var Rect: TRect;
begin
Rect.Left := ((CellWidth + GridLineThickness) * Column) + GridLineThickness;
Rect.Right := Rect.Left + CellWidth;
Rect.Top := ((CellHeight + GridLineThickness) * Row) + GridLineThickness;
Rect.Bottom := Rect.Top + CellHeight;
PaintBox1.Canvas.StretchDraw(Rect, Image);
end;
And if you want an ability to zoom in or zoom out just multiply the CellWidth and CellHeight by a zoom factor. And when being zoomed out a lot just omit rendering grid lines.

Converting Delphi TPoint to C# Point

I am trying to convert some Delphi code as we are re-writing a Delphi 6.0 (VCL) application in .Net. I am not sure and could not figure out the comparison between 2 Delphi Tpoints(x,y) with that of C# Point(x,y).
I am trying to draw a line between 2 points but since I have no idea how Delphi draws it, I am not able to set the C# coordinates for it.
The Delphi code is simple:
Canvas.MoveTo(x, y - 128);
Canvas.LineTo(x, y);
I know about the C# coordinates though about 72 Points per inch and need to calculate the pixel density. But I am not sure about the Delphi PPI.
Any would be appreciated. Thanks.
Edit: If someone is wondering what TPoint I am talking when there is none in my code snippet, Canvas.MoveTo sets the PenPos property of the canvas which is of type TPoint.
I'm not sure what the exact question is that's being asked here. You have no Delphi TPoint in your code snippet; you simply have client rect logical coordinates.
The origin is at X = 0, Y = 0, which is the top left corner of the client area. Increasing X moves the position to the right, and increasing Y moves the position down. Logical units are pixels, so starting at the origin of 0, 0, a Canvas.MoveTo(10, 10) would set the new drawing position in from the left edge 10 pixels and down from the top 10 pixels, and a Canvas.LineTo(20, 20) from there would draw a line from the point at 10, 10 to 20, 20.
TCanvas.MoveTo and TCanvas.LineTo are simply wrappers around the underlying Windows GDI functions MoveToEx (with an always NULL third parameter) and LineTo.
As far as the C# equivalent, if you're referring to System.Drawing.Point, the units used are exactly the same (although I'm not sure where the origin is based by default). Given an origin of 0, 0, System.Drawing.Point(10, 10) should be the same position described above - 10 pixels from the left edge and 10 pixels down from the top edge.
A quick check confirms that the origin in a WinForms application is in fact the top left corner of the client area, using:
// Delphi code
procedure TForm3.FormPaint(Sender: TObject);
begin
Canvas.Pen.Color := clRed;
Canvas.MoveTo(0, 0);
Canvas.LineTo(100, 100);
end;
// C# code
private void Form1_Paint(object sender, PaintEventArgs e)
{
Pen newPen = new System.Drawing.Pen(Color.Red);
e.Graphics.DrawLine(newPen, new Point(0, 0), new Point(100, 100));
}
This produces the following output:

TeeChart VCL chart scale issue

I have an issue using Delphi 2007 & TChart 7.0.10.0 or 7.0.11.0 or the latest evaluation 9.0.5.0 on the TChart scaling.
The problem arises as soon I enlarge the window after a certain width and KEEP the Form height!
This is the drawing using a smaller form size.
now if I enlarge to 1200 weight I get this ugly scaling:
If I export in the designer without the aspect ratio set and with 1200 weight you will se this:
How to get ride of this?
Hp
I see you've set top and bottom margins to Chart1 in your project (8 and 20 percent respectively). I guess this has the intention of giving more space (in height) for Chart2 when you resize the form making it bigger.
Chart1's Top and Height properties should be set according to fill this blank space in the Form's OnResize event.
Try this:
procedure TGSSkillgroupStatisticForm.FormResize(Sender: TObject);
begin
Chart1.Draw;
Chart2.Top:=Chart1.ChartRect.Bottom + 25;
Chart2.Height:=Chart1.Height-Chart1.ChartRect.Bottom-40;
end;
Steema Support Central
Keep in mind that I only scale in the x-axis. Your 3-D bar / construct will after a certain width, overlap the scaling numbers! Your given answer do not fix this issue at all. To see the real problem in a better way, I added on the form creation:
Chart2.BottomAxis.Maximum := 20;
Series2.AddBar(12, 'Hallo', clred);
Here the result:

How can I copy TBitmap memory using with windows CopyMemory function

I have 1 bitmap object witdh : 1024px and height : 768 px
I want to cut this bitmap object to 2 part like left and right but I don't want to use DrawBitmap method in canvas because this method can use more CPU then CopyMemory.
I don't want to use this method ( leftImg.Canvas.DrawBitmap(MainBmp, RectF(0,0, MainBmp.Width div 2, bmp.Height),
RectF(0,0, leftImg.Width, leftImg.Height), 1, True); )
MainBmp := TBitmap.Create(1024, 768);
leftImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
rightImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
leftBits := PAlphaColorArray(leftImg.Scanline[0]);
CopyMemory(#leftBits[0], #MainBmp.StartLine[0], (MainBmp.Width div 2) * bmp.Height);
if I am doing like this he can copy but not left part of bitmap :( he copy half of top to bottom.
That drawing is exactly what I want to do.
After cut procces, i need like this without using any loop (like while or for)
Thanks
No can do! As you've found out image data is layout in the memory line by line (hence scanline). What you want could only be possible if it was column by column. Without any loops this is not possible.
As you noticed, a scanline is a row of pixels, from left to right. There is one scanline for each pixel of vertical height in the image.
Your 1024px x 768px images have 768 scanlines. Copying the first half of the data from scanlines yields you the top half of the image.
You wouldn't have to go through every pixel, you can skip ahead since everything is indexed.
However, since you want both halves, you're not wasting any work by going through the whole thing. As you iterate through the data, copy both the left and right parts out at the same time. So, for the first scanline, copy the first half of pixels to the left image and the rest of the pixels to the right image, go to the next line, and repeat.
This should be less work than DrawBitmap twice.
Also, rather than loading the image, displaying it, then splitting it, split it while you're loading the image.
You'll still need a loop, unless you want to write everything 768 times.
Technically, you could rotate the image and do it the way you want, but rotating it would require loops too, and you'd have to rotate it back when you're done.
Use the TCanvas.CopyRect() method to copy portions of one TCanvas to another TCanvas. It allows the two bitmaps to have different pixel formats. The OS will handle the differences internally for you:
MainBmp := TBitmap.Create(1024, 768);
leftImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
rightImg := TBitmap.Create(MainBmp.Width div 2, MainBmp.Height);
leftImg.Canvas.CopyRect(
Rect(0, 0, leftImg.Width, leftImg.Height),
MainBmp.Canvas,
Rect(0, 0, leftImg.Width, leftImg.Height)
);
rightImg.Canvas.CopyRect(
Rect(0, 0, rightImg.Width, rightImg.Height),
MainBmp.Canvas,
Rect(leftBmp.Width, 0, rightImg.Width, rightImg.Height)
);

Resources