IDE: c++ Builder XE5 Update 2
TeeChart Build: 2014.11.140512
I am trying to bring certain series on a TChart component to the front(as you would typically do with a BringToFront() function).
I've done some reading and found the following options/suggestions:
A. Change the ZOrder property of the series.
B. Use TChart.ExchangeSeries()
Using TChart.ExchangeSeries() is not a proper way of changing the z-order of a series. Its primary function is to swap around two series in a TChart component's SeriesList(which then inherently changes the z-order of those series). If you require your series-ordering to be fixed(fixed ordering in SeriesList), then this will not work.
Changing the ZOrder properties of the series delivered better results. However, changing the ZOrder of the first series(Series[0]) apparently does nothing. Series[0] seem to like sitting at the back of the class.
The above might be the result of my implementation. In which case, some more details:
On my TChart component I have multiple series. The series-types can be changed dynamically. The types to which the series can be changed are limited to TLineSeries and TBarSeries.
I always want the TLineSeries at the front.
Any advice on how this can be done?
(Will we ever see that elusive TChartSeries.BringToFront() function?) :)
Any advice on how this can be done?
Looks like mixing series styles and changing ZOrder is not working very well. For example, using this code snippet:
uses Series;
procedure TForm1.FormCreate(Sender: TObject);
begin
Chart1.AddSeries(TLineSeries.Create(Self)).FillSampleValues(10);
Chart1.AddSeries(TLineSeries.Create(Self)).FillSampleValues(10);
Chart1.AddSeries(TBarSeries.Create(Self)).FillSampleValues(10);
Chart1.AddSeries(TBarSeries.Create(Self)).FillSampleValues(10);
Chart1.AddSeries(TBarSeries.Create(Self)).FillSampleValues(10);
end;
procedure TForm1.Button1Click(Sender: TObject);
var i: Integer;
begin
for i:=0 to Chart1.SeriesCount-1 do
begin
Chart1[i].Marks.Visible:=False;
if Chart1[i] is TLineSeries then
Chart1[i].ZOrder:=Chart1.SeriesCount - 1 - i
else
Chart1[i].ZOrder:=i;
end;
end;
Line series are brought to the front but bar series style remain there. I have also tried with bar series StackGroups: However, it doesn't make much of a difference. We'd need StackGroups for line series as well for this work.
(Will we ever see that elusive TChartSeries.BringToFront() function?)
:)
Why not? I have added your request to Steema Software's bugzilla platform: http://bugs.teechart.net/show_bug.cgi?id=853. Feel free to sign up and add yourself to the CC List to receive automatic issue updates.
Related
I am developing a Delphi 10.1 VCL application for Windows.
For integer or float input I need a number input field which is connected with a slider. When the user changes the number in the input field the slider position changes accordingly. When the user changes the slider position the number in the number field is updated.
I can solve this by using a TEdit and a TTrackBar and add the necessary update functionality in their OnChange event handlers.
The problem is that I need many of such inputs on different forms. Therefore I would like to create a new component which combines the two controls TEdit and TTrackBar in one component.
Is the creation of a new component the best strategy for the multiple use of such a slider input?
What is the best way to create such a new component?
Is the creation of a new component the best strategy for the multiple
use of such a slider input?
Not necessarily true all the time. (by my standards at least).
What is the best way to create such a new component?
I know three ways to solve your problem.
Way number 1:
create the component using the new component wizard where you create dynamically the TEdit and the TTrackBar sub components in a TGroupBox descendant.
the following is how I would do that.
unit Combindedittrack;
interface
uses
System.SysUtils,
System.Classes,
Vcl.Controls,
Vcl.comctrls,
Vcl.StdCtrls;
type
TCombindEditTrack = class(TGroupBox)
private
{ Private declarations }
FEdit: TEdit;
FTrackBar: TTrackBar;
procedure EditOnChangeProc(Sender: TObject);
procedure TrackBaroOnChangeProc(Sender: TObject);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TCombindEditTrack]);
end;
constructor TCombindEditTrack.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetBounds(0, 0, 250, 50);
FEdit := TEdit.Create(Self);
with FEdit do
begin
Text := ''; //<-- you control the appearence here
Top := 10;
Left := 10;
Height := 27;
Width := 50;
Parent := Self;
OnChange := EditOnChangeProc; // your Onchange event handler for the Tedit
end;
FTrackBar := TTrackBar.Create(self);
with FTrackBar do
begin
Top := 10; //<-- you control the appearence here
Left := 60;
Height := 30;
Width := 50;
Parent := self;
Onchange := TrackBaroOnChangeProc; // your Onchange event handler for the Ttrackbar
end;
end;
destructor TCombindEditTrack.Destroy;
begin
FTrackBar.Free;
FEdit.Free;
inherited;
end;
procedure TCombindEditTrack.TrackBaroOnChangeProc(Sender: TObject);
begin
// <-- track bar onchange handling here.
end;
procedure TCombindEditTrack.EditOnChangeProc(Sender: TObject);
begin
// <-- edit onchange handling here.
end;
end.
Way number 2:
Use frames like this (I'm on delphi 10 seattle).
File-->New-->Other-->(search for frames on delphi files).
Now add the edit and the track bar and set their Onchange events.
Save the unit.
on the tool palette (the standard component section) click on the frame component.
choose the frame you just created.
You will have a replica of the frame each time you use it.
Way number 3:
Use component template like this (again I'm on delphi 10 seattle)
Select your already created and modified tedit and ttrackbar.
On the toolbar's "component" click "create component template".
Name your template and press OK.
Select the Template palette and then your template.
Now notice that even your code (events) are added as well to your project.
Finally
With the level I have on delphi and the IDE I'm really unable to give you a clear answer to which is the best way but nevertheless I have shared all what I know that could help you.
edit: Since a lot of comments are insisting that the answer should state which is the best way to do this. this is the best way based on the following.
let's put some of the key point that should be accounted for when choosing
1. Ease of modifying the combined control(s) if you wish so (by my experience you will).
2. time needed to complete this task (it means the time it will take
you to fully complete the task with minimum debugging and coding).
3. general source code readability.
4. usefulness for the future of your projects.
Now lets start criticizing the three methods based on the those criteria.
Way number 1:
C1(criteria number 1): Just modify the the source implementation of the component and each replica/use will have the same effects and properties. However this is not the case for way number 3.
C2: It depends on your knowledge of component writing but for this component it took me 5 min to create it and I'm only a beginner in delphi. For the debugging if something went wrong and the problem is in the component implementation than you just need to fix once (see C1)
C3: their is no implementation in your form(s) source code for your component just add it to your form and every thing is hidden (for example add a tedit and go to see the implementation in your forms source).
C4: You are creating a component after all this will open the door for you to create your own set of components like the Flatstyle or Indy open source components. so next time you need some thing like this you just drop it in your form designer and you are done.
Way number 2: frames
C1: It is like way number 1 because you are creating a component but it is visually this time. modifying the source frame will change the effects and properties of the replicas, also you can add extra handling to your replicas.
the event handler of the replica's Onchange event is like this
procedure TForm1.Frame2Edit1Change(Sender: TObject);
begin
Frame2.Edit1Change(Sender); //<-- this is the original onchange event you setup at the beginning
//<-- you can extra handling here if you want one of the replicas to behave differently than the original
end;
C2: same time and maybe faster than way number 1.
C3: over all, it has the same out come as way number 1.
C4: unlike way number 1 you can not use frames created in project A in project B. So your coding and debugging will stay in project A.
Way number 3: component template.
C1: you are not creating a component you are creating a repleca/macro of the exact steps you did in your last project. changing one will not change the others they are separated.
C2: same time and maybe faster than way number 1.
C3: each time you add a template to your form the events code will be added (not a good view if it is a long Onchange code).
C4: You can use templates created in project A in project B. However what you wrote in project A will be in project B (see c1) even the references of variables that don't exist in project B (this can be hard to debug and misleading, considering the period of time between each use of the template).
Conclusion: each of the ways presented will consume time to code and debug and all of them will do the task, How ever for the sake of simplicity and the reuse with minimum risks Way number 1 is the safe choice here because it will give you the chance to update and upgrade safely. also debug faster.
the good thing also about way number 1 is that after a while when you will forget the implementation and how things are working internally. The only thing that should stay in mind is the purpose of the component because it will become one of the various component you use (you don't know how Tedit is implemented and you don't need to but yet you use it in every single project you create).
based on the criteria given Way number 1 is the best.
Maybe using a container control that contains both controls is a simpler alternative. I am using ccpack for this.
https://sourceforge.net/projects/ccpack/
Custom Containers Pack (CCPack) is an integrated tool and component mini-
library to produce and maintain composite controls (or simply “composites”)
and other containers (forms, data modules and frames). The process of
building composite components looks like ActiveForm and Frame creating, but
the result is the native VCL component. You can create new composites just
as usual forms.
You can create a Frame and then register that Frame as a component. The end result is very similar to creating a code only component where the sub components are created in the constructor (Nasreddine's number 1 option). However this method allows you to visually design the component and use the object inspector to create your event handlers.
Here is a Stack Overflow question that shows how to register the frame:
How to Improve the Use of Delphi Frames
I would like to use the gallery pictures (Metal, Wood, Stone, Clouds, etc.) which are available at design time under Chart/Panel/Image/Gallery.
If I set it at design time, I can easily disable it at run time with:
g.backImage := nil;
But if I want to set it to a particular value, e.g. with
g.backImage := 'metal';
I get an 'Incompatible types' error, because the compiler requires a TBackImage value. I do not have the source codes, and I cannot find the appropriate values on several Google searches.
Thinking that it could be just an enum, I tried typecasting it to one:
g.backImage := TBackImage(1);
But it generates an exception. I also tried to "guess" the names, like tbiMetal, tbMetal, tMetal, and so on, to no avail...
What are those values?! Thank you
TBackImage is a class whose methods you must call.
Chart.BackImage.LoadFromFile('full/path/to/imagefile');
Those are real texture images embedded in TBrushDialog, they can be used/accessed like this:
uses TeeBrushDlg;
procedure TForm1.FormCreate(Sender: TObject);
var BrushDialog: TBrushDialog;
begin
BrushDialog:=TBrushDialog.Create(Self);
Chart1.BackImage.Graphic:=BrushDialog.ImageCarbon.Picture.Graphic;
end;
[TeeChart 2015.14.150120]
I've a Gantt-Chart so the x axis is date based. I would need to mark time periods in the chart. So i.e. mark 2009 -2011 describing it with a certain description, 2011-2013 with another name ...
I've tried to use TColorBandTool but it had some drawbacks:
I wasn't able anymore to click the Chart-Entries (even if the tool was marked as "behind"
I could not show a description of the period.
So I've tried to use TColorLineTool which worked better (almost perfect) but:
the text panels for differnt period where shown at the same (vertical) position, so they overlapped some times.
When the last TextPanel was longer then the remaining part of the chart, it "dropped" out, the Chart was not redimensioned in order to show the Panel within the Diagram.
So, now I had another Idea: to use different series to build one line over the whole width of the chart, each series for one period to show. But I would have to show the description of these series in extra Legends (TExtraLegendTool) in oder to have space enough for the texts. But I couldn't get the TExtraLegendTool shown. I assume there is a bug in that version of TeeChart since also the demo put by the installer doesn't show that tool.
Now I'm rather at a loss how to go on. Anybody has an idea?
I've given a try at the first approach:
I've tried to use TColorBandTool but it had some drawbacks:
I wasn't able anymore to click the Chart-Entries (even if the tool was marked as "behind"
I could not show a description of the period.
And I think it works fine for me here. This is how it looks and below the code I used.
Note I disabled the TColorBandTool's AllowDrag, ResizeStart and ResizeEnd properties.
var gantt1: TGanttSeries;
greenBand, blueBand: TColorBandTool;
greenAnnot, blueAnnot: TAnnotationTool;
procedure TForm1.FormCreate(Sender: TObject);
begin
Chart1.Title.Visible:=false;
Chart1.View3D:=false;
Chart1.Legend.Alignment:=laBottom;
gantt1:=Chart1.AddSeries(TGanttSeries) as TGanttSeries;
gantt1.FillSampleValues(10);
greenBand:= Chart1.Tools.Add(TColorBandTool) as TColorBandTool;
with greenBand do
begin
Color:=clGreen;
Transparency:=50;
Axis:=Chart1.Axes.Bottom;
StartValue:=gantt1.StartValues[1];
EndValue:=gantt1.EndValues[2];
AllowDrag:=false;
ResizeStart:=false;
ResizeEnd:=false;
end;
blueBand:= Chart1.Tools.Add(TColorBandTool) as TColorBandTool;
with blueBand do
begin
Color:=clBlue;
Transparency:=50;
Axis:=Chart1.Axes.Bottom;
StartValue:=gantt1.StartValues[9];
EndValue:=gantt1.EndValues[8];
AllowDrag:=false;
ResizeStart:=false;
ResizeEnd:=false;
end;
Chart1.MarginTop:=10;
Chart1.Draw;
greenAnnot:=Chart1.Tools.Add(TAnnotationTool) as TAnnotationTool;
with greenAnnot do
begin
Shape.Transparent:=true;
Shape.Font.Color:=clGreen;
Text:='Green annotation';
Top:=Chart1.ChartRect.Top-15;
Left:=Chart1.Axes.Bottom.CalcPosValue(greenBand.StartValue+(greenBand.EndValue-greenBand.StartValue)/2) -
(Chart1.Canvas.TextWidth(Text) div 2);
end;
blueAnnot:=Chart1.Tools.Add(TAnnotationTool) as TAnnotationTool;
with blueAnnot do
begin
Shape.Transparent:=true;
Shape.Font.Color:=clBlue;
Text:='Blue annotation';
Top:=Chart1.ChartRect.Top-15;
Left:=Chart1.Axes.Bottom.CalcPosValue(blueBand.StartValue+(blueBand.EndValue-blueBand.StartValue)/2) -
(Chart1.Canvas.TextWidth(Text) div 2);
end;
end;
I am trying the TeeChart software before I buy and was directed from their website to here for support.
I am trying to get my head around using the TScrollPagerTool as it seems perfect for what I am trying to achieve but I am experiencing performance issues when resizing.
In the resize event for the chart it is advised to add the following line if you are using this scrolling tool:
ScrollPagerTool.Series := Chart1.Series[0];
I don't know why you have to reassign the series but the chart won't resize without it. Further more that line effectively adds the Series again, meaning I have twice the series now and next resize, 3 times the series and so on, therefore I get a performance problem with scrolling after a few resizes.
Can someone tell me if I am doing this right or of an alternate method here?
I can post more code if required.
First note that TScrollPagerTool uses a TSubChartTool with a clone of the main series in it. That's why the the SubChart is set when the Series is assigned, because it has no sense without a series.
In some TeeChart versions, the SubChart needs the main chart to be already drawn when you assign a series to the tool in order to calculate the space each chart should use (the ChartRects). That's why we assign the series at the OnResize event, but I see in TeeChart VCL you can do it all at creation time:
uses Series, TeeScrollPagerTool;
procedure TForm1.FormCreate(Sender: TObject);
begin
Chart1.AddSeries(TLineSeries).FillSampleValues;
(Chart1.Tools.Add(TScrollPagerTool) as TScrollPagerTool).Series:=Chart1[0];
end;
BUG:
I've seen there was actually a bug, as you said. Assigning the series in the OnResize event to make the chart and the subchart resize with the form, it made the source series to be cloned again and again:
uses Series, TeeScrollPagerTool;
var scrollPager1: TScrollPagerTool;
procedure TForm1.FormCreate(Sender: TObject);
begin
Chart1.Align:=alClient;
scrollPager1:=Chart1.Tools.Add(TScrollPagerTool) as TScrollPagerTool;
Chart1.AddSeries(TLineSeries).FillSampleValues;
scrollPager1.Series:=Chart1[0];
end;
procedure TForm1.Chart1Resize(Sender: TObject);
begin
scrollPager1.Series:=Chart1[0];
end;
I've made some changes to fix it:
In the next version, the Series property will remove all the series in the subchart and will clone the series assigned. So calling it several times won't end up in more and more clones. It will also call SetUpScrollPager that calculates the ChartRect for both the main chart and the subchart.
I've also made the SetUpScrollPager public so this will be the one to call at OnResize event to adjust the ChartRects without needing to add or remove series.
procedure TForm1.Chart1Resize(Sender: TObject);
begin
scrollPager1.SetUpScrollPager;
end;
WORKAROUND:
In the meanwhile, with the actual version you still could remove all the series in the SubChart and use the Series property to clone the source series, all at the OnResize event:
procedure TForm1.Chart1Resize(Sender: TObject);
begin
scrollPager1.SubChartTChart.RemoveAllSeries;
scrollPager1.Series:=Chart1[0];
end;
RELATED FEATURE:
How to have multiple series in the ScollPager? You can use the SubChartTChart property to access the subchart and add/remove any series to/from it. But you still need one series in the Series property. So it gets a bit tricky:
uses Series, TeeScrollPagerTool;
var scrollPager1: TScrollPagerTool;
procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
Chart1.Align:=alClient;
scrollPager1:=Chart1.Tools.Add(TScrollPagerTool) as TScrollPagerTool;
for i:=0 to 4 do
begin
Chart1.AddSeries(TLineSeries).FillSampleValues;
if scrollPager1.Series=nil then
scrollPager1.Series:=Chart1[i]
else
scrollPager1.SubChartTChart.AddSeries(CloneChartSeries(Chart1[i]));
end;
end;
That's why I've also added a new AddSeries(const Value: TChartSeries) method. Use it to add several series to the ScrollPager:
for i:=0 to 4 do
begin
Chart1.AddSeries(TLineSeries).FillSampleValues;
scrollPager1.AddSeries(Chart1[i]);
end;
I need to copy a form (Delphi 2007) to the clipboard as an image to paste what the user can see into a word document. The clipboard part is not really a problem. The questions is how to get a bitmap for the form.
Searching has turned up multiple options.
Call GetFormImage
Use the PrintWindow API function that is part of the GDI+
Send a WM_PRINT message
Copy the Canvas for the current form using Canvas.CopyRect
I also found a component called TExcellentFormPrinter that has a solution that claims to works better than any of these options, but I don't know what method it is using.
All of these options seem to have different problems. Most of the information I am finding seems to be outdated. I can't seem any good source that compares the different options with enough detail for me to make a choice. Any advice on which option to go with.
I have tried these on my form and they all seem to work OK, I just trying to avoid problems down the road. Any advice on what solution to go with?
Update: What Potential Problems with GetFormImage?
Andreas asked what the problem is with GetFormImage. Hopefully nothing anymore, that is part of what I am trying to get an answer to. What has me concerned is so many of my search results seem to be suggesting creative alternatives to using GetFormImage. I was hoping the answers would clear up the waters a little bit.
I would be really happy with an answer that got a lot of up votes that said - GetFormImage used to have some problems but there is no reason not to use it now. :-)
As to the actual problem with GetFormImage. One issue for some users was only the visible part of the form would appear in the image (i.e. you can't capture a hidden or overlapped window). That is not really an issue for me as my entire form is visible.
1) The bigger issues deal with specific support required from the controls on your form. The Delphi 4 Fixes and Known issues page list has this entry (note it is listed as "Deferred to Next"). I could not find a QC entry that showed this resolved:
Area: vcl\core vcl classes
Reference Number: 1088 (Published: 12/16/98)
Status: Deferred to Next
Rel Date Reported: 8/6/98 Severity:
Commonly Encountered Type: Basic
Functionality Failure Problem:
The problem is with GetFormImage most nest windows controls like comboboxes, etc. are drawn blank.
2) I am also using the DevExpress controls. At one time their controls (fixed at the end of 2006) did not support the PaintTo messages that GetFormImage was using. This is fixed in the DevExpress release I am using, but it raises other issues with me, what is the chance that other control I am using may not work correctly?
3) Here is a more recent (2010) post on the Embarcadero Groups. The user was having trouble using GetFormImage where part of the graph they were showing on screen did not appear in the final image. They also needed the form caption included (which I do not) and they took the Canvas.CopyRect approach outlined in this post.
4) Here is the quote from the TExcellentImagePrinter page. I would have no problem buying their product if needed. There component looks like it was last updated in 2002 (There is a Delphi 2007 trial version though). I can't tell if I really need to go that direction or not.
You can try using GetFormImage or
Form.Print. Try dropping a ComboBox
down on a form, then call GetFormImage
or Form.Print. If you get a
printout, do you see the text in the
ComboBox? No? Neither does anyone
else! This is only a small example of
the problems you will encounter when
printing VCL forms.
You can also try using Borland's
TI-3155 "A better way to print a
form". I wrote the TI when I worked at
Borland as a stop gap measure. While
it will print the combobox text, it
will fail on many printers, it can't
print the entire form if your user has
resized the form, and it can't print
forms that are hidden from view or is
located partially off the screen. The
code basically produces a screenshot,
and to print an image reliably, you
would probably want to take a look at
our TExcellentImagePrinter product!
Why? Simply put, it can require a
couple of thousand lines of low level
graphics code to get bitmaps to print
well under Windows.
I do not know what the problem is with GetFormImage, but an option that you have not tried (at least not explicitly) is
procedure TForm1.FormClick(Sender: TObject);
var
bm: TBitmap;
begin
bm := TBitmap.Create;
try
bm.SetSize(ClientWidth, ClientHeight);
BitBlt(bm.Canvas.Handle, 0, 0, ClientWidth, ClientHeight, Canvas.Handle, 0, 0, SRCCOPY);
Clipboard.Assign(bm);
finally
bm.Free;
end;
end;
In almost all cases I would expect this to produce the same result as
bm := GetFormImage;
try
Clipboard.Assign(bm);
finally
bm.Free;
end;
though. (Also, the Canvas.CopyRect procedure employes StretchBlt which I would expect to produce the same result as BitBlt when no stretching is applied.)
Method 2
You can always use Print Screen:
procedure TForm1.FormClick(Sender: TObject);
begin
keybd_event(VK_SNAPSHOT, 1, 0, 0);
end;
This will also capture the border and the title bar. If you only wish to obtain the client area, you can crop the image:
procedure TForm1.FormClick(Sender: TObject);
var
bm, bm2: TBitmap;
DX, DY: integer;
begin
Clipboard.Clear;
keybd_event(VK_SNAPSHOT, 1, 0, 0);
repeat
Application.ProcessMessages;
until Clipboard.HasFormat(CF_BITMAP);
bm := TBitmap.Create;
try
bm.Assign(Clipboard);
bm2 := TBitmap.Create;
try
bm2.SetSize(ClientWidth, ClientHeight);
DX := (Width - ClientWidth) div 2;
DY := GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME );
BitBlt(bm2.Canvas.Handle, 0, 0, ClientWidth, ClientHeight, bm.Canvas.Handle, DX, DY, SRCCOPY);
Clipboard.Assign(bm2);
finally
bm2.Free;
end;
finally
bm.Free;
end;
end;