Windows NT Systems Programming: Spring 2000
[ Home | Syllabus | Course Notes | Assignments | Search]
A Simple Text Editor in Document View
The objectives of this example are
- Build simple document view application to illustrates its architecture
- Show updating menu entries as state of program changes
- Show Keyboard accelerators
- Demonstrate Property List Dialogs
- Show power of built in MFC classes
You can follow the directions in the book to generate this example (it would be
good practice in learning visual c++) or you can download the finished application here.
Illustrating the Document/View Architecture
- In this example, the document is very simple. It consists merely of a single
string.
(and as such cannot store all the formatting information possible with RichEditView)
- This string is stored in ex13aDoc.h and is named "m_strText" (you can
edit the header file directly or use the Class View to insert a new data member by right
clicking on the class name).
- The view consists of a single CRichEditCtrl object called "m_rich". This
is a fancy version of CEdit.
- Information flows between the document and view.
- After the user edits the text, it can be stored in the document as follows
CEx13aDoc* pDoc = GetDocument( ); // get a pointer to the document object
// get data from the CRichEditCtrl object and pass it to the Document
m_rich.GetWindowText(pDoc->m_strText);
m_rich.SetModify(FALSE); // Set flag that document is up to date
- You can also move the data from the document and draw it in the view as follows
CEx13aDoc* pDoc = GetDocument();
m_rich.SetWindowText(pDoc->m_strText);
m_rich.SetModify(FALSE);
- The movement of the data between the view and document is controlled by two menu
items in this application.
In a "real" application the movement would probably be under program control.
- When the program starts up, or when the user selects the "File/New" menu
item, a "OnNewDocument" message is sent to the Document object. In this example
the document is initialized as follows.
BOOL CEx13aDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_strText = "Hello (from CEx13aDoc::OnNewDocument)";
return TRUE;
}
- The "OnEditClear" message handler sets the string
to empty as follows
void CEx13aDoc::OnEditClearDocument()
{
m_strText.Empty( ) ;
}
Menus/Updating Menu Entries
- Menu are usually created using the menu resource editor (see directions in book)
- Each menu item is associated with an ID which is also the message ID sent when the
user selects this menu item.
- When using the app wizard, the usual menu items are already available (and mapped
into message handlers in the base classes according to the document view architecture).
- The state of the menu items can be set using "UPDATE_COMMAND_UI" message
which are sent by the CMenu object just before it draws a menu item. If you don't have a
handler, then the item is drawn as normal, otherwise the handler can decide how the menu
item is displayed. This allows the menu to change as the state of the program changes. For
instance, "Clear Document", Transfer Store Data", and "Format
Selection" are controlled depending on the state of the program.
- Consider as an example the "Clear Document" menu item, its update handler
is
void CEx13aDoc::OnUpdateEditClearDocument(CCmdUI* pCmdUI)
{
pCmdUI->Enable(!m_strText.IsEmpty());
}
This handler is part of the document because it depends on
the state of "m_strText" which is part of the document.
This menu is enabled whenever the document string is NOT empty.
When the document is empty, there is no need to clear it.
- Now consider the "Transfer Store Data" menu item,
its update handler is
void CEx13aView::OnUpdateTransferStoreData(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_rich.GetModify());
}
This handler is stored in the view, since it depends on the
state of the CRichEditCtrl object which is part of the view object.
"GetModify" is true if the user has edited the view since the last time the view
text was set and the "SetModify(FALSE)" member function of CRichEditCtrl was
called (See OnTransferStoreData above)
There is no need to Store the view if it has not been modified since the last time it was
stored.
- Now consider "Format Selection", this allows the
user to format any text which has been selected in some fashion, if no text is selected,
then this menu item should be disabled.
void CEx13aView::OnUpdateFormatSelection(CCmdUI* pCmdUI)
{
long nStart, nEnd;
m_rich.GetSel(nStart, nEnd);
pCmdUI->Enable(nStart != nEnd);
}
"GetSel" returns the starting and ending positions
of the text that is currently selected, If they are the same, then no text is currently
selected.
Keyboard Accelerators
- Some of the menu items have captions with a '\t' (tab) character followed by the
name of a keyboard accelerator which does the same thing as the menu item (common keyboard
accelerators are "Ctrl+c" to "Copy", "Ctrl-p" to
"Print".
However, putting the keyboard acceleration in the menu item caption does not make it
so
- Keyboard accelerators are added by a separate resource editor. (see book (p. 297)
for more details)
CRichEdit
- CRichEdit supports multiple fonts types, effects, colors and sizes.
- To set different properties of the font, we will set up four dialog boxes stored as
tabs in a property sheet. These dialog boxes are referred to as property pages.
Property Sheets
- Property sheet is a set of property pages (dialog boxes) organized in one place and
accessed by tabs
- Use the dialog editor to define each property page as described in the book (we
will discuss some interesting points later)
- Associate each dialog box with a MFC class derived from CPropertyPage (instead of
CDialog as in example EX5a)
- Derive a new class from CPropertySheet and add a data member for each Property Page
you want.
- Add views of the property pages in the property sheet's constructor using the
"AddPage" member function
- Write an "OnApply" handler to effect the change the user selected.
- Let's go through some of the property pages.
Font Type
The Font Type dialog box looks like
- This dialog consists of three "radio" buttons - so called because like
old time radios, selecting one button deselects the rest, Thus only one button can be
selected at a time. This is appropriate for selecting font types, since only one type can
be used for a particular section of text.
- How does the program know which radio buttons belong together? by grouping (the
first and only the first button has the "group" attribute selected and the next
control not in the group has its "group" attribute selected - marking the
beginning of a new group). Also the tab order of the controls defines which is the first
control in a group and which is the first in the next group. For more details see ex06a.
- Like regular dialog boxes, you can set/get data from the controls in the dialog
using DoDataExchange functions provided by the wizard. For radio buttons, each button is
numbered according to its position in the tabbing sequence (see page 114-5 in book)
starting from the button with the "group" attribute. This number uniquely
identifies the radio button. In this example, we have asked the class wizard to remember
the font type button selected in a varaible called "m_nFont". The Data Exchange
function call is
//{{AFX_DATA_MAP(CPage1)
DDX_Radio(pDX, IDC_FONT, m_nFont);
//}}AFX_DATA_MAP
Notice that the name of radio button group is the ID of the first radio button and
the same DDX is called regardless which radio button is pushed
- Here is the sequence of operations and messages. When the user selects a radio
button a WM_NOTIFY message is sent. The command handler for this messages sets the
modified flag in the following message handler.
BOOL CPage1::OnCommand(WPARAM wParam, LPARAM lParam)
{
SetModified(TRUE); // used when switching pages
return CPropertyPage::OnCommand(wParam, lParam);
}
- When the user switches between pages, the DDX function for the old page is called
to save its values.
(If the user returns to this pages, the saved values are exchanged from the object
back into the controls using the same DDX function). Nothing happens to the underlying
view yet.
Font Color
This is also a radio button dialog and is similar to font type above
Font Effects
- Sets one or more font effects from the choices (bold, italic, underline).
- Since more than one effect can be chosen at the same time, this dialog uses
"Check box" controls which are naturally associated with a boolean variable
(checked or not checked). Again the wizard can map these to boolean variables in the
derived PropertyPage object and provide DDX functions for them. as shown here
//{{AFX_DATA_MAP(CPage2)
DDX_Check(pDX, IDC_BOLD, m_bBold);
DDX_Check(pDX, IDC_ITALIC, m_bItalic);
DDX_Check(pDX, IDC_UNDERLINE, m_bUnderline);
//}}AFX_DATA_MAP
- Also handles the WM_NOTIFY message and sets the modified flag
so that the DDX functions are called if you switch pages
Font Size
Changing the view
- To change the view, you need to write code for the OnApply message, shown here.
BOOL CPage1::OnApply()
{
g_pView->SendMessage(WM_USERAPPLY);
return TRUE;
}
(Question: where does g_pView come from and how is it set?)
- Notice, that all we do is to send a message to the view object. Here is the code
that handles that message
LRESULT CEx13aView::OnUserApply(WPARAM wParam, LPARAM lParam)
{
TRACE("CEx13aView::OnUserApply -- wParam = %x\n", wParam);
CHARFORMAT cf;
Format(cf);
if (m_bDefault) { // check whether setting default or selection formatting
m_rich.SetDefaultCharFormat(cf);
}
else {
m_rich.SetSelectionCharFormat(cf);
}
return 0;
}
- Trace is used in debugging mode
- Format (shown next) does all the work of setting the font properties
- This member function sets default text properties
- This one sets only for the current selection.
- Here is code for Format
void CEx13aView::Format(CHARFORMAT &cf)
{
cf.cbSize = sizeof(CHARFORMAT); //CHARFORMAT can be variant sizes
cf.dwMask = CFM_BOLD | CFM_COLOR | CFM_FACE |
CFM_ITALIC | CFM_SIZE | CFM_UNDERLINE;
cf.dwEffects = (m_sh.m_page2.m_bBold ? CFE_BOLD : 0) |
(m_sh.m_page2.m_bItalic ? CFE_ITALIC : 0) |
(m_sh.m_page2.m_bUnderline ? CFE_UNDERLINE : 0);
cf.yHeight = m_sh.m_page4.m_nFontSize * 20;
switch(m_sh.m_page3.m_nColor) {
case -1:
case 0:
cf.crTextColor = RGB(0, 0, 0); // black
break;
case 1:
cf.crTextColor = RGB(255, 0, 0);
break;
case 2:
cf.crTextColor = RGB(0, 255, 0);
break;
}
switch(m_sh.m_page1.m_nFont) {
case -1:
case 0:
strcpy(cf.szFaceName, "Times New Roman");
break;
case 1:
strcpy(cf.szFaceName, "Arial");
break;
case 2:
strcpy(cf.szFaceName, "Courier New");
break;
}
cf.bCharSet = 0;
cf.bPitchAndFamily = 0;
}
- Can be two different sizes
- These are the attributes that can be set
Some Experiments on this example
Copyright chris wild 1999/2000.
For problems or questions regarding this web contact [Dr.
Wild].
Last updated: February 02, 2000.