0

I'm creating a search features in an existing c++ CodeGear project.

When you double-click a word, the background of all occurrences of the word is paint in green like in notepad++.

Before color is applied, I'm saving the original TRichEDit text in a TMemoryStream to be able to get the original text back. I reset the color back to normal on click event in the TRichEdit.

I would like to know if there is a way to save each occurrence of the search word in a TMemoryStream or maybe by using a Message like EM_STREAMOUT?

Right now everything works fine, but when the TRichEdit text is too big, it takes forever to reload the big memo of all the text. I think it would be better to only remember the color of the word that changed rather then reload all the text.

I'm really a beginner in programming, any help is appreciate. Tell me if it's not clear enough.

Here is my code that is working and putting background color to occurrences of word: ` void SearchInText::searchWordInText(TRichEdit* reTextToSearch, AnsiString strWordToFind) { lstOccurrences->Clear(); //reset lst

strTextToParse = AnsiReplaceText(strTextToParse, "\r\n", "\n");
int nPrevTagPos = 0;

int nTagPos = strTextToParse.AnsiPos(strWordToFind);

while (nTagPos != 0)
{
    int nPosMin = nPrevTagPos + nTagPos - 1;

    //List of all the occurrence in the TRichEdit with their position in the text
    //It's not a list of int, but it POINT to adresses of INT so it's the same result =)
    lstOccurrences->Add((TObject*) nPosMin);

    //Change color of background when there is an occurrence
    changeBgColor(reTextToSearch, strWordToFind, nPosMin +1, 155, 255, 155); //lime
    bColorWasApplied = true;

    nPrevTagPos = nPosMin + strWordToFind.Length();
    strTextToParse = strTextToParse.SubString(nTagPos + strWordToFind.Length(), strTextToParse.Length());
    nTagPos = strTextToParse.AnsiPos(strWordToFind);
}

} `

pikarie
  • 183
  • 1
  • 12
  • 1
    You can create your own structure with `Start` and `Length` members, and store them in a list or array. Save the `RichEdit->SelStart` and `->SelLength` for each word you highlight. When you need to restore them, loop through the list, change the RichEdit->SelStart and ->SelLength to the stored value and reset the color of that selection to the original color. (You could even save the original color as part of that structure if needed.) – Ken White Nov 03 '14 at 21:54
  • @KenWhite: that should be posted as an answer rather than a comment. – Remy Lebeau Nov 03 '14 at 23:37
  • @Remy: I don't have a machine with Builder installed here, and it's far from my area of expertise. :-) – Ken White Nov 04 '14 at 00:26
  • @KenWhite: The problem with SelStart is that it doesn't keep the RTF of the text in my `TRichEdit`. I already have a "list" in my .h that is `TObjectList* lstOccurrences;`. I use it to keep track of at which occurrence we are so we can go to the next one in the text. It point to an int of the position of where the word begin in the text. It's also with it that I put the background color with EM_EXSETSEL. – pikarie Nov 04 '14 at 15:23

2 Answers2

1

Try something like this:

#include <vector>

struct WordOccurrence
{
    CHARRANGE Range;
    CHARFORMAT2 OriginalFormat;
};

std::vector<WordOccurrence> WordOccurrences;

