|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
![]() Last update2/27/06 - Updated code in zip file to fix a bug. Also slightly modified the 'Modeless dialogs' section outlining the bug and the fix. IntroductionThis article will discuss one of DWinLib's benefits that I have not seen in other window wrappers. That is the elimination of dialogs. This solution makes Visual Studio Express much more usable for a straight C++ Windows programming environment, as it circumvents the lack of a resource editor in VSE in a fairly simple manner. Along the path we'll see what else kicks up. It should be an interesting ride, so tag along! Getting startedYou might think that getting rid of dialogs is not that big of an issue. After all, you simply design them in your drag-n-drop editor, and use the thingy's. Right? OK, OK. It isn't quite so simple, is it? You have to create a dialog window procedure, and you have to deal with control IDs. And, if you are not using a Visual Studio environment, you may not have access to a great dialog editor. In addition, I believe there are also some other rules you must keep in mind while you design that window procedure. In my opinion, dialog boxes are an Achilles' heel in Windows programming, especially if you are not using Visual Studio. Cross-compiler compatibility and ease-of-use were prime considerations in DWinLib, and even though I did not have the time or the inclination to create a dialog editor, I was able to make it so you can create dialog-like windows without having the problems I have mentioned. (But if you really want to create dialogs the hard way, it is possible to define a dialog resource yourself, and call As I write this, I have succeeded in making both modal and modeless dialog equivalents in DWinLib, but there is one thing I haven't done, and that is returning a value from my equivalent window. We will think some more about this problem as we go through this article, and give a method that may work for you. So, let's get started. Hello World, with polish!Even though I have shown how to build a basic DWinLib program, I have not shown you how to modify it for your own use. Let me rectify that situation with the most boring of intro programs, "Hello World", DWinLib style. If you are going to follow along without using the above zip file, first create a subdirectory to work in, and extract the .h, .cpp, and .rc files from the basic DWinLib program zip file into this directory. You might also want to rename the 'BareApp.cpp' and 'BareApp.rc' files to 'ModalExample.cpp' and 'ModalExample.rc'. Then create a project in the subdirectory, as outlined in the build instructions that were given. Complete the instructions until the program will compile and run. Actually, if you have already followed the basic DWinLib program instructions, the easiest way to get your subdirectory set up properly may be to copy the 'BareApp' directory, rename the copy to whatever you want, delete the .sln, .user, .ncb, .vcproj, and .suo files in the new directory, and create the project in the new directory as outlined in the instructions in the basic DWinLib program page. Again, you might want to change the 'BareApp' units to 'ModalExample' units. The above zip file uses a "D:\Programs\DWinLibExamples\ModalExample" subdirectory to hold the project. The project is named "ModalExample", and the "Hello World" example will be a drive-by shot in this solution. After you have succeeded in compiling the program and executing it in Debug mode, it is time to get busy. For the stupidity of "Hello World", double-click the ' LRESULT AppWindow::wPaint(WinDC & dc) {
RECT r;
GetClientRect(hwndC, &r);
FillRect(dc(), &r, (HBRUSH)(COLOR_WINDOW+1));
return 0;
}
Modify this to: LRESULT AppWindow::wPaint(WinDC & dc) {
RECT r;
GetClientRect(hwndC, &r);
FillRect(dc(), &r, (HBRUSH)(COLOR_WINDOW+1));
std::string str = "Hello World!";
//Now, check check how big the text will be:
SIZE textSize;
GetTextExtentPoint32(dc(), str.c_str(), str.length(), &textSize);
TextOut(dc(), (clientWidth()-textSize.cx)/2, (clientHeight()-textSize.cy)/2, str.c_str(),
str.length());
return 0;
}
Build and execute the project, and when it runs, press 'Ctrl+N'. You will have a window that looks something like this: ![]() As you can see, it has quite a lot more on it than a standard "Hello World" program. It already has a menu, a status bar, and a dockable toolbar. It is also already set up as a MDI (Multiple Document Interface) program - pressing 'Ctrl+N' again will create a new subwindow. It is these reasons and more that justify the existing I could have made it so that you had to derive Let us give our wonderful little program another item, and make it flicker-free (almost). If you resize the child window and you pay attention, you will note that the "Hello World!" text will sometimes flicker. To eliminate that flicker, replace the LRESULT AppWindow::wPaint(WinDC & dc) {
RECT r;
HDC mdc = CreateCompatibleDC(dc());
GetClientRect(hwndC, &r);
HBITMAP bmp = CreateCompatibleBitmap(dc(), r.right-r.left, r.bottom-r.top);
HBITMAP oldBmp = (HBITMAP)SelectObject(mdc, bmp);
FillRect(mdc, &r, (HBRUSH)(COLOR_WINDOW+1));
std::string str = "Hello World!";
SIZE textSize;
GetTextExtentPoint32(mdc, str.c_str(), str.length(), &textSize);
TextOut(mdc, (clientWidth()-textSize.cx)/2, (clientHeight()-textSize.cy)/2, str.c_str(),
str.length());
BitBlt(dc(), 0, 0, wWidthC, wHeightC, mdc, 0, 0, SRCCOPY);
SelectObject(mdc, oldBmp);
DeleteObject(bmp);
DeleteDC(mdc);
return 0;
}
Resize the And the reason I said that the program is 'almost' flicker-free is because the program is set up to use a standard Windows status bar. If you resize the main window using the top edge, you will notice that the "This is a status bar test." text flickers terribly. That is the joy of Windows Common Controls - you have to sometimes go to extraordinary lengths to overcome this flicker. In this case, you can replace the references to the ' I tried many things to get the Windows Common Control status bar to stop flickering, but in the end I simply resorted to creating a new window to put in its place. That unit needs some work in order to respond to custom user text height settings, but it should get you started. It is also missing 'zones', or whatever they are called, and does not have the angled hash gripper in the right corner, but it works for a flicker-free demo. Oh! One last thing. You will notice that the application remembers its settings between executions. This is performed using Moving On!All righty, then! Lets get on to those modal thingy's, and see a couple more tricks in DWinLib! First, undo the "Hello World" changes, so we have a nice white screen to play with again. Now let us put a button on the window that will open our 'dialog'. In the Now, in the buttonC.reset(new WinButton(this, 10, 10, 130, 25, "Open Modal Dialog", CreateDelegate(AppWindow, buttonCallback, this, this))); Then add the following two function definitions: void AppWindow::buttonCallback(WinObject * ) { ModalBaseForm * w = new ModalBaseForm(200, 200, 200, 200); //You can perform 'ShowWindow' from the ModalBaseForm ctor, or from here. //I usually do it here, so I can use values this window may know about to center //the window where I want it to be centered. Doing it from here also //allows you to derive several layers deep from ModalBaseForm, and //not worry about flashing the window in a partial state on the screen before //the final constructors finish. ShowWindow(w->hwnd(), SW_SHOW); } LRESULT AppWindow::wPosChanged(WINDOWPOS * wp) { buttonC->moveWindow((clientWidth()-buttonC->width())/2, (clientHeight()-buttonC->height())/2, buttonC->width(), buttonC->height()); return 0; } Build and run the executable. 'Ctrl+N' a new ![]() You will note that there does not appear to be any way to close the window, but pressing the ' Before we do this, though, let us make a non-note comment about the In order to really obtain that aspect of the functioning of modal dialog boxes, it would be necessary to either re-write DWinLib to enable it to create windows in separate threads, or create your dialog the hard way, and create a window procedure for it as well as a resource. The reasons I say you would need to enable DWinLib to create windows in separate threads are because 1) I believe that is what Faking a return valueLet us start by adding a header and .cpp file to the 'aaa' filter in the Solution Explorer. I usually just take a .cpp / .h file combination that is close to the class I need, copy them to a new name, drag that into the Solution Explorer, and then modify the files in the IDE. But to my knowledge, I don't have a file that is close for the purposes at hand, and I should probably show this method at least once. (I sure wish VCE had Borland's 'Create New Unit' command, that added a .h / .cpp combination, and even sets up the include guards in the .h file!) So, right-click on the 'aaa' filter, select 'Add -> New Item' twice, selecting 'Visual C++ -> Header File (.h) and 'C++ File (.cpp)', using a filename of 'ModalReturnTest' for them. Then modify them to: //ModalReturnTest.h: #ifndef ModalReturnTestH #define ModalReturnTestH #include "ModalBaseForm.h" class ModalReturnTest : public ModalBaseForm { public: ModalReturnTest(); }; #endif //ModalReturnTest.cpp: #include "PrecompiledHeaders.h" #pragma hdrstop #include "ModalReturnTest.h" ModalReturnTest::ModalReturnTest() : ModalBaseForm(200, 200, 200, 200) { } Then change the ModalReturnTest * w = new ModalReturnTest();
You can compile now, and have the same thing we had earlier. Now it's time to flush the functionality out in the derived unit, so let me get to work. I'll post the completed unit below, but here is the order I'm taking to get there as I work...
And, after completing the above, the following is the result: //ModalReturnTest.h, excluding include guards: #include "ModalBaseForm.h" #include "WinButton.h" class AppWindow; class ModalReturnTest : public ModalBaseForm { public: //Step 3: Set up a public enum for the return value: enum MRT { OptionA, OptionB, OptionC }; private: rmw::grin_ptr<WinButton> buttonAC; rmw::grin_ptr<WinButton> buttonBC; rmw::grin_ptr<WinButton> buttonCC; rmw::grin_ptr<WinButton> cancelButtonC; MRT returnValueC; AppWindow * appWindowC; public: ModalReturnTest(AppWindow * win); void buttonCallback(WinObject * obj); }; //ModalReturnTest.cpp: #include "PrecompiledHeaders.h" #pragma hdrstop #include "ModalReturnTest.h" #include "AppWindow.h" ModalReturnTest::ModalReturnTest(AppWindow * win) : ModalBaseForm(200, 200, 155, 165), appWindowC(win) { //Step 1: Modify the title bar: DWORD style = ((WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_OVERLAPPEDWINDOW) & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX); SetWindowLong(hwndC, GWL_STYLE, style); SetWindowPos(hwndC, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_FRAMECHANGED); SetWindowText(hwndC, "Modal Return Test"); //Step 2: Set up the buttons: buttonAC.reset(new WinButton(this, 10, 10, 130, 25, "Option A", CreateDelegate(ModalReturnTest, buttonCallback, this, this))); buttonAC->WinControlWin::user = OptionA; buttonBC.reset(new WinButton(this, 10, 40, 130, 25, "Option B", CreateDelegate(ModalReturnTest, buttonCallback, this, this))); buttonBC->WinControlWin::user = OptionB; buttonCC.reset(new WinButton(this, 10, 70, 130, 25, "Option C", CreateDelegate(ModalReturnTest, buttonCallback, this, this))); buttonCC->WinControlWin::user = OptionC; cancelButtonC.reset(new WinButton(this, 10, 100, 130, 25, "Cancel", CreateDelegate(ModalBaseForm, cancel, this, this))); } //Step 4: Set up the callback: void ModalReturnTest::buttonCallback(WinObject * obj) { WinButton * button = d_cast<WinButton*>(obj); if (!button) return; MRT option = button->user.cast<MRT>(); appWindowC->optionCallback(option); wClose(); } Pretty simple, all told. You will note using this technique that the callback is called before the 'modal' window is destroyed. I don't see any reason that would ever be a problem, but decided to point it out anyway. You will also note that the Of course, it is better to disallow incompatible items to be selected on the form in the first place, but the above method allows flexibility on your end. You can also disregard And that's about it for modal dialog boxes. Modeless dialogsI just had a thought as I was writing this. Previously, I had created modeless dialogs by simply creating a window with no parent, and doing the work in that. If you try out MEdit 3.03, an example is the frequency finder window. That method has the drawback that it creates another icon in the Windows Start Bar. This can be circumvented by modifying the definition and implementation of the As I am doing this, I will note that I made the decision that only modal dialogs will create a new accelerator table state. This keeps things simple, as it otherwise becomes necessary to add logic to enable and disable the accelerator table state whenever the modeless window becomes enabled and disabled. I also made the decision to make the Lucky me! That change did not change a single item in the above exposition. ... Darn! I want the example project to show one of each form, therefore I'm going to modify the This solution to modal dialogs in DWinLib raises two more items that should be mentioned before leaving. The first is that if you test the example program, you will find that it is possible to open a modeless dialog and then a modal dialog, and it will be possible to switch to the modeless dialog while the modal dialog is displayed. A true modal dialog would not allow that to happen. A solution to that problem is to hold a pointer to the modeless dialog when it is created, have the modeless dialog call back to the The second item is that if you must make a response to the user's input, as I have in the demo, using a To make the modal window the parent of the == A Wrap ==That feels like a good place to end this article. I hope the information in here is is useful to you, and was as enjoyable to read as it was to write. NotesNote 1 - The use of the Note 2 - Windows can be declared either on the stack or in the heap in DWinLib. DWinLib does not impose any limitations, but the logic will quite often dictate that the window must be in the heap, as in this instance. The button cannot be stack-based here, as the button requires a valid If, on the other hand, we were working with a window derived from (I don't often do this, simply out of the habit of keeping the number of Note 3 - You will see that you cannot remove the gWinApp->accelC.addAccelerator(FVIRTKEY, VK_ESCAPE, (short)cancelCallbackC->id()); When the As noted at the end of the article, the accelerator table is only changed for modal dialog windows. Modeless windows will not have their own accelerator. Update history2/27/06 - Updated code in zip file to fix a bug. Also slightly modified the 'Modeless dialogs' section by outlining the bug and the fix.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||