📄 4app.html
字号:
<h3>Windows Controls</h3>
<p>A Windows control is a window whose class and procedure--the major elements of behavior--are implemented by the system. Controls vary from extremely simple, like static text, to very complex, like ListView or TreeView. They are ubiquitous in dialog boxes (about which we'll talk soon), but they can also be created as stand-alone child windows. The application communicates with a control by sending it messages. Conversely, a control communicates with its parent by sending messages to it. In addition to common messages, each type of control has its own distinct set of messages it understands.
<p>I defined a class <var>Win::SimpleCtrl</var> that combines the attributes common to all controls. Since a control is a window, <var>Win::SimpleCtrl</var> inherits from <var>Win::Dow</var>. Like all child windows, controls can be assigned numerical ids, so that their parent can distinguish between them (although it may also tell them apart by their window handles). If a control is created standalone, using an appropriate maker, we can initialize the corresponding <var>SimpleCtrl</var> object directly with its window handle. Otherwise, we initialize it with the parent's window handle and the child id (this pair is immediately translated to the window handle using the <var>GetDlgItem</var> API).
<p>The simplest control is called <i>static text</i>. It displays text in a simple frame. The corresponding object, <var>Win::StaticText</var>, is the simplest descendant of <var>Win::SimpleCtrl</var>. The program can modify the text displayed in it by calling the <var>SetText</var> method that <var>Win::StaticText</var> inherits from <var>Win::Dow</var>.
<p>A little more interesting is the <i>edit control</i>. Its read-only version behaves just like static text, except that you can copy text from it into the clipboard (by selecting it and pressing Ctrl-C). The full-blown edit control can be used not only to display text, but also to read user input. It also supports several editing functions, like delete, copy, cut, paste, etc. In fact, a multi-line edit control is the engine behind the Windows own Notepad.
<p>The most important thing that an application may want to get from an edit control is the text that's been input there by the user. The class <var>Win::Edit</var>, another descendant of <var>Win::SimpleCtrl</var>, provides two different ways of doing that. The low-level method <var>GetText</var> takes a buffer and a count (you can use <var>GetLen</var> method to enquire about the length first). The high-level method <var>GetText</var> takes no arguments and returns a <var>std::string</var>.
<p>Controls keep sending messages to their parent windows. The interesting ones are <var>WM_COMMAND</var> and, for the newer generation of controls, <var>WM_NOTIFY</var>. It so happens that, for some historical reason, <var>WM_COMMAND</var> is not only used by controls, but also by menus and accellerators. Our generic window procedure sorts them out and calls, respectively, <var>OnControl</var> or <var>OnCommand</var>. <var>OnControl</var> is passed the control's window handle, its numerical id and the id of the command. For instance, every time the user changes the text in an edit control, a command message is set to the parent, with the command id <var>EN_CHANGE</var> (applications usually ignore this message).
<p>The main question with edit controls is when it's a good time to retrieve their text. The user must have some way of telling the program that the input is ready--in our case, that the whole expression has been entered. We could have added a special button "Evaluate" for the user to click on. But that's not what the user expects. He or she will most likely press the <i>Enter</i> key on the keyboard and expect the program to take it as a cue that the input is ready. Surprisingly, this very basic feature is not easy to implement. There is no simple way to tell the edit control to notify the parent when the Enter key is pressed.
<p>So what are we to do? We'll have to use the back-door approach called <i>window subclassing</i>. We have to write our own window procedure and plug it into the edit control. Fortunately, our window procedure doesn't have to implement all the editing functionality from scratch. All it has to do is to intercept the few messages we're interested in and pass the rest to the original, Windows-defined procedure.
<p>I have conveniently encapsulated window subclassing in two <var>Win::Dow</var> methods, <var>SubClass</var> and <var>UnSubClass</var>. The <var>SubClass</var> method takes a pointer to the <var>SubController</var> object. It substitutes a generic <var>SubProcedure</var> in place of the old window procedure. For every message, the <var>SubProcedure</var> first calls the <var>SubController</var> to process it. If the <var>SubController</var> doesn't process it (i.e., returns <var>false</var>), it calls the old window procedure. Notice how the procedures are chained together. Each of them either processes a message or calls the next, until the last one calls the default window procedure.
<p>The subclassing of the edit control proceeds as follows. We define a subclass of <var>Win::SubController</var>, and call it <var>EditController</var>. It overrides only one method, <var>OnKeyDown</var>, and processes it only if the key code corresponds to the Enter key (<var>VK_RETURN</var>). On intercepting the Enter key, it sends a message to the parent window. I decided to use a standard control message, <var>WM_CONTROL</var>, which could be processed by the <var>OnControl</var> method of the parent window. I had to pack the wParam and lParam arguments to mimic the control messages sent by Windows. As the control id I chose the pre-defined constant <var>IDOK</var> (thus immitating the pressing of the OK button in dialog boxes).
<p>To perform the subclassing, we call the <var>SubClass</var> method of the edit window, passing it the <var>EditController</var>. We have to override the <var>OnControl</var> method of our main controller to intercept the <var>IDOK</var> message and react appropriately. In fact that's where all the code from our old <var>main</var> went. We retrieve the string from the edit control, encapsulate it in a stream, create the scanner and the parser and evaluate the result. At this point we display the result and update the history and memory displays (in case the calculation changed memory).
<p>Displaying history is very simple. We just tell the ListBox to add a line to its window containing the string that was input by the user. ListBox will than take care of storing the string, displaying it, as well as scrolling and re-painting when necessary. We don't have to do anyting else. That's the power of Window controls.
<p>Displaying memory, on the other hand, is not so simple. That's because, in that case, the display has to reflect the state of a data structure that constantly changes. New variables are added and the values of old variables are modified as a result of user actions. Only the parser has any understanding of these actions. It decides whether an expression entered in the input window modifies the calculator's memory or not. Thus the display has to change in response to a change in the model.
<p>There are two approaches in dealing with the model-view feedback loop. The shotgun approach assumes that every user action that may change the state of the model requires the refreshing of the particular part of the view. In this scheme, display changes are controller-driven. The controller tells the view to refresh itself, and the view queries the model for the changes to be displayed. The model has no dependency on the view, as it shouldn't.
<p>The notification approach, on the other hand, assumes that the model will notify the view directly. A notification might simply tell the view to refresh itself, or it might provide the exact information about what and how should be changed. The problem with this scheme is that it introduces a circular dependency. The view depends on the model, because it has to know how to query for data to be displayed. The model depends on the view, because it has to know how to notify it about changes. Now, if you look back at our definition of the model, you'll find that it was supposed to be unaware of the details of the user interface. It seems like we'll have to abandon this nice subdivision of responsibilities and break the simple hierarchy of dependencies in favor of a more entangled (and complex) system.
<p>Don't despair yet! I'll show you how you can have a cake of hierarchies and eat it too with notifications. The trick is to give the model only a very minimal glimpse of the view. All the model needs is a "notification sink," and object that expresses interest in being notified about certain events. The model will have no clue about how these notifications are used. Furthermore, it will have no knowledge of the existence of the class <var>View</var>.
<p>A notification sink should be a separate class that will encapsulate only the notification interface. The model will have to know about this class, have access to an object of this class and call its methods. The view could simply inherit from the notification sink and implement its virtual methods. When the model sends a notification to its notification sink object, it really sends it to the view. But it still lives in a state of ignorance about the details of the UI.
<p>The absence of the dependency loop in this scheme is best illustrated by the hierarchy of includes. The header file that defines the notification sink is at the top of the hierarchy. It has to be included in <var>view.h</var>, because class <var>View</var> inherits from class <var>NotificationSink</var>. Some of the files that constitute the implementation of the model (the <var>Calculator</var> object) will also have to include it, because they call methods of <var>NotificationSink</var>. <var>View.cpp</var> will have to include <var>calc.h</var>, because it needs data from the model. The important thing is that <i>no model file</i> will have to include <var>view.h</var>. The graph of dependencies is a DAG--a directed acyclic graph--the sign of a good design.
<p>Now that you have an idea how to implement the optimal solution, you might be pleased to learn that in the implementation of our calculator I voted for the simpler, shotgun approach. The controller calls <var>View::RefreshMemory</var> every time a new expression is processed by the parser. As you might recall, this is done in response to the <var>IDOK</var> command sent by the edit control after detecting the enter key.
<p>In order not to be really crude (and not to cause too much screen flicker), I chose to update the memory display line-by-line, and modify only these entries which have changed since the last refresh. So in our port to Windows, the changes to the calculator not only require a new interface for memory listing, but also some mechanism to mark (and unmark) individual memory entries. This snippet of code shows how it works:
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>void View::UpdateMemory ()
{
int count = _memoryView.GetCount ();
for (int i = 0; i < count; ++i)
{
int id = _memoryView.GetData (i);
if (_calc.<b>HasValueChanged</b> (id))
{
_calc.<b>ResetChange</b> (id);
std::string varStr = FormatMemoryString (id);
_memoryView.ReplaceString (i, varStr);
_memoryView.SetData (i, id);
}
}
int iNew;
while ((iNew = _calc.<b>FindNewVariable</b> ()) != idNotFound)
{
_calc.<b>ResetChange</b> (iNew);
std::string varStr = FormatMemoryString (iNew);
int i = _memoryView.AddString (varStr);
_memoryView.SetData (i, iNew);
}
}</pre>
</td></tr></table><!-- End Code -->
<p>The member <var>_memoryView</var> represent the ListBox control that displays the contents of the calculator's memory.
<h3>Dialogs</h3>
<p>The other, and indeed more common, application of Window controls is in the construction of dialog boxes. A dialog box is a pre-fabricated window that provides a frame for various controls specified by the programmer. The type of controls and their positions are usually described in the resource script (the same where we described the icon). Here's an example of such a description:
<!-- Code --><table width=100% cellspacing=10><tr> <td class=codetable>
<pre>IDD_ABOUT DIALOG DISCARDABLE 0, 0, 142, 70
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
CAPTION "About Symbolic Calculator"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK",IDOK,46,49,50,14
CTEXT "Bartosz Milewski
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -