menu
  Home  ==>  papers  ==>  controls  ==>  find_memo   

Find Memo Control - Felix John COLIBRI.

  • abstract : the e c_find_memo unit allows to place on a tForm a tMemo with "find", "find next" and save functions. The found occurences are highlighted and the text scrolls to this position
  • key words : tMemo - Search function - Line of Caret - Scroll to Caret
  • software used : Windows XP, Delphi 6
  • hardware used : Pentium 1.400Mhz, 256 M memory, 140 G hard disc
  • scope : Delphi 1 to 8 for Windows, Kylix
  • level : Delphi developer
  • plan :


1 - Introduction

As you might have noticed, we very often use tMemos in our applications, for debug or display purposes. It sometimes happens that in huge debug logs, we want to find some string patterns. The basic tMemo does not include a "find" function. This is why we created the c_find_memo.




2 - Organization

2.1 - The search function

The control will be a tMemo descendent (or container) with the added search functionality.

To search a pattern in a string, there are many possibilities:

  • the Pos function
  • search algorithm (Boyer More Horspool etc)
  • direct search with Pascal test and loops
The Pos function only finds the first occurence. To find the next, we must build a string with the part remaining after the current occurence, which is somehow a waste of time (and complicated for backward searches)

The Boyer More Horspool is quite nice, and we presented an implementation for disc "find in file" utility (see the the coliget file expression search engine  ), but is more heavy to implement. For a simple tMemo search, we considered it overkill.

So we implemented a very simple algorithm:

  • loop until the first matching character is found
  • if this is the case, check the end of the search string


2.2 - tMemo functions

The tMemo function follows the classical Delphi encapsulation pattern:
  • Windows can create Edit Controls
  • Delphi encapsulates this Edit in a CLASS.
In addition to the display attributes, Left, Top, etc, Delphi added the Lines and Text PROPERTIEs.

The Text PROPERTY allows to get or set the text. Like the Caption PROPERTY, it simple reads or write the string that Windows saves along with the position parameters, the handle etc.

The Lines PROPERTY is computed upon loading (in assembler).

However, the tMemo lacks some methods which could be useful:

  • find the line (index or string) of the Caret
  • scroll to a specified line
To find the index of a line, we simply added a function which compares the running text index with SelStart.

The scrolling is done using Windows API calls (or sending Windows messages):

  • em_GetFirstVisibleLine tells us the index of the current top line
  • em_LineScroll performs the scrolling


2.3 - The control creation

As already presented in the 3d Graphic Designer paper, we will not build a component, but only a CLASS whith automatic control creation. In our case the c_find_memo CLASSe's CONSTRUCTOR uses a tPanel reference, and nests inside this tPanel:
  • the find tEdit
  • the find next tButtons
  • the tMemo
This automatic creation avoids the tedious clicks to place those controls when a find memo is used in a more complex project. We simply create the c_find_memo, and the creation is performed.

Sure, this is not as nice as having a full fledged c_find_memo component on the Palette. But on the other hand, using a CLASS with self creating possibilities (but no REGISTER procedure) avoids the installation on the Palette.




3 - The Delphi Source code

4 - The c_find_memo UNIT

Here is the definition:

 c_find_memoclass(c_basic_object)
                m_c_parent_component_reftWinControl;
                m_save_pathString;

                // -- optional to avoid erasing the selected part
                // -- (could also use a ReadOnly memo)
                m_do_hilite_resultBoolean;

                m_c_paneltPanel;
                  m_c_text_length_labelm_c_text_line_count_labeltLabel;
                  m_c_find_edittEdit;
                  m_c_find_buttonm_c_next_buttonm_c_sort_buttontButton;
                  m_c_save_edittEdit;
                m_c_memotMemo;

                m_find_stringString;
                m_find_lengthInteger;

                m_textString;
                m_text_indexInteger;
                m_text_lengthInteger;

                m_did_hit_controlBoolean;

                Constructor create_find_memo(p_nameString;
                    p_c_parent_component_reftWinControl;
                    p_save_pathString);

                procedure set_panel_color(p_colorInteger);
                function f_memo_line_of_index(p_text_indexInteger): Integer;
                procedure set_memo_top_line_index(p_top_line_indexInteger);
                procedure find_next_occurence;

                procedure handle_find_edit_keypress(p_c_senderTObject;
                    var pv_keyChar);
                procedure handle_memo_and_find_edit_keydown(p_c_senderTObject;
                    var pv_scan_codeWordp_shift_stateTShiftState);
                procedure handle_next_click(p_c_sendertObject);
                procedure handle_sort_click(p_c_sendertObject);
                procedure handle_memo_change(p_c_sendertObject);
                procedure handle_save_edit_keypress(p_c_senderTObject;
                    var pv_keyChar);

                Destructor DestroyOverride;
              end// c_find_memo

And:

  • m_c_parent_component_ref: this is the Control which will contain our tMemo
  • m_save_path: the default save path which will be used when saving the text
  • m_do_hilite_result: the user can cancel selection highlighting, since there is a risk of accidentally removing the highlighted found occurence when hitting the keyboard

  • m_c_panel, m_c_text_length_label, m_c_text_line_count_label, m_c_find_edit, m_c_find_button, m_c_next_button, m_c_sort_button, m_c_save_edit: these controls are automatically created when calling the CONSTRUCTOR

  • m_find_string, m_find_length, m_text, m_text_index, m_text_length: the search attributes
  • m_did_hit_control: used to manage Ctrl-L keypresses


The constructor builds the controls:

Constructor c_find_memo.create_find_memo(p_nameString;
    p_c_parent_component_reftWinControl;
    p_save_pathString);

  procedure create_panel;
    const k_button_height= 25;
    var l_xInteger;
    begin
      m_c_panel:= f_c_create_panel('panel''',
        m_c_parent_component_refm_c_parent_component_ref,
        alTop, 0, k_button_height+ 2, clBtnFace);

      l_x:= 15;

      m_c_text_length_label:= f_c_create_label('tlength''length',
          m_c_panelm_c_panelalNonel_x, 2+ 4, -1, -1); 
      Inc(l_xm_c_text_length_label.Width+ 5);

      m_c_text_line_count_label:= f_c_create_label('tline''lines  ',
          m_c_panelm_c_panelalNonel_x, 2+ 4, -1, -1);
      Inc(l_xm_c_text_line_count_label.Width+ 5);

      Inc(l_x, 10);
      m_c_find_edit:= f_c_create_edit('edit''find',
          m_c_panelm_c_panell_x, 2, 100, k_button_height);
      with m_c_find_edit do
      begin
        OnKeyPress:= handle_find_edit_keypress;
        OnKeyDown:= handle_memo_and_find_edit_keydown;

        Inc(l_xWidth+ 5);
      end// with m_c_find_edit

      m_c_next_button:= f_c_create_button('next''>',
          m_c_panelm_c_panell_x, 2, 20, k_button_height);
      Inc(l_xm_c_next_button.Width+ 5);
      m_c_next_button.OnClick:= handle_next_click;

      m_c_sort_button:= f_c_create_button('sort''sort',
          m_c_panelm_c_panell_x, 2, 40, k_button_height);
      Inc(l_xm_c_sort_button.Width+ 5);
      m_c_sort_button.OnClick:= handle_sort_click;

      Inc(l_x, 10);
      m_c_save_edit:= f_c_create_edit('save''save.txt',
          m_c_panelm_c_panell_x, 2, 100, k_button_height);
      with m_c_save_edit do
      begin
        OnKeyPress:= handle_save_edit_keypress;

        Inc(l_xWidth+ 5);
      end// with m_c_save_edit
    end// create_panel

  begin // create_find_memo
    Inherited create_basic_object(p_name);

    m_c_parent_component_ref:= p_c_parent_component_ref;
    m_save_path:= p_save_path;

    create_panel;

    m_c_memo:= tMemo.Create(m_c_parent_component_ref);
    with m_c_memo do
    begin
      Parent:= m_c_parent_component_ref;
      Align:= alClient;
      ScrollBars:= ssVertical;

      OnKeyDown:= handle_memo_and_find_edit_keydown;
      OnChange:= handle_memo_change;
    end// with m_c_memo
  end// create_find_memo

where the f_c_create_panel, f_c_create_edit etc are very simple function returning a tPanel, tEdit etc.



This is our very simple search routine:

