Fastreports custom preview problem - delphi

I encounter problem on FastReports, it will not print correctly on pages which contain Korean character. It hapens only on Printer HP K5300 jet, T test it using rave and having no problem. I think it a bug for fast reports. I already convert all my reports from rave to FastReports and dont have plan to moved back.
I am planning to get the generated pages as images without saving it to hard drive and then generate a new preports. this time, the generated images will be used and print. I know this solution is not good. this is worable for now While waiting for their responds.
anybody has any idea how to get the images form generated pages?

If you just want to avoid saving a lot of files, you can create a new export class to print the file just after it is created and delete it instantly.
You can create a whole new export class which print the bitmap from memory (for example, using the TPrinter class and drawing the bitmap directly in the printer canvas)... you will learn how checking the source file of the TfrxBMPExport class.
Take this untested code as an example which will guide you how to create a new class to save/print/delete:
type
TBMPPrintExport = class(TfrxBMPExport)
private
FCurrentPage: Integer;
FFileSuffix: string;
protected
function Start: Boolean; override;
procedure StartPage(Page: TfrxReportPage; Index: Integer); override;
procedure Save; override;
end;
{ TBMPPrintExport }
procedure TBMPPrintExport.Save;
var
SavedFileName: string;
begin
inherited;
if SeparateFiles then
FFileSuffix := '.' + IntToStr(FCurrentPage)
else
FFileSuffix := '';
SavedFileName := ChangeFileExt(FileName, FFileSuffix + '.bmp');
//call your actual printing routine here. Be sure your the control returns here when the bitmap file is not needed anymore.
PrintBitmapFile(SavedFileName);
try
DeleteFile(SavedFileName);
except
//handle exceptions here if you want to continue if the file is not deleted
//or let the exception fly to stop the printing process.
//you may want to add the file to a queue for later deletion
end;
end;
function TBMPPrintExport.Start: Boolean;
begin
inherited;
FCurrentPage := 0;
end;
procedure TBMPPrintExport.StartPage(Page: TfrxReportPage; Index: Integer);
begin
inherited;
Inc(FCurrentPage);
end;
In production code you will want to override another methods to initialize and finalize the printer job, cleaning up, etc.
Code is based on FastReport v4.0 implementation of TfrxCustomImageExport, specially for page numbering and file naming. It may require adjustments for other FastReport versions.

You can use the TfrxBMPExport (frxExportImage unit) component to save the report as BMP.
For example, this code will export the report:
procedure ExportToBMP(AReport: TfrxReport; AFileName: String = '');
var
BMPExport: TfrxBMPExport;
begin
BMPExport := TfrxBMPExport.Create(nil);
try
BMPExport.ShowProgress := True;
if AFileName <> '' then
begin
BMPExport.ShowDialog := False;
BMPExport.FileName := AFileName;
BMPExport.SeparateFiles := True;
end;
AReport.PrepareReport(True);
AReport.Export(BMPExport);
finally
BMPExport.Free;
end;
end;
The Export component, in this case, uses a different file name for each page. If you pass 'c:\path\report.bmp' as the filename, the export component will generate c:\path\report.1.bmp, c:\path\report.2.bmp and such.
As usual, you can drop and manually configure the component on any form/data module if you prefer that way.

Related

Indy download selected file from the listbox

