Limit directories in TFileOpenDialog - delphi

I'm using a TFileOpenDialog on a data entry form in Delphi XE. The user selects a PDF document in the dialog and the UNC path and filename gets stored in a database field. I want to limit the scope of where the user browses to the DefaultDirectory property and files/subdirectories below that. My hope is to prevent the user from selecting files that are on local drives or mapped drives not accessible to other users who will need the values stored in the database.
I think the way to do this is the TFileOpenDialog.OnFolderChanging event. But I’m not sure how to test if the selected file or folder is a child of DefaultDirectory. Given a filename or directory name, how can I tell if it is a dependent of DefaultDirectory?

you can compare the ShellItem property of the TFileOpenDialog dialog against the DefaultFolder property using the StartsText function and the set the CanChange value according to the result.
check this sample.
uses
StrUtils,
ActiveX,
ShlObj;
{$R *.dfm}
procedure TForm50.Button1Click(Sender: TObject);
begin
FileOpenDialog1.DefaultFolder:='C:\Program Files';
FileOpenDialog1.Execute;
end;
function GetItemName(Item: IShellItem; var ItemName: TFileName): HResult;
var
pszItemName: LPCWSTR;
begin
Result := Item.GetDisplayName(SIGDN_FILESYSPATH, pszItemName);
if Failed(Result) then
Result := Item.GetDisplayName(SIGDN_NORMALDISPLAY, pszItemName);
if Succeeded(Result) then
try
ItemName := pszItemName;
finally
CoTaskMemFree(pszItemName);
end;
end;
procedure TForm50.FileOpenDialog1FolderChanging(Sender: TObject;var CanChange: Boolean);
var
CurrentDir : TFileName;
Result : HRESULT;
begin
Result := GetItemName(TFileOpenDialog(Sender).ShellItem,CurrentDir);
CanChange := Succeeded(Result) and StartsText(TFileOpenDialog(Sender).DefaultFolder,CurrentDir);
if not CanChange then
ShowMessage('Sorry do you not have access to this folder');
end;

Just thinking along with what you're trying to do here...
You could always let the user choose a folder, and then show an error if an invalid path was chosen. After that, return the user to the root of the valid folder tree.
Advantages:
You get to do your own validation. You could have multiple starting folders, or you can have more complex patterns in what you accept and what you don't.
You can inform your users about what you want them to do, instead of forbidding what's not allowed. In modern UI patterns, it's adviced not to disable buttons, but to let the user click it, and then inform the user why a certain operation cannot be performed. Otherwise it can be confusing for users why they cannot do certain things.

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 to implement some sort of Form Manager?

I'm in the middle of a project with a number of child forms. Many of the forms may be open at once. I'd like to know if there's already something I can use to manage and keep track of these forms, much like the windows taskbar and/or task manager. If not, then what would be the best approach? I don't want to have to reinvent the wheel if this is already done.
Description
As mentioned above, this project has many forms which may be opened at once. I will also be implementing some visual list control (much like the taskbar or task manager) for user control of these forms (or in the user's case, the forms are called windows). The most ideal way to manage these would be to first capture each of these forms as they're created and keep record of them somewhere. Some forms need this behavior, and some forms do not. For example, modal forms will never need this handling.
I will be giving the user access to show, minimize, or close these forms, as well as some other future un-thought handling, like maybe a custom popup menu associated with one of these forms (but that's another subject). The point is, I need to build something to capture these forms and keep them in order.
This will also include some other user interaction with all the forms at once, as well as simple access to each one of them, similar to how Screen.Forms already works. For example, a command to minimize all forms (FormManager.MinimizeAll), to maximize the currently active form (FormManager.ActiveForm.Maximize), or with a particular form (FormManager[3].Maximize).
Possible Options
I understand there are a few far different approaches to accomplish similar results, and haven't started coding it yet because each of those approaches has a different starting point. The options are...
Wrap Screen.Forms and other associated functionality from the Screen (which wouldn't allow too much of my desired flexibility)
Every time I create a form, register it with this form manager (which is very flexible, but I have to make sure I always register each created form)
Build a master form to register its self with the form manager and inherit everything from it (which is also very flexible, but in different ways, and much more complex)
The second option is sounding the most promising so far. But again, I don't want to start building it if there is already a solution for this. I'm pretty confident that I'm not the first person to do this. I don't know how to search for such a thing, I get nothing related to what I want on Google.
The global variable Screen (in Forms unit) does some "tracking", ie
Screen.Forms list all currently open forms;
Screen.ActiveForm form which has input focus (see also FocusedForm);
Screen.OnActiveFormChange event;
You could add each form to a TObjectList. I wrote a component called FormStack, which allows you to add forms (even forms with the same name), retrieve, remove, etc. To get a Task Manager like behavior, I think you'd just need to iterate the list to obtain form names . Hopefully you can use something here to shed some light on your idea..
Here's the code for FormStack.
unit uFormstack;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Contnrs;
type
TFormstack = class(TComponent)
private
{ Private declarations }
FormList: TObjectList;
protected
{ Protected declarations }
public
{ Public declarations }
Constructor Create(AOwner: TComponent); Override;
Destructor Destroy; Override;
Procedure Add(InstanceClass: TComponentClass; Var Reference);
Procedure RemoveLast;
Procedure RemoveAll;
Function FindForm(AComponentClass: TComponentClass): Boolean;
Function GetForm(AComponentClass: TComponentClass): TObject;
Function GetByIndex(AIndex: Integer): TObject;
Procedure RemoveByIndex(AIndex: Integer);
published
{ Published declarations }
end;
procedure Register;
implementation
//{$R *.res}
procedure Register;
begin
RegisterComponents('FormStack', [TFormstack]);
end;
{-----------------------------------------------------------------------------
TFormStack
-----------------------------------------------------------------------------}
Constructor TFormStack.Create(AOwner: TComponent);
Begin
Inherited Create(AOwner);
FormList := TObjectList.Create;
FormList.OwnsObjects := True;
End;
Destructor TFormStack.Destroy;
Begin
FormList.Free;
Inherited Destroy;
End;
Procedure TFormStack.Add(InstanceClass: TComponentClass; Var Reference);
Var
Instance: TComponent;
Begin
Instance := TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
Instance.Create(Self); // Owner is FormList <<-- blows up if datamodule in D2010
FormList.Add(Instance);
Instance.Tag := FormList.Count-1;
End;
Procedure TFormStack.RemoveAll;
Var
I: Integer;
Begin
For I := FormList.Count -1 downto 0 do // last in first out
begin
Self.RemoveLast;
End;
End;
// This removes the last form on the stack
Procedure TFormStack.RemoveLast;
Begin
if FormList.Count > 0 then
FormList.Remove(FormList.Items[FormList.Count-1]);
End;
Function TFormStack.FindForm(AComponentClass: TComponentClass): Boolean;
Var
I: Integer;
Begin
Result := False;
For I := FormList.Count-1 downto 0 do
If Formlist.Items[I].ClassType = AComponentClass then
Result := True;
End;
Function TFormStack.GetForm(AComponentClass: TComponentClass): TObject;
Var
I: Integer;
begin
Result := Nil;
For I := FormList.Count-1 downto 0 do
If Formlist.Items[I].ClassType = AComponentClass then
Result := FormList.Items[I];
end;
Function TFormStack.GetByIndex(AIndex: Integer): TObject;
begin
Result := Nil;
If FormList.Count-1 >= AIndex then
Result := FormList.Items[AIndex];
end;
Procedure TFormStack.RemoveByIndex(AIndex: Integer);
begin
If FormList.Count-1 >= AIndex then
FormList.Remove(FormList.Items[AIndex]);
end;
end.
If I understand you correctly, you want to track this in code while the app is running?
Maybe you can do something with Screen.Forms?

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;

Fastreports custom preview problem

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.

Dynamically list all forms in a project

I want to list name of all forms exist in my project in a ListBox Dynamically, then by clicking on each of them, list all buttons exist on that form in another ListBox.
But I don't know if it can be implemented and how it can.
In case you are on Delphi 2010 you can use RTTI to list all registered ( = somehow used in the application) form classes:
uses
TypInfo, RTTI;
procedure ListAllFormClasses(Target: TStrings);
var
aClass: TClass;
context: TRttiContext;
types: TArray<TRttiType>;
aType: TRttiType;
begin
context := TRttiContext.Create;
types := context.GetTypes;
for aType in types do begin
if aType.TypeKind = tkClass then begin
aClass := aType.AsInstance.MetaclassType;
if (aClass <> TForm) and aClass.InheritsFrom(TForm) then begin
Target.Add(aClass.ClassName);
end;
end;
end;
end;
You must somehow take care that the class is not completely removed by the linker (therefor the registered hint above). Otherwise you cannot get hands on that class with the method described.
The forms are usually listed using Screen.Forms property, ex:
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
Memo1.Lines.Clear;
for I:= 0 to Screen.CustomFormCount - 1 do
Memo1.Lines.Add(Screen.Forms[I].Caption);
end;
sabri.arslan's answer is the way to go to find all instantiated forms at run-time.
In the comments Hamid asked for a way to find unassigned forms as well. Assuming that by unassigned he means uninstantiated forms, there would be only one way to do so and that is to iterate over the registry of classes used by the vcl streaming system to instantiate components by name when a dfm is streamed in.
However, IIRC, forms are not automatically added to the registry. In fact, if you want to instantiate forms based on a string of their name, you need(ed) to add them to the class registry yourself. OP could of course do that for each of the forms in his project himself. But, that leaves the problem that the class registry used by the streaming system is implemented using var's in the implementation section of the classes unit. And therefore can't be iterated over (easily) from the outside.
So the solution would be to use the initialization section of all form units in the project and register each form in a "roll-your-own" registry with their name and class and have the registry provide the methods to iterate over the registered forms. These method can be used to populate the listbox mentioned by the OP.
To get at the TButtons on a form would then require instantiating the form (it could remain hidden) and iterating over the components using code similar to sabri.arslan's answer to find the TButton instances.
Instantiating the form would require getting the class of the form from the registry based on the form's name selected in the listbox.
Example of a simple roll-your-own form registry:
unit Unit1;
interface
uses
Classes
, Forms
, SysUtils
;
procedure RegisterForm(aName: string; aClass: TFormClass);
procedure ListForms(aNames: TStrings);
function InstantiateForm(aName: string): TCustomForm;
implementation
var
FormRegistry: TStringList;
procedure RegisterForm(aName: string; aClass: TFormClass);
begin
FormRegistry.AddObject(aName, Pointer(aClass));
end;
procedure ListForms(aNames: TStrings);
var
i: Integer;
begin
for i := 0 to FormRegistry.Count - 1 do begin
aNames.Add(FormRegistry[i]);
end;
end;
function InstantiateForm(aName: string): TCustomForm;
var
idx: Integer;
frmClass: TFormClass;
begin
Result := nil;
idx := FormRegistry.IndexOf(aName);
if idx > -1 then begin
frmClass := TFormClass(FormRegistry.Objects[idx]);
Result := frmClass.Create(nil);
end;
end;
initialization
FormRegistry := TStringList.Create;
FormRegistry.Duplicates := dupError;
FormRegistry.Sorted := True;
finalization
FreeAndNil(FormRegistry);
end.
you can use "for" loop.
procedure ListForms(lbForms:TListBox);
var
i,j:integer;
begin
for i:=0 to application.ComponentCount-1 do
if application.components[i] is tform then
begin
lbForms.add(tform(application.components[i]).Name);
end;
end;
procedure ListBox1Click(Sender:TObject);
var
ix,j,i:integer;
begin
ix:=ListBox1.ItemIndex;
if ix>=0 then
begin
for i:=0 to application.componentcount-1 do
if application.components[i] is tform then
begin
if tform(application.components[i]).name=listbox1.items.strings[ix] then
begin
for j:=0 to tform(application.components[i]).controlcount - 1 do
if tform(application.components[i]).controls[i] is tbutton then
begin
listbox2.add(tbutton(tform(application.components[i]).controls[i]).caption);
end;
break;
end;
end;
end;
end;
There is no way (easy) to find the included forms.
But if you loop through the RCdata of the resources (See (1) (2) (3)), you can find the names of the forms. But that dosn't help you creating them.
In order to make forms "findable" the have to "register" them yourself, using RegisterCLass og finding them again using FindClass. See an example here: http://www.obsof.com/delphi_tips/delphi_tips.html#Button
Do you need this to be built at run time, or would compile time information work for you?
In recent versions (Delphi 2006 and higher?), you can set a compiler option to generate XML documentation for your project. A separate XML file is generated for each unit. You could parse this XML to find and forms and look at the members for any buttons.

Resources