void TMyForm::HighlightWords(const String &WordToFind)
{
    // disable the RichEdit's notification messages
    int OriginalEventMask = RichEdit1->Perform(EM_SETEVENTMASK, 0, 0);

    // disable the RichEdit's painting
    RichEdit1->Perform(WM_SETREDRAW, FALSE, 0);

    // save the RichEdit's current selection
    CHARRANGE OriginalSelection;
    RichEdit1->Perform(EM_EXGETSEL, 0, (LPARAM)&OriginalSelection);

    // assign values to use while searching
    int WordLen = WordToFind.Length();
    int TextLen = RichEdit1->GetTextLen();
    TSearchTypes SearchTypes = TSearchTypes() << stWholeWord << stMatchCase;

    // find the first occurrence of the word
    int StartPos = RichEdit1->FindText(WordToFind, 0, TextLen, SearchTypes);
    while (StartPos != -1)
    {
        WordOccurrence Occurrence;
        Occurrence.Range.cpMin = StartPos;
        Occurrence.Range.cpMax = StartPos + WordLen;

        // select the word
        RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&Occurrence.Range);

        // get the word's current formatting
        Occurrence.OriginalFormat.cbSize = sizeof(CHARFORMAT2);
        RichEdit1->Perform(EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&Occurrence.OriginalFormat);

        // save it for later
        WordOccurrences.push_back(Occurrence);

        // set the word's new formatting
        CHARFORMAT2 NewFormat = Occurrence.OriginalFormat;
        NewFormat.dwMask |= (CFM_COLOR | CFM_BACKCOLOR);
        NewFormat.crTextColor = ...;
        newFormat.crBackColor = ...;
        RichEdit1->Perform(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&NewFormat);

        // find the next occurrence of the word
        StartPos = RichEdit1->FindText(WordToFind, Occurrence.Range.cpMax, TextLen - Occurence.Range.cpMax, SearchTypes);
    }

    // restore the RichEdit's original selection
    RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&OriginalSelection);

    // re-enable the RichEdit's painting
    RichEdit1->Perform(WM_SETREDRAW, TRUE, 0);
    RichEdit1->Invalidate();

    // re-enable the RichEdit's notification messages
    RichEdit1->Perform(EM_SETEVENTMASK, 0, OriginalEventMask);
}

void TMyForm::RestoreHighlightedWords()
{
    // are there any occurrences to restore?
    if (WordOccurances.empty())
        return;

    // disable the RichEdit's notification messages
    int OriginalEventMask = RichEdit1->Perform(EM_SETEVENTMASK, 0, 0);

    // disable the RichEdit's painting
    RichEdit1->Perform(WM_SETREDRAW, FALSE, 0);

    // save the RichEdit's current selection
    CHARRANGE OriginalSelection;
    RichEdit1->Perform(EM_EXGETSEL, 0, (LPARAM)&OriginalSelection);

    // restore the formatting of each occurrence
    for (std::vector<WordOccurrence>::iterator iter = WordOccurrences.begin();
        iter != WordOccurrences.end();
        ++iter)
    {
        WordOccurrence &occurrence = *iter;

        // select the word
        RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&occurrence.Range);

        // restore the word's original formatting
        RichEdit1->Perform(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&occurrence.OriginalFormat);
    }

    // clear the list
    WordOccurances.clear();

    // restore the RichEdit's original selection
    RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&OriginalSelection);

    // re-enable the RichEdit's painting
    RichEdit1->Perform(WM_SETREDRAW, TRUE, 0);
    RichEdit1->Invalidate();

    // re-enable the RichEdit's notification messages
    RichEdit1->Perform(EM_SETEVENTMASK, 0, OriginalEventMask);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for the suggestion. In my situation, I don't want to restore the highlighted words manually by setting the `crTextColor` and the `crBackColor` to something specific. I want to restore the original formatting to whatever it was before the highlight of the search feature. (I'm talking about your `RestoreHighlightedWords()` void.) The first void `HighlightWords` seems great. There is the just `TSearchTypes SearchTypes = TSearchTypes() << stWholeWord << stMatchCase` that I'm not sure to understand. – pikarie Nov 04 '14 at 14:49
  • +1. I knew you could do this much better than I would have. :-) – Ken White Nov 04 '14 at 15:59
  • Like Ken said in his earlier comment, you can save the original formatting and then restore it when needed. While highlighting words, after using `EM_EXSETSEL` to select a specific word, use `EM_GETCHARFORMAT` to retrieve its current formatting before then using `EM_SETCHARFORMAT` to change it. Store the formatting in the list, then you can use `EM_SETCHARFORMAT` later to restore it. – Remy Lebeau Nov 04 '14 at 19:52
  • As for the `SearchTypes` variable, it is simply assigning flags that `TRichEdit::FindText()` takes as input to influence how it finds matching text. `stWholeWord` and `stMatchCase` can be specified together, individually, or not at all. It depends on your particular needs. For highlighting matching text, specifying them together makes sense. – Remy Lebeau Nov 04 '14 at 20:20