I never used Indy and am struggling to learn the basic. Took me some time to figure out how to populate the listbox. Now that I have done that how can I download the selected file in the listbox ?
I tried :
procedure TFTP.Button2Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to ListBox1.Items.Count - 1 do begin
if ListBox1.Selected[i] then begin
IdFTP1.Get(listbox1.Selected[i]);
end;
end;
end;
But I am getting :
[dcc32 Error] FTP_Form.pas(75): E2250 There is no overloaded version
of 'Get' that can be called with these arguments
Or do I need to use a savedialog too? Please help me with this. :)
ListBox1.Selected[i] is a Boolean. Note that in the previous line you wrote:
if ListBox1.Selected[i] then begin
Now, look at the TIdFTP.Get() method. It has two overloads:
procedure Get(const ASourceFile: string; ADest: TStream;
AResume: Boolean = false); overload;
procedure Get(const ASourceFile, ADestFile: string; const ACanOverwrite: boolean = false;
AResume: Boolean = false); overload;
You need to provide:
the source filename of the remote file you want to download.
a destination filename or stream to receive the content of the remote file.
I don't know where you intend to obtain these. Presumably the filename comes from the ListBox, which would therefore be ListBox1.Items[i].
What do you want to do with the content you download? Keep it in memory? Save it to a file? Something else? What destination you supply depends on your answers to those questions.
My advice to you is to put the ListBox to one side for the moment, and write a simpler program, one without any UI, that simply downloads a single file from the FTP server. Use a local filename or a TFileStream to save the downloaded content to your local disk. Check that the contents are what you expect. Once you can download one file, you can download any number of files, to other kinds of destinations.
Once you have mastered that, move on to the user interface. Spend some time learning how the ListBox control works, how you populate it, how you read back strings from it, how you test for selection, and so on.
Only when you have a good understanding of all parts involved, then you should you try to fit them together.
One way ....
procedure TFTP.Button2Click(Sender: TObject);
Var
Name{, Line}: String;
begin
Name := IdFTP1.DirectoryListing.Items[ListBox1.ItemIndex].FileName;
SaveDialog1.FileName := Name;
if SaveDialog1.Execute then begin
IdFTP1.Get(Name, SaveDialog1.FileName, true);
end;
end;
Assuming the ListBox contains the remote filenames to download (such as from the TIdFTP.DirectoryListing property after a call to TIdFTP.List()):
procedure TFTP.Button2Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to ListBox1.Items.Count - 1 do
begin
if ListBox1.Selected[i] then begin
IdFTP1.Get(ListBox1.Items[i], 'C:\Some Local Path\' + ListBox1.Items[i]);
end;
end;
end;

How do I append data to existing clipboard in a new format without removing the existing data?

I need to put data on the clipboard in several formats, one being RTF. The component I'm using handles everything except for RTF wonderfully. How can I append the RTF format data without blowing away the data placed there already by the ancestor class? I'm trying to avoid duplicating the clipboard logic from the ancestor in my copy to clipboard routine if there's a way to do so.
You can append to the existing clipboard data by doing the following:
Call Clipboard.Open.
Call the base class method that puts the other data on the clipboard.
Call Clipboard.SetAsHandle, or SetClipboardData passing your RTF.
Call Clipboard.Close.
So long as the inherited call is inside your Open/Close pair you will get the desired effect.
Here is a demonstration of what I mean:
procedure SetBuffer(Format: Word; const Buffer; Size: Integer);
var
DataPtr: Pointer;
Data: THandle;
begin
Data := GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, Size);
try
DataPtr := GlobalLock(Data);
try
Move(Buffer, DataPtr^, Size);
Win32Check(SetClipboardData(Format, Data) <> 0);
finally
GlobalUnlock(Data);
end;
except
GlobalFree(Data);
raise;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Bitmap: TBitmap;
const
Text: string = 'foo';
begin
Clipboard.Open;
try
// imagine this next block is the base component's method to set the clipboard
Clipboard.Open;
try
Bitmap := GetFormImage;
try
Clipboard.Assign(Bitmap);
finally
Bitmap.Free;
end;
finally
Clipboard.Close;
end;
// once that is done, we can add out extra data
SetBuffer(CF_UNICODETEXT, Text[1], ByteLength(Text));
finally
Clipboard.Close;
end;
end;
Because we hold a lock on the clipboard the whole time, no other process can get in our way. And the base component method will empty the clipboard before it starts adding its text.

Abbrevia ProgressBar

I am using the Open Source Abbrevia Components to Archive some Files into a single Zip, whilst this is happening I am using the TAbMeter Gauge to display the progress.
I would prefer to use the TProgressBar for this purpose instead though (maintaining a standard interface with Windows).
How may I use a TProgressBar instead of the TAbMeter? I know I could code the progress myself, but seeing as the Abbrevia Components already have this done in the TAbMeter, I see no sense in rewriting it.
If I could even access the Position property of the TAbMeter I could simulate the TProgressBar's progress by synchronizing with the TAbMeter.
Here is a snippet, FileNames is a TStringList containing the Filenames to archive..
procedure ArchiveFiles(SaveAs: string; ProgressBar: TAbMeter);
var
AZipper: TAbZipper;
i: Integer;
begin
AZipper := TAbZipper.Create(nil);
try
AZipper.AutoSave := False;
AZipper.BaseDirectory := ExtractFilePath(SaveAs);
AZipper.ArchiveSaveProgressMeter := ProgressBar;
AZipper.FileName := SaveAs;
AZipper.StoreOptions := AZipper.StoreOptions + [soStripDrive, soRemoveDots]
- [soStripPath];
AZipper.TempDirectory := GetTempDirectory;
try
Screen.Cursor := crHourGlass;
ProgressBar.Visible := True;
for i := 0 to FileList.Count - 1 do
begin
AZipper.AddFiles(FileList.Strings[i], 0);
end;
finally
AZipper.Save;
AZipper.CloseArchive;
ProgressBar.Visible := False;
Screen.Cursor := crDefault;
end;
finally
AZipper.Free;
end;
end;
You are presumably setting the ArchiveSaveProgressMeter somewhere in your code. You can simply stop doing this and instead set the OnArchiveSaveProgress event. Then you need to supply an event with this signature:
procedure(Sender: TObject; Progress: Byte; var Abort: Boolean) of object;
You would respond to receipt of such an event by updating the Position value of the progress bar in your UI.
The method that surfaces this progress event also handles the progress meter version:
procedure TAbCustomZipper.DoArchiveSaveProgress(
Sender: TObject; Progress: Byte; var Abort : Boolean);
begin
Abort := False;
if Assigned(FArchiveSaveProgressMeter) then
FArchiveSaveProgressMeter.DoProgress(Progress);
if Assigned(FOnArchiveSaveProgress) then
FOnArchiveSaveProgress(Self, Progress, Abort);
end;
So the designers of the component have simply provided two alternative routes to receiving progress: the meter or a callback.
In order to handle progress from a callback you need to write a method like this:
procedure TMyMainForm.OnArchiveSaveProgress(
Sender: TObject; Progress: Byte; var Abort: Boolean);
begin
FProgressBar.Position := Progress;
end;
You then assign this event handler to OnArchiveSaveProgress, most likely in the IDE. It's an identical procedure to assigning an OnClick event to a button.
Note: I've never used Abbrevia so I may have picked out a different component from the one you are using. However, all the components that interact with meters, also offer progress via a callback so this basic approach will work no matter which component you use.
In case it helps anyone else, I've created a new TAbProgressBar component that can be used instead of TAbMeter. They both implement the same interface, so it works with the same Archive*ProgressMeter properties. Just update Abbrevia from Subversion and recompile the AbbreviaVCL and AbbreviaVCLDesign packages.

How Can I Get Context Sensitive He-lp in Delphi to Use Symbolic Names instead of HelpID Aliases?

