Painting relative to scrollbars - delphi

I am making some components for my own use, i am trying to make a "riff generator" for making tunes in midi format. Therefore i am writing a pianoroll editor (like in fl studio etc).
The problem im having is drawing relative to the scrollbar positions. I am trying it for two days now, but i cant seem to figure out how to do this. I already added variables and procedures for setting the scroll positions, but i dont understand how i can draw up/down and sideways when i scroll.
I added the code i have so far, i am using the GDI+ unit from Erik Bilsen (www.bilsen.com/gdiplus) for drawing.
If anyone can look at my code, and help me to get the drawing right that would be awesome! I will release this and other components as opensource when i finish, so other people will be able to use these components too.
The component code: https://pastebin.com/562yfDvu
FScrollPosX : Integer;
FScrollPosY : Integer;
FScrollMaxX : Integer;
FScrollMaxY : Integer;
FOldScrollX : Integer;
FOldScrollY : Integer;

Some typical calculations
Scrollbar range
Given Virtual extent (VirtExt) in pixels
Given Viewport extent (VPExt) in pixels
Scrollbar range = VirtExt - VPExt (to leave the last part visible in the viewport)
Object position in ViewPort with a given scroll position
Given Object.pos (ObjPos) (in virtual space)
Given Sroll position (ScrPos)
Position in viewport: ObjPos - ScrPos
Object visibility filter
Visible if (OPos >= ScrPos) and (OPos < ScrPos+VPExt)

Related

TChart axis labels overlapping when using a lot (more than 100) of TLineSeries

I'm dealing with an application that abuses TChart with potentially hundreds of TLineSeries. I get unreadable X axis labels, because they overlap. According to this, that shouldn't happen, but it does.
I'm now looking for a way to completely disable X axis labels derived from the TLineSeries elements and only show calculated labels at regular intervals, e.g. no labels for individual data points. How would I do that?
The misbehaving application:
My failed attempt to replicate the problem:
If the Bottom Axis for the chart has a scale set with Automatic turned off (default is on) you can get overlap like that depending on your data, the desired increment and if Show all labels is checked.
However your example seems to show non-regular extra labels which might just be manually added using the Items (show in far right tab) or something else to add more axis labels.
I was finally able to reproduce the problem with a small test case. Turns out that the problem was, that the application used function TChartSeries.AddXY(Const AXValue, AYValue: TChartValue; Const ALabel: String; AColor: TColor): Integer; and provided a value for ALabel. Using only X and Y values fixed the problem.

How to format any arbitrary float to fit a given display width?

I'm working on a project where a single read-only TEdit control displays any arbitrary numeric value, formatted to a particular unit of measurement, so it has also an arbitrary suffix. The width of this edit control is fixed (yet can change based on re-sizing the UI), and I need to make sure whatever number is displayed fits reasonably within this control without overflowing out of view.
Think of it like fitting a mathematical answer on a calculator screen, because that's literally what I'm doing.
Although the complexity of this particular project is much more detailed, let's stick with a simple example which applies to all...
function FormattedValue(const ANum: Double; const ASuffix: String;
const AWidth: Integer; const DC: HDC): String;
begin
Result:= FormatFloat('#,###,###,##0.######', ANum) + ASuffix;
//TODO: How to enforce result to always fit within AWidth pixels?
//DC = canvas handle of the control where text will be displayed
end;
However, I have absolutely no idea how to go about formatting it to fit within AWidth constraint, which would be the TEdit.ClientWidth. I found FloatToStrF, which I'm sure will be needed, but even that has nothing to do with the display area available.
The only way I see so far is to load the font associated with the canvas, and iterate all possible characters, looking for the widest possible character. Then, see how many times the largest character can fit within the given width. Then, use FloatToStrF to actually format it, combined with the suffix width. But all that seems like overkill for what seems like a trivial task.
How do I do this, while using scientific notation when necessary?

Is it possible to draw a line between points on a TChart "point" chart

