summaryrefslogtreecommitdiffstats
path: root/qtwinmigrate/doc/walkthrough.qdoc
blob: 840100cbf55d4e0c57c4e89334a47d329d63c57a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
**     of its contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
\page winmigrate-walkthrough.html
\title MFC to Qt Migration - Walkthrough

\tableofcontents

\section1 Introduction

This walkthrough covers the migration of an MFC based program,
generated by Microsoft Visual Studio's MFC Application Wizard, to the
Qt toolkit using the \e{Windows Migration Framework}.

\section2 Goals for this Walkthrough

The original application, located in the \c step1 directory, is a 
trivial SDI program with a main window including a menu bar, a single 
child view and an about dialog. The walkthrough will demonstrate how 
easy it is to gradually replace the MFC code in an existing application
with multiplatform Qt code, while being able to extend the program 
with new functionality based on Qt. The final step will be a complete 
replacement of the Windows-only MFC code with a single source that can 
be compiled for any platform supported by Qt, including Windows, 
Mac OS X, Linux/Unix and embedded Linux.

\section2 A note about MFC's memory leak detection

MFC contains a checkpoint-based memory leak detection mechanism. This
mechanism does not handle well Qt's system of allocating global static
objects. The result is that when running applications that combine Qt
and MFC (like the examples below) from within Visual Studio, one will
get a report about leaked objects upon application exit. These
warnings can safely be ignored.

\section1 Getting Started

Load the project file \c qtmfc1.dsp into a Workspace in Visual Studio
and make sure that everything is set up correctly by building and 
running the project.

The MFC application has an interface to use dialogs provided by an 
external DLL that will be explicitly loaded. The interface is
fairly simple: the DLL must export a C function called \c showDialog
that can take a window handle \e parent. The DLL must show its dialog
modally, and when the function returns the DLL is unloaded again.

The code that does this in the MFC application is in the OnAppAbout
command handler.

\quotefromfile step1/qtmfc.cpp
\skipto WindowsApp::OnAppAbout
\printto //////

If the DLL can be loaded and exports the \c showDialog symbol the
exported function is called, otherwise a default MFC about dialog
is displayed.

\section2 Plugin extension

The project in the \c qtdll example directory implements the plugin 
interface using the QMessageBox class. To use this class a QApplication
object must exist in the current process, and the Qt events must be 
processed in addition to the standard event dispatching performed by 
the running MFC application.

The DLL also has to make sure that it can be loaded together with other
Qt based DLLs in the same process (in which case a QApplication object
will probably exist already), and that the DLL that creates the
QApplication object remains loaded in memory to avoid other DLLs using
memory that is no longer available to the process.

All these issues are handled by the QMfcApp::pluginInstance() function.
This function creates a QApplication object and installs a message hook
that merges the Qt event loop with the existing standard Win32 message
pump of MFC. If an instance to the DLL is passed as a parameter the 
function will also increase the DLL's reference count so that it is not
unloaded before the process exits.

This function can be used in an implementation of the \c DllMain entry 
point function when the DLL is loaded. A static bool variable is used 
to remember whether this DLL is responsible for the QApplication 
object, and when the DLL is unloaded the QApplication object, 
accessible through the global \c qApp pointer, can be deleted.

To use the function and the other Qt classes involved we also need to
include a few header files.

\quotefromfile qtdll/main.cpp
\skipto #include
\printuntil }

The DLL interface is then implemented using an exported C function
called \c showDialog. The QWinWidget class is used to provide the 
proper placement and stacking of the Qt dialog.

\printuntil }

\section2 Linking against Qt

To use Qt classes directly in the MFC application we must link the
application against the Qt libraries, and add the location of the Qt
header files to the compiler's include directories.

The \c step2 directory includes a \c .pro file that generates a
proper \c .dsp file that has all the required settings. Run \
c{qmake -tp vc} in the directory to generate that \c .dsp file, and
check the settings necessary in case you want to modify the Visual 
Studio project manually.

\section2 Replacing the MFC event loop

To be able to use Qt, we need to create a QApplication object. The
QApplication class controls the event delivery and display 
management for all Qt objects and widgets.