I'm building my help system into my program, and I'm working on my context sensitive help which should bring up the appropriate help page for the active control when F1 is pushed.
On each control, I can set HelpType to htContext and HelpContext to the HelpID, or I can set HelpType to htKeyword and HelpContext to the HelpID Alias.
But in my help system (Dr. Explain), I have set up symbolic names (i.e. some text that is used as a bookmark in my help system). This is different than the HelpID and its alias, and is accessible from the Help system with the call: Application.HelpJump(SymbolicName).
I would like to use the HelpContext field for my symbolic names which is much simpler and easier to maintain than creating a duplicate set of HelpID Aliases. And I won't have to worry about creating the Help map file or have to deal with it.
It is the HelpKeyword routine, in the Forms unit, that processes the F1 when when HelpType is htKeyword:
function TApplication.HelpKeyword(const Keyword: string): Boolean;
var
CallHelp: Boolean;
begin
{$IF DEFINED(CLR)}
Result := DoOnHelp(HELP_COMMAND, TObject(Keyword), CallHelp);
{$ELSE}
Result := DoOnHelp(HELP_COMMAND, Integer(PChar(Keyword)), CallHelp);
{$IFEND}
if CallHelp then
begin
if ValidateHelpSystem then
begin
{ We have to asume ShowHelp worked }
Result := True;
HelpSystem.ShowHelp(Keyword, GetCurrentHelpFile);
end
else
Result := False;
end;
end;
To get this to work to process my symbolic names, all I really have to do is replace the routine with:
function TApplication.HelpKeyword(const Keyword: string): Boolean;
begin
Application.HelpJump(Keyword);
Result := true;
end;
What I can't seem to do is figure out how to write the proper code to customize the functionality of this routine in a clean way, without having to hack the Forms unit itself. How can I do this?
Or alternatively, is there another way to easily get the context sensitive help to access my help page based on the symbolic name?
For reference, I'm using Delphi 2009 (but will be upgrading to XE2 in the next month or so).
p.s. The word in the title is "He-lp" because stackoverflow won't let me put the word "Help" in the title.
Try this in your OnHelp event handler (of your form, or of the global Application, depending on what you're using):
function TForm1.FormHelp(Command: Word; Data: Integer; var CallHelp: Boolean): Boolean;
begin
if Command = HELP_COMMAND then
begin
// avoid default processing
CallHelp := False;
// do your own processing - in this case, do what Application.HelpJump would do
Application.HelpSystem.ShowTopicHelp(PChar(Data), Application.CurrentHelpFile);
// assume it worked
Result := True;
end;
end;

How to save and restore a form?

So, I have a form with a few dozen controls and someone would like to save and later restore their contents and settings - which radio button was selected, what was the Position of that up/down, etc.
I would also like to store any entries added to a list box at run time.
What's the simplest way to do it? DfmToString and reverse? Write/read a .INI? Something else?
PRUZ's solution is a ready made solution; JVCL is open-source, and using JvFormStorage is simple. But you can also use Delphi's own streaming mechanism without using any third-party components. Here is an example:
procedure SaveComponentToFile(Component: TComponent; const FileName: TFileName);
var
FileStream : TFileStream;
MemStream : TMemoryStream;
begin
MemStream := nil;
if not Assigned(Component) then
raise Exception.Create('Component is not assigned');
FileStream := TFileStream.Create(FileName,fmCreate);
try
MemStream := TMemoryStream.Create;
MemStream.WriteComponent(Component);
MemStream.Position := 0;
ObjectBinaryToText(MemStream, FileStream);
finally
MemStream.Free;
FileStream.Free;
end;
end;
SaveComponentToFile takes a component instance, plus a file name, and streams the component into the file, in a human-readable text.
To load the component from file, you can use a code like this:
procedure LoadComponentFromFile(Component: TComponent; const FileName: TFileName);
var
FileStream : TFileStream;
MemStream : TMemoryStream;
i: Integer;
begin
MemStream := nil;
if not Assigned(Component) then
raise Exception.Create('Component is not assigned');
if FileExists(FileName) then
begin
FileStream := TFileStream.Create(FileName,fmOpenRead);
try
for i := Component.ComponentCount - 1 downto 0 do
begin
if Component.Components[i] is TControl then
TControl(Component.Components[i]).Parent := nil;
Component.Components[i].Free;
end;
MemStream := TMemoryStream.Create;
ObjectTextToBinary(FileStream, MemStream);
MemStream.Position := 0;
MemStream.ReadComponent(Component);
Application.InsertComponent(Component);
finally
MemStream.Free;
FileStream.Free;
end;
end;
end;
LoadComponentFromFile takes a component instance, and a file name, then loads file content into the component instance. To avoid naming conflict, we are free all existing owned components of the instance, before loading file data into it.
Now you can use the above code for saving a form into a file:
SaveComponentToFile(FSecondForm,ExtractFilePath(Application.ExeName)+ 'formdata.txt');
FSecondForm is a form instance, and it will be saved into "formdata.txt" file inside the same folder as the EXE file.
And to load FSecondForm from "formdata.txt" file, we write this:
if not Assigned(FSecondForm) then
FSecondForm := TfrmSecond.Create(Application);
LoadComponentFromFile(FSecondForm,ExtractFilePath(Application.ExeName)+ 'formdata.txt');
FSecondForm.Show;
LoadComponentFromFile needs the instance to be created first, so we check if FSecondForm is assigned, if not, we create an instance of it (it is an instance of TfrmSecond class), and then load file data into it. And eventually, we show the loaded form.
It is pretty easy to read/write component or object properties, or forms position in INI file or registry. Everything you need exist in help. You just need to decide when you want to read them (on creating, before showing...) and store them (on close, ...). This depends on what you are saving/restoring.
If you are going to use ready made components and want to save form position, then make sure to check how do they treat multiple monitors. If you are doing it your own way, you should take care of that yourself. For example, you might have a laptop and a big 22" monitor, and position of a form was saved while your big monitor was used. Later, if you open this form on laptop it might be displayed of screen so you can not see the form if this case is not handled properly.

Resources