An instance of TChart in Delphi does a nice job of making a graph showing points. This is the "Point" series in the TeeChart gallery.
I would like to draw lines between some, but not all, of the points.
For example, image a scatter diagram of points, which is easy to make with Delphi/TChart. My objective is to draw straight lines between some of the points.
Is this possible?
This code works:
With Chart2.ChartRect do
begin
Canvas.MoveTo(0,0);
Canvas.LineTo(500,500);
end;
The line crosses over the graph, except the line is not visible over the graph. It seems as if the graph much have its own canvas but I can't find any documentation about finding and using it.
So, I need to find the coordinates of some points, as created by TChart, then draw a line between them.
Some options:
As David Heffernan said, the best option may be to use a series that supports both line segments and pointers. TLineSeries seems to be the perfect series to do this.
The problem here is that this series draws a line segment between each two consecutive points. You can make a point to be null with setNull(index) method and this will make the pointer at that index to disappear, but the predecessor and successor line segments will disappear with it.
The easiest solution could be creating a TMyLineSeries inheriting from TLineSeries to override DrawValue method as explained here.
Add a null point after each point you want to be visible. In this option, the only points not followed by a null point would be those where you want a line segment to be drawn.
Add a TLineSeries per line segment to be drawn.
You always have the possibility to use custom drawing techniques as LU RD said.
Attempting to paint lines yourself is not the way to proceed. The design basis of charting controls is that you define the chart in logical terms and let the control deal with painting it.
So, the way to proceed is to add some line series that represent the lines you wish to be drawn. You can perfectly well add these line series in addition to the other series of your chart.
Regarding your code that attempts to paint on the chart canvas, you should be aware that painting is a delicate process. The design of the system is such that control surfaces are not persistent. Controls are painted in response to the WM_PAINT message. So, whilst you may be able to paint on a control's canvas as you please, what you paint will survive only until the next cycle. Once the control becomes invalid, it needs to repaint itself. The lesson here is, as a general rule, only to paint in response to WM_PAINT messages. Or, in OnPaint events or overridden Paint methods which are called by the VCL in response to WM_PAINT.
To interconnect some points you must follow the drawing principle, all drawing must be done in a paint event.
In TChart, best option here is to do custom drawing in the OnAfterDraw event.
procedure TForm1.Chart2AfterDraw(Sender: TObject);
begin
With Chart2.ChartRect do
begin
Canvas.MoveTo(0,0);
Canvas.LineTo(500,500);
end;
end;
If you need to know the canvas coordinates for a given point in your array of points.
MyYPosX := Series1.CalcXPos( Series1.XValue[ 0 ] ); { <-- first point }
MyYPosY := Series1.CalcYPos( Series1.YValue[ 0 ] ); { <-- first point }
There is a chapter in the TChart help that is a good introduction to custom drawing: "Custom drawing on the Chart".
From the help:
When to draw ?
The order in which you draw to the Canvas is important.
If you wish Custom drawn items to appear above Chart Series you should use the Chart OnAfterDraw event. The Chart1.OnAfterDraw event is fired each time the Chart component is redrawn, just before copying the internal bitmap to screen.
You can place Custom drawn items above the Chart grid and below the Chart Series by placing your code in the Chart OnBeforeDrawSeries event.
Key Chart Paint events:
OnBeforeDrawChart
OnBeforeDrawAxes
OnBeforeDrawSeries
OnAfterDraw

Delphi Tchart x and y values in zoomed rectangle

I have a tchart series that contains 5000 points from a data base.
I can zoom in on a particular section of the data with the mouse.
I wish to copy only the x and y values that are visible in the subset to the clipboard (or text file).
I can easiy access the entire series, however I have not been able to figure out how to access only
the data in the zoomed rectangle.
Any help is appreciated.
Thanks in advance
Arthur
The series X index range in the zoomed rectangle is [FirstValueIndex..LastValueIndex].
Note : They are only accessible after the values are displayed.
A value of -1 means that the whole range is to be used. [0..Count-1]
Update :
And if you want to clip on Y values as well, then step through above indexes and check against leftAxis.Minimum and leftAxis.Maximum.
If you can represent any point from the series as a TPoint structure (e.g. with the help of the Point() function) and the view area as a TRect (e.g. using Rect()), you will be able to use the PtInRect() function to test whether the point is within the zoomed rectangle:
if PtInRect(ZoomedRect, ChartPoint) then ...
References:
TPoint
Point()
TRect
Rect()
PtInRect()
This was discussed here. Hope suggestions here are useful for you.

BCB: how to get the (approximate) width of a character in a given TFont?

It's a TMemo, not that that should make any difference.
Googling suggests that I can use Canvas->TextWidth() but those are Delphi examples and BCB doesn't seem to offer this property.
I really want something analogous to memo->Font->Height for width.
I realize that not all fonts are fixed width, so a good estimate will do.
All that I need is to take the width of a TMemo in pixels and make a reasonable guess at how many characters of the current font it will hold.
Of course, if I really want to be lazy, I can just google for the average height/width ratio, since height is known. Remember, an approximation is good enough for me if it is tricky to get exact.
http://www.plainlanguagenetwork.org/type/utbo211.htm says, " A width to height ratio of 3:5 (0.6) is recommended for most applications"
Actually your google search is not entirely off. You do need access to a canvas object, or at least a handle to a DC object. In general when searching for help concerning VCL classes it often pays to search for delphi examples since these are more common.
Anyway to calculate the size of a string you could have a look at the TextExtent function, it is a function for the TCanvas class. Simply pass the character which width you want to test, and the return value will be a TSize construct. However there is also a TextWidth function, as well as a TextHeight function. You can use these as well. Actually these call the TextExtent internally.
You have to note one thing though, the functions use the current font of the TCanvas object, more specifically the font bound to the DC the canvas uses. So assign the font you wish to test with first, and then pass the character.
I have some old code that calculates the width of a string like this:
// This canvas could be the form canvas: canvas = Form1->Canvas or the
// memo canvas which will probably be what you want.
canvas->Font->Assign(fontToTest);
int textwidth = TextWidth(textToTest);
If you want more control of what to do, you can also do this using the Windows API, this is essentially what the VCL does for you, in that case the following example would look like this:
// This canvas could be the form canvas: canvas = Form1->Canvas
canvas->Font->Assign(fontToTest);
// The initial size, this is really important if we use wordwrapping. This is
// the text area of the memo control.
TRect rect = ClientRect;
// This is the font format we wish to calculate using, in this example our text
// will be left aligned, at the top of the rectangle.
fontformat = DT_LEFT | DT_TOP;
// Here we calculate the size of the text, both width and height are calculated
// and stored in the rect variable. Also note that we add the DT_CALCRECT to the
// fontformat variable, this makes DrawTextEx calculate the size of the text,
// without drawing it.
::DrawTextEx(canvas->handle,
textToTest.c_str(),
textToTest.Length(),
&rect,
fontformat | DT_CALCRECT,
NULL);
// The width is:
int width = rect.Width();
The fontformat is a parameter that specifies different options for how to align and layout the text, if you plan on drawing text it will be a good idea to check out the different possibilities it offers: DrawTextEx Function [1]
EDIT: Reading through your question again, it struck me that the function you might be searching for is: GetTextExtentExPoint Windows API documentation states the following about this function:
The GetTextExtentExPoint function
retrieves the number of characters in
a specified string that will fit
within a specified space and fills an
array with the text extent for each of
those characters. (A text extent is
the distance between the beginning of
the space and a character that will
fit in the space.) This information is
useful for word-wrapping calculations.
You can find more information about the GetTextExtentExPoint function here: GetTextExtentExPoint Function [2]
[1] http://msdn.microsoft.com/en-us/library/dd162499%28VS.85%29.aspx
[2] http://msdn.microsoft.com/en-us/library/dd144935%28VS.85%29.aspx
What you could do, if you have access to Win32 API functions, is create a RichEdit Window the same size as your TMemo window, place the text in the RichEdit window, send the EM_FORMATRANGE message to the window and from the result determine how many characters it will hold. Of course this method will work with multiple lines etc...

Resources