0

Ok, so I finally get it! I add a struct in my .h

Inside it, I store:
-the position in the TRichEdit of the word found (nStart)
-the length of the word (nLength)
-the actual text and his RTF

struct sSelectedWord : public TObject
{
    public:
        __fastcall  ~sSelectedWord(); //destructor
        int nStart;
        int nLength;
        TMemoryStream* memoRTF;
};

Here is the code that save the RTF of my TRichEdit in my struct I just created in the .h.

void SearchInText::searchWordInText(TRichEdit* reTextToSearch, AnsiString strWordToFind)

{

lstOccurrences->Clear(); //reset lst
lstOccWithRTFMemo->Clear();
nCountOccurrence = 0;

strTextToParse = AnsiReplaceText(strTextToParse, "\r\n", "\n");
int nPrevTagPos = 0;

int nTagPos = strTextToParse.AnsiPos(strWordToFind);

while (nTagPos != 0)
{
    int nPosMin = nPrevTagPos + nTagPos - 1;

    //List of all the occurrence in the TRichEdit with their position in the text
    //It's not a list of int, but it POINT to adresses of INT so it's the same result =)
    lstOccurrences->Add((TObject*) nPosMin);
    nCountOccurrence++;

    //selected the word in the TRichEdit to save it with is RTF
    reTextToSearch->SelStart = nPosMin;
    reTextToSearch->SelLength = strWordToFind.Length();

    TMemoryStream* memo = new TMemoryStream;
            //important part! 
    rtfSaveStream(reTextToSearch,memo);

    sSelectedWord* currentWord = new sSelectedWord;
    currentWord->nStart = nPosMin;
    currentWord->nLength = strWordToFind.Length();
    currentWord->memoRTF = memo;

            //Here we go, we add our new object in a list to be able to loop through it when we will want to reset the color
    lstOccWithRTFMemo->Add(currentWord);
    lstOccWithRTFMemo->Count;

    //Change color of background when there is an occurrence
    changeBgColor(reTextToSearch, strWordToFind, nPosMin +1, 155, 255, 155); //lime
    bColorWasApplied = true;

    nPrevTagPos = nPosMin + strWordToFind.Length();
    strTextToParse = strTextToParse.SubString(nTagPos + strWordToFind.Length(), strTextToParse.Length());
    nTagPos = strTextToParse.AnsiPos(strWordToFind);


}

}

The important part was done with the EM_STREAMOUT message.

void SearchInText::rtfSaveStream(TRichEdit* re, TMemoryStream* memo)
{
    // Create an instance of an EDITSTREAM that will contain:
    // - The detail of our callback (StreamInCallback)
    // - The TMemoryStream that contains the text to paste
    EDITSTREAM es = {0};
    ZeroMemory(&es, sizeof(es));
    es.dwCookie = (DWORD_PTR) memo;
    es.dwError = 0;
    es.pfnCallback = &StreamSaveInCallback ; //pointer to function callBack

    //To save the selected word of the TRichEdit, use STREAMOUT
    re->Perform(EM_STREAMOUT, SF_RTF | SFF_SELECTION, (LPARAM)&es);
}
DWORD CALLBACK SearchInText::StreamSaveInCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
    TMemoryStream *memo = (TMemoryStream*)dwCookie;

    memo->Write(pbBuff, cb);
    *pcb = cb;

    return 0;
}

In the pbuff, you can see the word that you selected in your TRichEdit with his RTF!
Hope it will help others with the same issue! Thanks for those who suggested code =)

pikarie
  • 183
  • 1
  • 12