procedure c_find_memo.find_next_occurence;

  function f_find_rest(p_indexInteger): Boolean;
    var l_word_indexInteger;
    begin
      l_word_index:= 2;
      while (l_word_index<= m_find_length)
          and (p_indexl_word_index<= m_text_length)
          and (m_find_string[l_word_index]= m_text[p_indexl_word_index- 1]) do
        inc(l_word_index);

      Result:= l_word_indexLength(m_find_string);
    end// f_find_rest

  var l_first_characterChar;

  begin // find_next_occurence
    with m_c_memo do
    begin
      Inc(m_text_indexm_find_length);

      l_first_character:= m_find_string[1];

      while m_text_indexm_text_length do
      begin
        if m_text[m_text_index]= l_first_character
          then
            if f_find_rest(m_text_index)
              then begin
                  set_memo_top_line_index(f_memo_line_of_index(m_text_index));
                  SelStart:= m_text_index- 1;
                  if m_do_hilite_result
                    then SelLength:= m_find_length;
                  set_panel_color(clLime);
                  exit;
                end;

        inc(m_text_index);
      end// while pv_index
    end// with m_c_memo do

    set_panel_color(clRed);
  end// find_next_occurence



And to position the Caret on the found occurence and scroll the Memo:

function c_find_memo.f_memo_line_of_index(p_text_indexInteger): Integer;
  var l_line_indexInteger;
      l_running_text_indexInteger;
  begin
    Result:= 0;
    l_running_text_index:= 0;
    with m_c_memo do
      for l_line_index:= 0 to Lines.Count- 1 do
        if l_running_text_indexp_text_index
          then begin
              Result:= l_line_index- 1;
              Break;
            end
          else Inc(l_running_text_indexLength(Lines[l_line_index])+ 2);
  end// f_memo_line_of_index

procedure c_find_memo.set_memo_top_line_index(p_top_line_indexInteger);
  var l_first_visible_lineInteger;
  begin
    with m_c_memo do
    begin
      l_first_visible_line:= Perform(em_GetFirstVisibleLine, 0, 0);
      if l_first_visible_line<> p_top_line_index
        then Perform(em_LineScroll, 0, p_top_line_indexl_first_visible_line);
    end;
  end// set_memo_top_line_index



4.1 - Snapshot

Here is the snapshot of the tForm after creation (we loaded the main form text):

the main form



And this is the result after hitting "Felix Enter":

find next function



4.2 - Mini HowTo

The main form created the control with:

procedure TForm1.create_Click(SenderTObject);
  begin
    g_c_find_memo:= c_find_memo.create_find_memo('find'Panel3,
        f_exe_path'log\');
    with g_c_find_memo do
    begin
      m_c_memo.Lines.LoadFromFile('..\p_find_memo\u_find_memo.pas');
      m_do_hilite_result:= True;
    end// with g_c_find_memo
  end// create_Click

The search of the first occurence is done

  • by hitting Control-F when focus is in the tMemo, or selecting the find_edit
  • by entering the searched string, and hitting Enter
Next searches are done
  • by hitting Control-L, or the ">" next button
To save the file (in the path specified during creation) is done by:
  • entering the file name in the save edit
  • hitting Enter



5 - Improvements

We could add the following features:
  • replace the Pascal search loops with more efficient algorithms (Boyer Moore Horspool)
  • add the other find capabilities (backward, first, last, case sensitive, words only)
  • add the replace function
  • add a tSaveDialog for the saving
  • promote the CLASS to component status (add the PROPERTIes, the PUBLISHED visibility definitions and the REGISTER call)



6 - Download the Sources

Here are the source code files:

Those .ZIP files contain:
  • the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
  • any .TXT for parameters
  • all units (.PAS) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • 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.



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.



7 - Conclusion

The c_find_memo unit allows to place on a tForm a tMemo with find and save functions




8 - 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: nov-04. Last updated: dec-2019 - 103 articles, 239 .ZIP sources, 1292 figures
Contact : Felix COLIBRI - Phone: (33)1.42.83.69.36 / 06.87.88.23.91 - email:fcolibri@felix-colibri.com
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2019. 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
    + rest_services
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
      – find_memo
    + colibri_utilities
    + colibri_helpers
    + delphi
    + IDE
    + firemonkey
    + compilers
    + vcl
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog