Introduction
This article is for MFC beginners and gives a general overview of
using CFindReplaceDialog.
The problem we want to solve is how to allow the user to find any
text string in their data. This article discusses the user interface,
and not the actual searching algorithm.
One example of a good way of doing a long search is shown in RegEdit.
It has a good UI for searches that may take a long time (cancel-search dialog).
Our example is designed for smaller data search. Note that when the time
taken is noticable but not too long you can always use CWaitCursor.
We will demonstrate a way comparable with Wordpad search. First what will
happen is a dialog will be displayed where user will enter a search
string. It is not necessary to create our own Wordpad-like dialog in the
resource editor because windows supports a standard one.
The API function FindText() creates a non-modal dialog which
can communicate with its parent. (If you interested you can find details in help
index under "common dialog boxes [Win32]/code examples/Finding text".)
We show how use CFindReplaceDialog which is an MFC class that
calls FindText internally. Our FindDialog parent will be a
tree-control (CMyTreeCtrl), but in general it can be any window type.
This example ends at the FindWhatYouNeed() function call level.
In your handler (eg. in CMyTreeCtrl::OnFind()) you will create
an instance of CFindReplaceDialog
ASSERT(m_pFindDialog == NULL);
m_pFindDialog = new CFindReplaceDialog();
m_pFindDialog->Create(TRUE, "Initial Text", NULL, FR_DOWN, this);
where m_pFindDialog is in my case class member of parent window CMyTreeCtrl
CFindReplaceDialog *m_pFindDialog;
This way it will display the find dialog in its default style.
If you want you can change the dwFlags parameter of Create().
eg. FR_DOWN | FR_WHOLEWORD will check whole-word when the dialog starts.
See "FINDREPLACE" help for all details.
When the dialog is opened the user can edit the text and change the checks (if
you allow them to be shown). It's time to react to his actions.
CFindReplaceDialog sends a message to its parent. The
ID of this message you can found by
UINT message = ::RegisterWindowMessage(FINDMSGSTRING);
Because ClassWizard knows nothing about this message we must add it
manually. To MyTreeCtrl.h (parent window) add OnFindDialogMessage handler
protected:
afx_msg void OnSomethingGeneratedByClassWizard();
afx_msg LRESULT OnFindDialogMessage(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
To MyTreeCtrl.cpp (parent window)
const UINT CMyTreeCtrl::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
BEGIN_MESSAGE_MAP(CMyTreeCtrl, CTreeCtrl)
ON_WM_SOMETHINGGENERATEDBYCLASSWIZARD()
ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
END_MESSAGE_MAP()
where m_pFindDialogMessage is defined in MyTreeCtrl.h as
static const UINT m_pFindDialogMessage;
Of course can be simple (not class member) variable:
static UINT FindDialogMessage = ::RegisterWindowMessage(FINDMSGSTRING);
BEGIN_MESSAGE_MAP(CMyTreeCtrl, CTreeCtrl)
ON_WM_SOMETHING()
ON_REGISTERED_MESSAGE(FindDialogMessage, OnFindDialogMessage)
END_MESSAGE_MAP()
As you can see I wrote my own entry out of AFX_MSG_MAP so
ClassWizard knows nothing about/has no problems with.
Now we have to write message handler:
LRESULT CMyTreeCtrl::OnFindDialogMessage(WPARAM wParam, LPARAM lParam)
{
ASSERT(m_pFindDialog != NULL);
if (m_pFindDialog->IsTerminating())
{
m_pFindDialog = NULL;
return 0;
}
if(m_pFindDialog->FindNext())
{
CString FindName = m_pFindDialog->GetFindString();
bool bMatchCase = m_pFindDialog->MatchCase() == TRUE;
bool bMatchWholeWord = m_pFindDialog->MatchWholeWord() == TRUE;
bool bSearchDown = m_pFindDialog->SearchDown() == TRUE;
FindWhatYouNeed(FindName, bMatchCase, bMatchWholeWord, bSearchDown);
}
return 0;
}
It is more user friendly when you remember data read from the dialog and use it
to modify parameters of next Create() call. You can add new class
members to parent window class. If you will not use m_pFindDialog as a class
member you can get it dynamicaly by
CFindReplaceDialog *pFindDialog = CFindReplaceDialog::GetNotifier(lparam);
When your tree control does not have the TVS_SHOWSELALWAYS flag
set after find dialog opening user will not see actual line selection. Because of this
we will set this flag before dialog's creation (CMyTreeCtrl::OnFind()):
if(GetWindowLong(GetSafeHwnd(), GWL_STYLE) & TVS_SHOWSELALWAYS)
{
m_bRemoveShowSelAlwaysAtFindDialogExit = false;
}
else
{
SetWindowLong(GetSafeHwnd(), GWL_STYLE,
GetWindowLong(GetSafeHwnd(), GWL_STYLE) | TVS_SHOWSELALWAYS);
m_bRemoveShowSelAlwaysAtFindDialogExit = true; }
and in dialog termination (CMyTreeCtrl::OnFindDialogMessage()):
if (m_bRemoveShowSelAlwaysAtFindDialogExit)
SetWindowLong(GetSafeHwnd(), GWL_STYLE,
GetWindowLong(GetSafeHwnd(),
GWL_STYLE) & (~((LONG)TVS_SHOWSELALWAYS)));
If you want to enable it so that when you open the find dialog
another window will have focus, then the find dialog or our parent/tree must switch
TVS_SHOWSELALWAYS "more dynamicaly".
Logic requires that we do not open more find dialogs. Because of this, replace
ASSERT(m_pFindDialog == NULL);
by better code (if/return) or follow next.
We will close the find dialog when it loses focus. This is not very user
friendly (eg. new mail notification will close the dialog) but it's good for
a short example.
To do this I derived own CMyFindReplaceDialog class from
CFindReplaceDialog. (Do not forget call the new
CMyFindReplaceDialog in CMyTreeCtrl::OnFind()).
Then I mapped the WM_ACTIVATE message in MyFindReplaceDialog.h
afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);
in MyFindReplaceDialog.cpp add the message map entry:
ON_WM_ACTIVATE()
and finally:
void CmyLayerDialog::OnActivate( UINT nState, CWnd* pWndOther, BOOL bMinimized )
{
CFindReplaceDialog::OnActivate(nState, pWndOther, bMinimized );
if(nState == WA_INACTIVE && IsTerminating() == false)
{
PostMessage(WM_COMMAND, IDABORT); }
}
The trick is not to call DestroyWindow(). The
CFindReplaceDialog uses the protected class member m_szFindWhat
in afxdlgs.h
TCHAR m_szFindWhat[128];
So do not exceed this size (or your text will be truncated).
In "FINDREPLACE" help you can find "The buffer should be at least 80
characters long." If you want (I do not know why) to replace this buffer by your
own do not forget this point.
CFindReplaceDialog *pFindDialog = new CFindReplaceDialog();
pFindDialog->m_fr.lpstrFindWhat = MyBuffer;
pFindDialog->m_fr.wFindWhatLen = _countof(MyBuffer);
pFindDialog->Create(TRUE, "Initial Text", NULL, FR_DOWN, this);
Do not forget MyBuffer must exist during whole find-dialog life so it can't
be a local (stack) variable. (Easiest way is to derive CMyFindReplaceDialog
from CFindReplaceDialog and create it as a class member.)
It is sometimes useful to make the find dialog "modal" (ie. make it impossible to
switch to the parent until the dialog ends). It's easy: If we open it from a control
in a dialog than add to the find-dialog Create() call:
GetParent()->EnableWindow(false);
and on IsTerminating()
GetParent()->EnableWindow(true);
(For safety you should take care of the case when Create() will
be not successful.) If you have tree-control in a property page you must disable the
whole sheet, not the actual page only:
GetParent()->GetParent()->EnableWindow(false);
and on IsTerminating()
GetParent()->GetParent()->EnableWindow(true);
This logic, with more if-s around, you can see in MFC
CDialog::DoModal() in dlgcore.cpp or CPropertySheet::DoModal()
in dlgprop.cpp.
Another, more complex example of using CFindReplaceDialog can be
found in the MFC CRichEditView class. See the
CRichEditView::OnEditFindReplace() function in the viewrich.cpp file.