In the original MFC project the wizard generated \c WindowsApp class, 
a subclass of \c CWinApp, runs the event loop in the default 
implementation of \c Run(). The MFC event loop is a standard Win32 
event loop, but uses the CWinApp API \c PreTranslateMessage() to 
activate accelerators.

In order to keep MFC accelerators working we must use the 
\c QApplication subclass \c QMfcApp that is provided by the Windows
Migration framework and which merges the Qt and the MFC event loops.

The first step of the Qt migration is to reimplement the \c Run() 
function in the \c WindowsApp class. We can either use the wizard to 
add a reimplementation of Run(), or add the reimplementation ourselves 
to the class declaration in the \c qtmfc.h header file:

\quotefromfile step2/qtmfc.h
\skipto class WindowsApp
\printuntil };

The implementation of this function is in the \c qtmfc.cpp source file.
To use the QMfcApp API we need to \c #include the \c qmfcapp.h header
file.

\quotefromfile step2/qtmfc.cpp
\skipto #include
\printto #ifdef _DEBUG

\skipto BOOL WindowsApp::Run()
\printuntil }

The implementation uses the static run() function of the QMfcApp class
to implicitly instantiate a QApplication object, and run the event
loops for both Qt and MFC:

The code in the plugin DLL does not need to be modified: Since we
have a QApplication object created in the application itself the
pluginInstance() function will do nothing, and the DLL will open the
message box in the exported function just like before.

\section2 Replacing a Dialog

Instead of using the Qt plugin DLL we will now replace the MFC About
Dialog directly in the application source code. Using the Visual
Studio integration toolbar we could quickly create a new QDialog with
\e{Qt Designer} from scratch. Or convert the existing dialogs from
Microsoft resource files into \e{Qt Designer} \c .ui files by using a
conversion tool such as \l
{http://www.kdab.net/?page=products&sub=knut} {KDAB's KNUT} tool. But
for a basic dialog as the one in this example, it is even easier to
use the QMessageBox::about() function as we did in the plugin code
shown earlier.

\quotefromfile step3/qtmfc.cpp
\skipto #include
\printto #ifdef _DEBUG

To use the QMessageBox API we must include the appropriate header.
Since we need to create the QMessageBox as a child of the MFC based
main window we also need to use the \c QWinWidget class again and 
include that header as well.

\skipto WindowsApp message handlers
\printuntil }

We can remove the class declaration and implementation of the 
CAboutDlg class from the source file, and use the QWinWidget and
QMessageBox API in the implementation of the \c WindowsApp::OnAppAbout()
command handler.

A QWinWidget object is created on the stack, using the MFC 
application's main window as the parent window. The showCentered() API 
is used to make sure that the Qt message box, which uses the QWinWidget
object as its parent, will open centered over the main window.

\section1 New Functionality with Qt

We can now add new functionality to the MFC application using Qt.
We will add a Qt based user interface to the MFC child view, and add
an additional modeless options dialog created with \e{Qt Designer}.

\section2 Creating Qt widgets

To be able to create Qt widgets in the initialization of the MFC
application we must first create an instance of QApplication. The
current use of the static \c QMfcApp::run() API creates the 
QApplication object in the \c Run() reimplementation of the CWinApp
subclass, while the GUI is already being created in the \c InitInstance
reimplementation.

\quotefromfile step4/qtmfc.cpp
\skipto ::InitInstance
\printuntil MainFrame* pFrame =
\skipto return true;
\printuntil }

To create the QApplication object in the \c InitInstance
implementation we must use the static function \c QMfcApp::instance().

\printuntil }

\c QMfcApp:run() will then use that instance,
which must then be deleted explicitly using the global \c qApp pointer.

MFC's window creation infrastructure is rather complicated, and we
must add a message handler for the \c WM_CREATE and \c WM_DESTROY
messages to be able to add children only when the creation of the MFC
window is complete, and to delete the children before the MFC window
is destroyed.

\quotefromfile step4/childview.h
\skipto ChildView window
\printuntil ChildView();
\code ... \endcode
\skipto // Generated message map
\printuntil };

We can again use Visual Studio's wizards, or simply 
add the code ourselves. We also add a forward declaration of the 
QWinWidget class, and add a pointer to that class as a member of the 
ChildView.

\quotefromfile step4/childview.cpp
\skipto #include
\printto #ifdef _DEBUG

We include the headers for the Qt widget we want to use, as well as
the header for the QWinWidget class.

\skipto ChildView::ChildView
\printuntil }

We initialize the pointer to the QWinWidget member to zero. We cannot
create it yet, since the MFC window has not yet been created. This 
happens only when the \c MainWindow::OnCreate() message handler calls 
the \c Create function, which then calls our \c ChildView::OnCreate 
implementation.

\skipto BEGIN_MESSAGE_MAP
\printuntil END_MESSAGE_MAP

The message handlers are added to the message map.

\skipto ChildView::OnCreate
\printuntil return -1;

The implementation of the \c OnCreate message handler calls and 
verifies the parent class function and returns an error code if an 
error occurred.

\printuntil }

Now we can create the QWinWidget instance with \c this CWnd instance as
a parent window, and use that instance as a parent to the QWidgets we
want to use to create the user interface. Since QWinWidget is a proper
QWidget it can be laid out, and we move the Qt GUI to the upper left
corner of the MFC child and show() the user interface immediately.

\printuntil }

Since the \c ChildView class of MFC was not supposed to be a container
for other windows we now see some bad flickering whenever we resize
the main window. The \c QLabel widget is obviously painted over by the
\c ChildView window on every resize before it has a chance to fill its
own background. To prevent this we must change the style of
the window to include the \c CS_CLIPCHILDREN flag.

\printuntil }

In the \c OnDestroy message handler we delete the QWinWidget instance,
which deletes all other QWidgets we have created as children.

\section2 A new Qt Dialog

To add a new dialog we use the Visual Studio integration toolbar's
"New Qt Dialog" button. We add a \e{Qt Designer} \c{.ui} file 
"optionsdialog.ui" to the current project, and add the required build 
steps to generate a C++ class from that file.
\footnote
If you don't have the integration toolbar, add the following commands
to the custom build step of the \c .ui file:
\code
%qtdir%\bin\uic.exe $(InputPath) -o $(InputDir)\$(InputName).h
%qtdir%\bin\uic.exe $(InputPath) -i $(InputName).h -o $(InputDir)\$(InputName).cpp
%qtdir%\bin\moc.exe $(InputDir)\$(InputName).h -o $(InputDir)\moc_$(InputName).cpp
\endcode
And the following Output files:
\code
$(InputDir)\$(InputName).h
$(InputDir)\$(InputName).cpp
$(InputDir)\moc_$(InputName).cpp
\endcode
\endfootnote
MFC projects have the precompiled header option turned on by default,
and since Qt or \e{Qt Designer} cannot rely on the compiler used supporting
precompiled headers the respective preprocessor directives are missing
from the generated \c{.cpp} files. We must turn the precompiled headers
option off for those files, but we can just as well turn them off for 
the complete project.

To be able to invoke the dialog we add a new entry to the MFC menu 
using the Visual Studio resource editor. The menu entry is called
"Options", and has the ID \c ID_EDIT_OPTIONS.

\quotefromfile step4/qtmfc.h
\skipto class WindowsApp
\printuntil public:
\code ... \endcode
\skipto // Implementation
\printuntil };

We add a command handler for that option to the WindowsApp class and 
add the mapping to the message map. We also include the generated 
header file.

\quotefromfile step4/qtmfc.cpp
\skipto #include <qmfcapp.h>
\printto #ifdef _DEBUG
\skipto BEGIN_MESSAGE_MAP
\printuntil END_MESSAGE_MAP

The implementation of the command handler once again uses the QWinWidget
class to make sure that the dialog is properly positioned and stacked.
Since we want the dialog to be modeless we cannot create the QWinWidget
on the stack, since it would be deleted when it leaves the scope, and all
its children, including the dialog, would be deleted as well.

\skipto WindowsApp::OnAppOptions
\printuntil }

Instead we create QWinWidget on the heap, using operator \c new, and use
the \c WDestructiveClose widget flag when creating the dialog as a child,
also with operator \c new.

Both the QWinWidget object and the modeless dialog will be destroyed
when the dialog is closed, e.g. when clicking the OK button.

\section1 Removing MFC

We will now turn the complete MFC/Qt hybrid application into a genuine,
multiplatform Qt application. To make our application compilable with 
different compilers for different platforms we need to remove the 
dependency on Visual C++ and Visual Studio, and replace the Visual 
Studio project file with a qmake project.

\section2 Using the Qt build system

The project file lists all the sources involved.

\quotefromfile step4/step4.pro
\printuntil qtwinmigrate

Until we have completed the transition we must still link against
the Windows Migration Framework, compile the Visual C++ resources, set 
the preprocessor define to pull in the MFC DLL, and turn off UNICODE to 
avoid library conflicts with the non-UNICODE MFC version. We must also
remove the \c qtmain library which implements the \c WinMain entry point
function to call the multiplatform \c main entry point.

Running \c{qmake -tp vc} on the \c .pro file will generate a new \c
.dsp file that we can use in Visual Studio to compile and link the
application.

\section2 Replacing the ChildView

The first MFC class we will move over to Qt is the ChildView class. We
replace the old class declaration with the declaration of a QWidget
subclass.

\quotefromfile step5/childview.h
\skipto #ifndef
\printuntil #endif

We don't need any creation and destruction message handlers anymore,
and the QWinWidget member is obsolete as well. However, we will keep an
event handler for paint events as in the original class.

\quotefromfile step5/childview.cpp
\skipto ChildView::ChildView
\printuntil QPainter
\printuntil }

The implementation of the class creates the user interface elements
directly in the constructor and sets the erase color property to white. 
The paintEvent does nothing, at least for now.

\section2 Replacing the MainFrame

The next MFC class we will move over to Qt is the MainFrame class. We
could use \e{Qt Designer} to generate a main window in a visual 
environment, but it's just as easy to add the few elements manually.

\quotefromfile step5/mainframe.h
\skipto #ifndef
\printuntil #endif

The class implements the constructor, and keeps a reference to the 
ChildView object. The \c Q_OBJECT macro is used to allow this class to
declare signals and slots. We add two slots, \c editOptions and 
\c helpAbout.

\quotefromfile step5/mainframe.cpp
\skipto MainFrame::
\printuntil }

The implementation of the class creates the menu, instantiates the
ChildView as the central widget, and adds a status bar.

\printuntil }

\printuntil }

The slot implementations are identical to the application command
handlers in the Qt/MFC hybrid, but of course don't need to use the 
QWinWidget class anymore.

\section2 Replacing the MFC application

The final step is to remove the WindowsApp class completely, and
handle the application startup and initialization in a multiplatform
\c main entry point function.

\quotefromfile step5/step5.pro
\printuntil RC_FILE

We can delete the \c qtmfc.h header file and remove it from the 
\c HEADERS section in the qmake \c .pro file. We can also remove the 
linking against the Migration Framework library, and the modifications 
of the preprocessor symbols.

Then we rerun qmake to regenerate the \c .dsp file. Since we added the
\c Q_OBJECT macro to the \c MainFrame class declaration we have to make
sure that the meta object compiler, \c{moc}, is added to the build
step for the \c MainFrame class, and this is also done when running
qmake.

The \c qtmfc.cpp file is completely replaced.

\quotefromfile step5/qtmfc.cpp
\skipto #include
\printuntil }

All it does now is to include the required headers, and to implement
a standard main entry point function.

\section2 Cleaning up

Finally we edit the resource script (with a plain text editor, Visual
Studio is too powerful for this) and remove the obsolete entries. The
only entry we keep is the application icon.

\quotefromfile step5/qtmfc.rc
\printuntil IDR_MAINFRAME

Now we can delete the files that are no longer needed:

\code
StdAfx.h, StdAfx.cpp, qtmfc.h, res/qtmfc.rc2, resource.h
\endcode

The code we have now does the same job as the MFC based code, is much
smaller, has less files to maintain and will compile with different 
compilers for different platforms.

*/