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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.