Treeview .HTML Help Viewer - Felix John COLIBRI. |
- abstract : the use of a Treeview along with a WebBrowser to display .HTML files alows both structuring and ordering of the help topics
- key words : tTreeView - tTreeView drag and drop - tWebBrowser - help file - Delphi PRISM
- software used : Windows XP Home, Delphi 6
- hardware used : Pentium 2.800Mhz, 512 M memory, 140 G hard disc
- scope : Delphi 1 to 2006, Turbo Delphi for Windows, Kylix
Delphi 5, 6, 7, 8 Delphi 2005, 2006, Turbo Delphi, Turbo 2007, Rad Studio 2007, Delphi 2009 - level : Any user of .HTML files
- plan :
1 - Delphi PRISM Help viewer We recently looked at Delphi Prism. The help is provided as a "Wiki", which is a set of .HTML pages, linked by
hyperlinks, summaries, glossaries etc (plus the usual Wiki functionalities, like editing). The Wiki works just fine, but we faced a couple of problems: - we wanted to have an exhaustive view of the html files, to be sure not to
miss some deeply linked item.
- we recently installed Firefox along with Internet Explorer. And now the loading of .HTML files from our disc is rather slow
To solve those two problems, we wrote a small project which uses:
- a tTreeView containing all the .HTML pages. tTreeView Drag and Drop will allow us to classify the pages into topics (IDE, Language, Tools etc)
- the tWebBrowser to display the content of each topic.
There are at least two additional motivations:
Here is an example of the resulting help viewer:
In the remainder of this article, we will no longer reference Delphi PRISM, since the utility could be used to display just any collection of .HTML pages.
2 - The Delphi .HTML Help Viewer Project 2.1 - Viewing Local .HTML pages with a tTreeView All the .HTML pages are in a single directory.
An easy solution would be to load the .HTML pages in the tTreeNode.Text, and in the tTreeView.OnClick event, load the corresponding page in the tWebBrowser.
The organization of the pages would be set by the disc layout. Therefore - there would be no hierarchical organization
- the ordering would be alphabetical
It would be possible to create sub-folders, but each time a new set of .HTML files are downloaded (which is the case for Delphi PRISM Wiki), this operation would have to be repeated. In addition, this would not solve the order of the
nodes at the same level of the tTreeView.
Therefore we chose to use an indirection with a .TXT file which specifies both the nesting and the ordering of the pages.
We have to prepare the viewer by writing this table of content before : - we created a list of all .HTML files (1760 pages for Delphi PRISM, the 14th February)
- this list was loaded in a text editor (NotePad, Delphi ...), and reorganized
manually.
- for the intermediate nodes, we can
- either use a page present in the help
- or uses some placeholder.
We often used the second solution, and used the convention to prefix those page name with a "-"
Here is an example our our .Txt file, with the detail of the "contract" portion of the help: -ide -language -objects
-contract Class_Contracts.html Ensure_(keyword).html
Invariants_(keyword).html Old_(keyword).html Require_(keyword).html
-events ooo -db -asp -web_services -framework -faq -prism |
Note that
- 1.700 pages to classify is quite a job. It took us around an hour. However, after the first classification, we only have to incrementally add the new pages downloaded from the Wiki
- we chose the "indented" file format, rather than the "tabbed format". The reason is that tabbing will bring us quickly at the rigth border of the editor, if our classification is deep
- therefore we used the Delphi editor, which nicely allows block indentation
- our tTreeView can change the classification using drag and drop, but for massive reorganization, text manipulation is much much easier
- many pages can be classified as a group. For instance the mid-february version of the Wiki contains nearly 1.000 pages which are just some automatically generated pages from the Dbx4 units (properties, events,
methods, with one or two lines of explanation)
- to avoid the handling of the Dbx4 references in the same .TXT file, we added an "include" notation, which allows to isolate those lengthy lists in separate files. Here is an example
- of the DbxCommon pages (this file is 456 lines long)
DBXCommon--Constants.html DBXCommon--DBXDefaultRowSetSize.html
DBXCommon--TDBXAnsiStringValue--Create.html DBXCommon--TDBXAnsiStringValue--Destroy.html DBXCommon--TDBXAnsiStringValue--GetAnsiString.html DBXCommon--TDBXAnsiStringValue--IsNull.html DBXCommon--TDBXAnsiStringValue--SetAnsiString.html
DBXCommon--TDBXAnsiStringValue.html DBXCommon--TDBXBcdValue--GetBcd.html DBXCommon--TDBXBcdValue--IsNull.html DBXCommon--TDBXBcdValue--SetBcd.html DBXCommon--TDBXBcdValue.html DBXCommon--TDBXBooleanValue--GetBoolean.html
DBXCommon--TDBXBooleanValue--IsNull.html ooo | - and the main .txt for the Dbx help:
-dbExpress DbExpress.html -dbx_classes DBXCommon.html
$i dbxcommon.txt DBXCommon_Unit.html Borland.Data.MetaData.html
$i metadata.txt DBXMetaDataNames.html $i dbx_metadata_names.txt
$i dbx.txt -DataSnap ooo | Of course we can also reorganise those pages, and do this in the sub-file
To summarize: - the .HTML files are those downloaded from the web, organized in any way that suited the writer of the pages and the ordering of disc files
- our tTreeview sets
- the structure
- the ordering of the pages
and each tTreeNode contains the file address of a .HTML file (they could be nested in sub-directories) - by clicking on a tTreeNode, the page is loaded in a tWebBrowser. The
hyperlinks work as usual, and can still be used to navigate in the Help
2.2 - The tTreeView We use the standard tTreeView (not the Virtual Tree View). We implemented the following functionalities
- loading of an indented file (not the standard tabbed file)
- drag and drop of the tTreeNode which allow reorganization of the structure of the help
Since those functionalities are used in other utilities, we encapsulated them in a Class which is created in the tForm.OnCreate event.
Here is our Class: - the the definition is the following:
type c_expand_treeview=
class(c_basic_object)
m_c_parent_component_ref: tWinControl;
m_c_panel: tPanel;
m_c_full_expand_button_: tButton;
m_c_treeview: tTreeView;
m_path, m_file_name: String;
m_did_change: Boolean;
Constructor create_expand_treeview(p_name: String;
p_c_parent_component_ref: tWinControl);
procedure display_treeview;
procedure handle_expand_click(p_c_sender: tObject);
procedure handle_treeview_dragover(p_c_sender, p_c_source: TObject;
p_x, p_y: Integer;
p_drag_state: TDragState; var pv_accept: Boolean);
procedure handle_treeview_drag_drop(p_c_sender, p_c_source: TObject;
p_x, p_y: Integer);
procedure load_from_file(p_full_file_name: String);
procedure save_to_file;
end; // c_expand_treeview | - the loading of an indented file is performed like this:
procedure c_expand_treeview.load_from_file(p_full_file_name: String);
var l_c_stringlist: tStringList;
l_list_index: Integer;
l_the_line, l_trimmed_line: String;
l_indentation: Integer; l_eol: Boolean;
procedure read_line; begin
// ooo end; // read_line;
procedure add_node_recursive(p_indentation: Integer; p_c_parent_treenode: tTreeNode);
var l_c_treenode: tTreeNode; begin
repeat
if l_indentation= p_indentation
then begin
if p_c_parent_treenode= Nil
then l_c_treenode:= m_c_treeview.Items.Add(p_c_parent_treenode, l_trimmed_line)
else l_c_treenode:= m_c_treeview.Items.AddChild(p_c_parent_treenode, l_trimmed_line);
read_line; end
else
if l_indentation= p_indentation+ 1
then begin
//-- get one level down
add_node_recursive(p_indentation+ 1, l_c_treenode);
end
else Break;
until l_eol; end; // add_node_recursive
begin // load_from_file
m_c_treeview.Items.Clear;
m_c_treeview.Items.BeginUpdate;
l_c_stringlist:= tStringList.Create;
l_c_stringlist.LoadFromFile(p_full_file_name); l_list_index:= 0;
l_eol:= False; read_line;
add_node_recursive(0, Nil); l_c_stringlist.Free;
m_c_treeview.Items.EndUpdate;
end; // load_from_file | - and the drag and drop part is:
procedure c_expand_treeview.handle_treeview_drag_drop(
p_c_sender, p_c_source: TObject; p_x, p_y: Integer);
var l_c_target_treenode: TTreeNode;
l_node_attach_mode: TNodeAttachMode;
l_hit_test: THitTests; begin
l_hit_test:= m_c_treeview.GetHitTestInfoAt(p_x, p_y);
l_c_target_treenode:= m_c_treeview.GetNodeAt(p_x, p_y);
l_node_attach_mode:= naAdd;
if (l_hit_test- [htOnItem, htOnIcon,
htNowhere, htOnIndent]<> l_hit_test)
then begin
if (htOnItem in l_hit_test) or (htOnIcon in l_hit_test)
then l_node_attach_mode:= naAddChild
else
if htNowhere in l_hit_test
then l_node_attach_mode:= naAdd
else
if htOnIndent in l_hit_test
then l_node_attach_mode:= naInsert;
m_c_treeview.Selected.MoveTo(l_c_target_treenode, l_node_attach_mode);
m_did_change:= True; end;
end; // handle_treeview_drag_drop |
2.3 - The tWebBrowser display
The tWebBrowser is a natural choice for displaying the .HTML files. We used the standard loading statements in the tTreeNode.OnClick event:
procedure TForm1.handle_treeview_click(p_c_sender: tObject);
var l_file_name: String;
l_browser_flag: OLEVariant; begin
with g_c_expand_treeview.m_c_treeview do
begin if Selected<> Nil
then begin
l_file_name:= Selected.Text;
if FileExists(g_path+ l_file_name)
then begin
original_memo.Lines.LoadFromFile(g_path+ l_file_name);
l_file_name:= 'file://'+ g_path+ l_file_name;
Caption:= l_file_name;
l_browser_flag := 0;
try
WebBrowser1.Navigate(WideString(l_file_name),
l_browser_flag, l_browser_flag, l_browser_flag,
l_browser_flag);
except
on e: Exception do
begin
display('err '+ e.Message);
end;
end; // Try ... Except
end;
end; // selected <> nil
end; // with g_c_expand_treeview
end; // handle_treeview_click |
The only difficulty was to enable the cut and paste features. A quick search in
the Delphi Newsgroups told us to use OleInitialize and OleUnInitialize, which we included in the Unit Initialization / Finalization part:
Initialization OleInitialize(nil); finalization
OleUninitialize; end. |
3 - Comments 3.1 - Classes vs Components
You might have noticed that we used a simple Class to encapsulate the tTreeView handling: - the Class contains all the functionalities
- the object is created either in tForm.OnCreate or on demand during a
suitable tButton.OnClick, for instance. The Constructor contains the (hard coded) properties that would have been initialized from the Object Inspector
As we already explained in this site, this approach allows "version resistant" applications, which can be compiled from Delphi 5 to Delphi 2009 without having to install components on the Palette. This is well suited for our "feature
poor" Classes, for which a full fledged component would be, in our opinion, an overkill. And when you work on different PCs (our own, our students, our customer) and with different versions of Delphi sometimes on the same machine,
this quickly becomes an advantage. The .ZIP also contains a small c_load_save_memo Classes which is quite self explanatory.
3.2 - Help on Help
This help viewer is a nice improvement over our previous "help reordering endeavour". When Zack URLOCKER gave us in 1995 the first Delphi 1.0 Beta version (nicknamed VBK: Visual Basic Killer !), we used a "Windows help
disassembler", printed all the files, and jumped around the room for nearly a day to reorganize the pages into chapters. Well, reordering the Delphi PRISM pages only took around an hour (plus a couple of hours to write de viewer).
You might also have noticed that our .HTML display is quite lean. The WIKI display for the same topic is:
In fact we used yet another utility to remove all the logos, titles, directories, repetitive useless headers and footers and what not, to just keep the pure help text. The tTreeView already indicates the chapter and the topic.
3.3 - JFIF Images ? Just one (small) rant: all the images are .PNG suffixed files. This worked nicely with Internet Explorer and Firefox. But not with tWebBrowser (at least
not the Delphi 6 tWebBrowser, or the ActiveX which was installed at that time). The reason is that some of the .PNG files are in fact .JPEG files. We simply displayed an hex dump of the start of those files, and found an nice "JFIF"
signature (an not the standard PNG one): 0000: FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 60 : ......JF
IF.....` < | Google told us that this was some kind of .JPEG file, so we used a small utility to convert the files with this JFIF files into .PNG files, using the
tJpegImage and the usual .Png generator
Maybe it is standard practice to use .PNG suffixes for .JPEG files, but for us, this smelled like naming a Double variable my_string. Anyway ...
3.4 - About PrismWiki The Delphi PRISM Wiki is a moving target. Currently, this certainly is welcome to beef up the original WIKI (about 400 files (=topics) for the whole Delphi PRISM product).
To accomodate this constant change, we added to our viewer a small utility which extracts the "new files", and now we only have to add those new pages to the previous .TXT table of content
4 - Download the Sources Here are the source code files: The .ZIP file(s) contain: - the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
- any .TXT for parameters, samples, test data
- all units (.PAS) for units
Those .ZIP - are self-contained: you will not need any other product (unless expressly mentioned).
- for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP: - create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder. The Pascal code uses the Alsacian notation, which prefixes identifier by
program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lass etc. This notation is presented in the Alsacian Notation paper. The .ZIP file(s) contain:
- the main program (.DPROJ, .DPR, .RES), the main form (.PAS, .ASPX), and any other auxiliary form or files
- any .TXT for parameters, samples, test data
- all units (.PAS .ASPX and other) for units
Those .ZIP
- are self-contained: you will not need any other product (unless expressly mentioned).
- will not modify your PC in any way beyond the path where you placed the .ZIP
(no registry changes, no path outside from the container path creation etc).
To use the .ZIP: - create or select any folder of your choice.
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder. The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre,
F_unction, C_lass etc. This notation is presented in the Alsacian Notation paper.
As usual:
- please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will
be helpful for other readers
- we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
- or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
- and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in
your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.
5 - Links and References The Delphi PRISM Wiki can be found at Delphi PRISM Wiki, and includes links to download a .ZIP of all the files.
6 - The author Felix John COLIBRI works at the Pascal
Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions. |