menu
  Home  ==>  papers  ==>  colibri_utilities  ==>  treeview_html_help_viewer   

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:
  • for a 3 level deep page, the Wiki already contains glossaries, which links to secondary link pages, which link to our target .HTML page.

    However, using a TreeView allows to keep the "context": we see the neigbour pages and the chapters in which this topic is presented. This was not the case for the Wiki pages (only the path gives a hint of the topic area)

  • using our own indexing scheme allows us to organize the help in any way we find relevant for our use (drop administrative pages, for instance)


Here is an example of the resulting help viewer:

prism_wiki_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_reftWinControl;

               m_c_paneltPanel;
               m_c_full_expand_button_tButton;

               m_c_treeviewtTreeView;

               m_pathm_file_nameString;
               m_did_changeBoolean;

               Constructor create_expand_treeview(p_nameString;
                   p_c_parent_component_reftWinControl);

               procedure display_treeview;

               procedure handle_expand_click(p_c_sendertObject);

               procedure handle_treeview_dragover(p_c_senderp_c_sourceTObject;
                   p_xp_yInteger;
                   p_drag_stateTDragStatevar pv_acceptBoolean);
               procedure handle_treeview_drag_drop(p_c_senderp_c_sourceTObject;
                   p_xp_yInteger);

               procedure load_from_file(p_full_file_nameString);
               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_nameString);
      var l_c_stringlisttStringList;
          l_list_indexInteger;
          l_the_linel_trimmed_lineString;
          l_indentationInteger;
          l_eolBoolean;

      procedure read_line;
        begin
          // ooo
        end// read_line;

      procedure add_node_recursive(p_indentationIntegerp_c_parent_treenodetTreeNode);
        var l_c_treenodetTreeNode;
        begin
          repeat
            if l_indentationp_indentation
              then begin
                  if p_c_parent_treenodeNil
                    then l_c_treenode:= m_c_treeview.Items.Add(p_c_parent_treenodel_trimmed_line)
                    else l_c_treenode:= m_c_treeview.Items.AddChild(p_c_parent_treenodel_trimmed_line);
                  read_line;
                end
              else
                if l_indentationp_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_senderp_c_sourceTObjectp_xp_yInteger);
      var l_c_target_treenodeTTreeNode;
          l_node_attach_modeTNodeAttachMode;
          l_hit_testTHitTests;
      begin
        l_hit_test:= m_c_treeview.GetHitTestInfoAt(p_xp_y);
        l_c_target_treenode:= m_c_treeview.GetNodeAt(p_xp_y);
        l_node_attach_mode:= naAdd;

        if (l_hit_test- [htOnItemhtOnIcon,
                htNowherehtOnIndent]<> l_hit_test)
          then
            begin
              if (htOnItem in l_hit_testor (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_treenodel_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_sendertObject);
  var l_file_nameString;
      l_browser_flagOLEVariant;
  begin
    with g_c_expand_treeview.m_c_treeview do
    begin
      if Selected<> Nil
        then begin
            l_file_name:= Selected.Text;
            if FileExists(g_pathl_file_name)
              then begin
                  original_memo.Lines.LoadFromFile(g_pathl_file_name);

                  l_file_name:= 'file://'g_pathl_file_name;
                  Caption:= l_file_name;

                  l_browser_flag := 0;
                  try
                    WebBrowser1.Navigate(WideString(l_file_name),
                      l_browser_flagl_browser_flagl_browser_flag,
                      l_browser_flag);
                  except
                    on eException 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:

prismwiki_class_contract_page

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:
  • prism_wiki_viewer.zip: the .HTML viewer and associated units, with a small .HTML and .TXT example (34 K)
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
    Name :
    E-mail :
    Comments * :
     

  • 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.
Created: feb-09. Last updated: jul-15 - 98 articles, 131 .ZIP sources, 1012 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2015. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
      – delphi_net_bdsproj
      – dccil_bat_generator
      – coliget_search_engine
      – dfm_parser
      – dfm_binary_to_text
      – component_to_code
      – exe_dll_pe_explorer
      – dll_process_viewer
      – the_alsacian_notation
      – html_help_viewer
      – cooking_the_code
      – events_record_playback
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog