출처 -
http://www.codeproject.com/cpp/avoidupdatedata.asp
Introduction
Microsoft does not adequately document the correct way to work with controls. It is left up to the programmer to somehow magically infer the correct way to do this. Unfortunately, all of the evidence suggests that UpdateData
is the correct way to handle the problem of accessing control information while working inside a dialog. This is not correct. In fact, it is dangerous to use it. It is simply the wrong way to go about it.
After I posted this essay, one reader, Doug Harrison, sent me a critique. I'm including his comments at the end, because some of them point out some potential flaws in my approach, and are valid points. I also disagree with a couple, and I'll tell you why, and you can judge for yourself what you want to do. But a Wave of the Flounder Fin to Doug, who took the time to send me a message.
You should never call UpdateData
in a modal dialog. Just Don't Do It. Ever. There are many reasons. One reason is that the OnOK
handler calls UpdateData
to store the control values in the associated member variables. The OnCancel
handler does not call UpdateData
. Therefore, the assumed behavior, or the should-be-assumed behavior, is that if you set a collection of member variables before calling DoModal
, upon successful completion of the dialog the member variables will hold the new control values, and upon error completion the member variables will have the same values that you put into them. This makes calling a modal dialog very simple:
CMyDialog dlg;
dlg.m_Count = somecounter;
dlg.m_Text = sometext;
dlg.m_Option = someBool;
dlg.DoModal( );
somecounter = dlg.m_Count;
sometext = dlg.m_Text;
someBool = dlg.m_Option;
Nothing to it! But if you ever call UpdateData
yourself, this simple paradigm won't work. That's because if the user clicks Cancel
, you've already messed over the values to represent some intermediate state that the user has just chosen to reject.
Doug Harrison sent me a critique which points out a serious flaw in the above sequence. It is a flaw I never knew about because I have never used a particular feature of dialogs (well, I used it, didn't like it, and chose to deliberately avoid it). But his critique is well-taken, and you should read it. I've included it at the end.
Another reason to avoid UpdateData
is that UpdateData
restores all the state. This means you have to remember to save that state! I found this the greatest problem I had in managing the use of UpdateData
before I figured out that it was simply a Bad Idea. You have to make sure, every time a change is made, that you copy all the values out of the controls, via UpdateData
, so that if you want to make another change and restore the values you have the correct set, not some mix of some previous values and current values. This drove me crazy. It will probably drive you crazy, too.
In addition, if you call UpdateData
, it snaps values from memory into all of your edit controls. This will immediately cause a set of OnChange
(WM_COMMAND/EN_CHANGE
) notifications, as each edit control is returned. Now assume that there is some issue about consistency checking, such that modifying control A affects the value in control B. But if you do UpdateData
, you have to deal with the fact that this flurry of events may happen in an order other than the expected order from a user, and you have to make all sorts of provisions to deal with it, such as always setting a flag that indicates if the change should be reacted to or not.
Overall, I found that the three programs I wrote using UpdateData
were harder to write, harder to debug, and much harder to maintain, than any program before or since in which I did not make any use of UpdateData
. To me, ease of maintenance is one of the most important concerns. Low cost of development is also important.
The correct way, I have found, is to use control variables. If you don't understand what a control variable is, click the hyperlink to read my essay on them.
So how do you get the values of the controls while the dialog is active? Just use the control variable to access the control! For example, to determine if the OK
button should be enabled, and it can only be enabled if the text value is nonblank you can write:
CString s;
c_Text.GetWindowText(s);
s.TrimLeft();
c_OK.EnableWindow(s.GetLength() != 0);
Far easier than having to remember how to call UpdateData
(is it TRUE
or FALSE
for the argument?) and avoids the danger of messing over the OK
/Cancel
behavior.
Where do you write the above code? Where do you write the above code? Well, that's another essay!
OK, I'll relent, just a bit, and tell you another reason to not use UpdateData
, which is the only reason I have found to use UpdateData
(keep reading, that's not a typo that makes the sentence look contradictory!) I have a client who is offended by the situation where you have made some changes in a dialog, gotten confused, and had to click Cancel
to get out of the dialog, only to immediately re-enter it to start over. He insisted that every dialog in his product have a Reset
button that reset the dialog to its original state. If you avoid UpdateData
this becomes easy: you use UpdateData
. What I mean is, if you avoid gratuitous, pointless, useless, UpdateData
calls (that is, in almost every dialog I can think of, any call on UpdateData
), then the original information is left intact, and a single UpdateData
call will restore all the original values. But note that the only use of UpdateData
is to load the controls with the original input values, and this will not work if you have used it to grab the values back at intermediate points; in this case, you have destroyed the input state. Actually, I like his style, and so, like most dogmatists, I have to say "but there is an exception". Thus far, it is the only exception I've found for non-database applications (see Doug Harrison's comments, which follow below). Since I have written, literally, hundreds of dialogs in the last five years or so, and after the first three applications have never called UpdateData
, with the one exception of resetting all of the state to the original input state, I cannot get terribly excited about any claims that this call is the right way to do general dialogs. Or that an explicit call of UpdateData
even has any place in them, other than the reset case.
Doug Harrison took the time to reply to my essay. He raises some interesting points, and one of them shows that I haven't used a number of dialog features which can lead to some problems. I include a slightly edited version of his comments here, so you can read them and make your own choices.
Doug writes:
I haven't read all your essays, but I do have some comments concerning your OnUpdateData
essay.
There are some problems with the sequence below:
CMyDialog dlg;
dlg.m_Count = somecounter;
dlg.m_Text = sometext;
dlg.m_Option = someBool;
dlg.DoModal( );
somecounter = dlg.m_Count;
sometext = dlg.m_Text;
someBool = dlg.m_Option;
1. You shouldn't rely on users to initialize the dialog members directly. As with almost every class, it's much better if the ctor has that responsibility.
This is one I disagree with. Only the caller knows what the values are, the default constructor cannot possibly know any details of the calling site. To avoid this, you have to modify the constructor by hand to add the necessary parameters. This, at least the one time I tried it a few versions back, horribly confused the ClassWizard, which proceeded to corrupt the source file.It made me sufficiently nervous that I've not tried it again. The technique of using the constructor, which he shows below (and, which in my opinion as well is a better way to do it), still requires pulling the data out afterward. Microsoft works in an illusory world in which all of these parameters have to be simple scalars, a restriction so brain-dead that I cannot imagine why anyone could ever imagine it is sufficient. I'd rather pass in a pointer to a structure, but then I can't use ClassWizard to deal with the value variables, because it won't parse structure accesses, or anything other than a simple variable. Since I still have to set everything up, and pull it back out, I prefer the symmetry of seeing the assignments. My inclination would be to have a ClassWizard which would allow me to specify a list of parameters which would be passed in as references, and which would update the variables only upon completely successful completion (that is, all validation was passed). In the absence of something useful, I consider the names of the member variables to be initialized to be part of the specification of the interface. Note that only place I ever use the m_
prefix is a member variable which is used to pass parameters to the dialog from the caller; in no other place anywhere in any dialog I write will you ever find an m_
variable that is used for any other purpose. So I disagree with this point, but only because the tools are still primitive. The bottom line is that Microsoft has a long way to go in providing the necessary automation. In the absence of decent automation, I prefer to do it myself. I'm not opposed to automation, just to poor automation. --jmn
2. If you care about the values of those members upon return from the dialog, you must care what DoModal
returns. To see why, consider that if there's a validation error, UpdateData
doesn't run to completion when called from OnOK
, and the user can subsequently exit the dialog by canceling it. The code above is subject to storing a mix of new and old values in that scenario, despite the user canceling the dialog, depending on which DDV
call in DoDataExchange
failed, as well as the order of the DDX
/DDV
calls.
Thus, the code above is better written as:
CMyDialog dlg(somecounter,sometext,someBool);
if (dlg.DoModal() == IDOK)
{
}
In this point he is absolutely correct. Since I never use DDV
, for what I think are sound reasons, I would never have encountered this case. If you do use DDV
, or think you might use DDV
in the future, you need to use the more formally correct form he shows here.--jmn
Also, I can't agree with the premise, "Never call UpdateData
." The function definitely has its uses, even in modal dialogs. For example, consider a dialog box that (in part) contains some controls that provide an interface to a simple database of (key,item) pairs, such as an STL map. There is a Save button, and pressing it saves the current record.
I must admit that I've never wanted to do this; I tend to favor writing the code explicitly so that I have some idea of what is going on. This is not a "I don't trust automation, and I'm a Real Programmer". It is a "The automation produces something that is far too simplistic to meet my needs". But if you want to do this, then the issue is that the dialog itself, rather than the caller, cares about the state, and this is a valid point. --jmn
3. To write the Save
function of your own, you need to fill the dialog data members, and you use UpdateData
for that. If you go to the controls, as you suggest, you bypass necessary dialog data validation as you complicate your code with a lot of control querying. Going directly to the controls is valid only in contexts in which you don't care that the dialog data doesn't reflect the current state of the controls.
I agree, if you are using DDV
. Since I believe that DDV
is deeply flawed in its basic design (it only validates when you click OK, and I don't like the way it works; I prefer to do continuous validation, enabling/disabling OK as the validity of the data changes, and even displaying the status of what is missing, incorrect, or inconsistent in the status line or other location. So I have deliberately chosen to avoid the DDV
mechanism in favor of a far more user-friendly (in my estimation) system. Check out my essays on dialog box state maintenance and on a real-time instantly-reactive validation mechanism. If you choose to use DDV, then his point about bypassing the checking is correct. --jmn
4. You need to override OnOK
, because you want to give the user the chance to save any changes. So, you write a QuerySave
function, which calls Save
if the user gives the OK
. In general, if you override OnOK
, your override must call UpdateData
. In this database example, it's also appropriate to override OnCancel
and have it go through the QuerySave
bit, so OnCancel
can also call UpdateData
in some scenarios.
I also agree, if you are working in a model in which after clicking OK you ask "Save changes?" and require a confirmation. I've always worked on the model that if the user clicks OK the intent is to save any changes, and if the user didn't want to save the changes, the user would click "Cancel". If you otherwise override OnOK
, you can certainly call CDialog::OnOK
which implicitly calls UpdateData
, since calling CDialog::OnOK
eventually invokes the virtual method. --jmn
Aside: When you override OnOK
, if your UpdateData
call and other validation are successful, the correct way to end the dialog is to call EndDialog
, usually as EndDialog(IDOK)
, instead of calling CDialog::OnOK
. This avoids a pointless second call of UpdateData
.
Yes, this is correct, if you choose to use this style. Since I choose to not use this style, it has not been a problem for me. One concern I have is that by calling EndDialog
, you are stating that you know that the implementation of CDialog::OnOK
is just UpdateData
followed by EndDialog
. This tends to violate the notion of inheritance abstraction, which bothers me. In particular, if you later subclass the dialog (another feature Microsoft doesn't support at all), then the superclass may be the class that does this, and doing an EndDialog
explicitly in the subclass will bypass the superclass method and produce incorrect results. But the statement he makes is correct, at least in this version of MFC. --jmn
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.
Send mail to newcomer@flounder.com with questions or comments about this article.
Copyright © 1999 CompanyLongName All Rights Reserved
www.flounder.com/mvp_tips.htm