30.3 An application-wide mute variable

The point of a mute variable is, as we mentioned above, to allow the user to turn all the sound for the application off or on. Let's repeat that you should never ever write a game that makes noise without giving the user an easily usable control to turn the sound off. Why? Sooner or later a user might get sick of the sounds. Or they might want to play the game without annoying the person at the next desk. Or maybe they want to play it in a crowded airplane. There has to be a way to tell the program to shut up.

What we need here is a BOOL _soundflag variable which we can set to either TRUE or FALSE. Where should the variable live? Keep in mind that Pop is an MDI application. It's capable of showing multiple views of multiple documents. If you turn sound off in one view window of the Pop program, you don't want it to suddenly come back on when you switch to another view or document window. This means that we don't want the _soundflag variable to live down inside the CPopView class. And by the same token, it won't do to put it inside CPopDoc. No, we need for _soundflag to be something very much like a global variable.

Well, what's the biggest scope object that an MDI program has? It turns out that your program always has one single CWinApp object, which stands for the program, or 'application' itself. We don't often put things into this class, but when you do have some application-wide data, this is the place it should go.

So we opened up Pop.h, found CPopApp, and declared a private: BOOL _soundflag; in the bottom of the class declaration. The variable doesn't need to be public as only CPopApp will do things to it or look at it. And then we opened Pop.cpp and initialized _soundflag to TRUE in the CPopApp::CPopApp constructor.

As we already discussed, we wrap the call to ::PlaySound up inside a CPopApp::playSound method, and we call this from anywhere in the program with a line like

((CPopApp*)::AfxGetApp())->playSound("Ding", SND_RESOURCE | 

What is the first part of this line doing? MFC provides you a bunch of special global functions for getting information about your application. These function start with the letters Afx. The AfxGetApp() returns a pointer to the currently active CWinApp object.

Alright, but why didn't we just write our code in a simple way like this?

(AfxGetApp()->playSound ... 

Well, if it was simple, it wouldn't be Windows programming, would it? Look at the upside: if Windows programming was simple, you wouldn't be able to get such a good salary for knowing it!

The thing is, AfxGetApp returns a CWinApp* pointer. And the general CWinApp class of course doesn't have a playSound member function. Only the child class CPopApp has the playSound() method. And if we try and compile the code as written the 'simple' way, the compiler will give us an error message telling us the problem. So after we get the AfxGetApp() pointer to our application, we need to cast it into a CPopApp* pointer, a pointer to our modified child version of the CWinApp class.

By the way, since AfxGetApp isn't a member of any class, in consistency with our practice of signaling a non-class-member function by the :: symbol we put that in front of it. We actually write our code like this.

((CPopApp*)::AfxGetApp())->playSound ... 

The exact way that you write the parentheses for the cast is important. That is, a C++ pointer cast has to have the form ((NewPointerType*)Pointer). As we mentioned earlier, you can instead use the C++ dynamic_cast operator and write lines like the following. Recall that, to help you catch errors, the dynamic_cast operator returns a NULL pointer if the cast is for some reason impossible.

CPopApp *ppopapp = dynamic_cast<CPopApp*>(::AfxGetApp()); 
ppopapp -> playSound(...); 

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference