Creating a file browser for Linux using Kylix
By Corbin Dunn
Delphi Developer Support
One of the first things that I wanted to do with Kylix was create a simple Linux file browser such as this:
I am only going to cover the general outline of how I made this project, so download the complete source from CodeCentral. I realize that it is a zip file, and Linux users like tar.gz'd files, but CodeCentral stores everything as zips. The KDE Archiver program allows you to easily unzip files on Linux.
I started the project by putting down a TTreeView on the form and set its Align property to alLeft. The TTreeView will hold the current directory tree. Next, I put down a TSplitter, so the user could resize the components at run time. Then, I put down a TListView, which will hold the files for the currently selected directory.
The first problem is finding directories. I could find all directories on the entire system when the application starts, but this would be very time consuming. Plus, the user may not expand all directories. So, I need to keep track of whether or not a user has opened up a given directory. TTreeNode's have a Data property that allows the programmer to store whatever information they want in it. I decided to create a new TTreeFolderData class that stores information about a given TTreeNode:
TTreeFolderData = class
private
FPath: string;
FHasSubFolders: Boolean;
FOpened: Boolean;
public
constructor Create(const Path: string);
destructor Destroy; override;
property Path: string read FPath write FPath;
property HasSubFolders: Boolean read FHasSubFolders write FHasSubFolders;
property Opened: Boolean read FOpened write FOpened;
end;
In my Form's OnCreate event, I call a LoadTreeDirectory procedure that fills in the root TTreeNode of the TTreeView:
procedure TMainForm.LoadTreeDirectory;
var
RootNode: TTreeNode;
TreeFolderData: TTreeFolderData;
begin
trvwLocal.Items.BeginUpdate; // trvwLocal is my TTreeView on the left
try
trvwLocal.Items.Clear;
RootNode := trvwLocal.Items.Add(nil, 'Root');
RootNode.ImageIndex := 0;
TreeFolderData := TTreeFolderData.Create(PathDelim);
TreeFolderData.Opened := True;
RootNode.Data := TreeFolderData;
AddSubFolders(PathDelim, RootNode);
RootNode.Expand(False);
finally
trvwLocal.Items.EndUpdate;
end;
end;
The AddSubFolders procedure simply takes a given path and node, and finds all folders that exist in it. I simply use our standard FindFirst and FindNext procedures, looking for faDirectory's:
procedure TMainForm.AddSubFolders(const Directory: string;
ParentNode: TTreeNode);
var
SearchRec: TSearchRec;
Attributes: Integer;
NewNode: TTreeNode;
TreeFolderData: TTreeFolderData;
begin
trvwLocal.Items.BeginUpdate;
try
// First, delete any subfolders from the parent node.
if ParentNode <> nil then
ParentNode.DeleteChildren;
Attributes := faAnyFile;
if FindFirst(IncludeTrailingPathDelimiter(Directory) + '*',
Attributes, SearchRec) = 0 then
begin
repeat
if (SearchRec.Attr and faDirectory) > 0 then
if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
begin
NewNode := trvwLocal.Items.AddChild(ParentNode, SearchRec.Name);
NewNode.ImageIndex := 0;
TreeFolderData := TTreeFolderData.Create(
IncludeTrailingPathDelimiter(Directory) + SearchRec.Name);
NewNode.Data := TreeFolderData;
if HasSubFolder(TreeFolderData.Path) then
begin
TreeFolderData.HasSubFolders := True;
// Add a fake child so the + appears
trvwLocal.Items.AddChild(newNode, '').ImageIndex := 0;
end
end;
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
finally
trvwLocal.Items.EndUpdate;
end;
end;
One thing to notice is the call to my HasSubFolder function. By checking to see if a given folder has a subfolder, I can add a fake child node so the + appears in the TTreeView. To dynamically fill in the sub folders when the user expands the plus, I have the following code in the TTreeView OnExpanded event:
procedure TMainForm.trvwLocalExpanded(Sender: TObject; Node: TTreeNode);
var
TreeFolderData: TTreeFolderData;
begin
TreeFolderData := TTreeFolderData(Node.Data);
if not TreeFolderData.Opened then
begin
TreeFolderData.Opened := True;
AddSubFolders(TreeFolderData.Path, Node);
end;
Node.ImageIndex := 1;
end;
Finally, when a directory is selected in the TTreeView, I need to update the TListView on the right with the selected directories contents. In the TTreeView's OnChange event I use the TTreeFolderData to find what path the current node contains, and then fill in the TListView with my FindFiles event:
procedure TMainForm.trvwLocalChange(Sender: TObject; Node: TTreeNode);
begin
if Node <> nil then
FindFiles(TTreeFolderData(Node.Data).Path);
end;
procedure TMainForm.FindFiles(const Directory: string);
var
SearchRec: TSearchRec;
Attributes: Integer;
Pixmap: QPixmapH;
begin
// Find files for the local folder to be added to the list view
lstvwLocal.Items.BeginUpdate;
try
lstvwLocal.Items.Clear;
Attributes := faAnyFile;
if FindFirst(IncludeTrailingPathDelimiter(Directory) + '*',
Attributes, SearchRec) = 0 then
begin
repeat
if (SearchRec.Attr and faDirectory) = 0 then
begin
with lstvwLocal.Items.Add do
begin
Caption := SearchRec.Name;
ImageIndex := 0;
SubItems.Add(IntToStr(SearchRec.Size));
SubItems.Add(DateTimeToStr(FileDateToDateTime(SearchRec.Time)));
// Work around for a bug in setting the image to nothing
// Search the community site for information on how to modify
// the VCL to have this fix.
Pixmap := QPixmap_create;
QListViewItem_setPixmap(Handle, 1, Pixmap);
QListViewItem_setPixmap(Handle, 2, Pixmap);
QPixmap_destroy(Pixmap);
end;
end;
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
finally
lstvwLocal.Items.EndUpdate;
end;
end;
That should pretty much be it! There are some other minor things I am leaving out, so download the complete source from CodeCentral and dig into it.