Software Application Development A Visual C MFC and STL Tutorial - Compress
Software Application Development A Visual C MFC and STL Tutorial - Compress
Development
A Visual C++ , ®
Bud Fox
Zhang Wenzu
Tan May Ling
Software Application
Development
A Visual C++ ,
®
Bud Fox
Zhang Wenzu
Tan May Ling
MATLAB® and Simulink® are trademarks of The MathWorks, Inc. and are used with permission. The MathWorks does
not warrant the accuracy of the text or exercises in this book. This book’s use or discussion of MATLAB® and Simulink®
software or related products does not constitute endorsement or sponsorship by The MathWorks of a particular peda-
gogical approach or particular use of the MATLAB® and Simulink® software.
CRC Press
Taylor & Francis Group
6000 Broken Sound Parkway NW, Suite 300
Boca Raton, FL 33487-2742
© 2013 by Taylor & Francis Group, LLC
CRC Press is an imprint of Taylor & Francis Group, an Informa business
This book contains information obtained from authentic and highly regarded sources. Reasonable efforts have been
made to publish reliable data and information, but the author and publisher cannot assume responsibility for the valid-
ity of all materials or the consequences of their use. The authors and publishers have attempted to trace the copyright
holders of all material reproduced in this publication and apologize to copyright holders if permission to publish in this
form has not been obtained. If any copyright material has not been acknowledged please write and let us know so we may
rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be reprinted, reproduced, transmitted, or uti-
lized in any form by any electronic, mechanical, or other means, now known or hereafter invented, including photocopy-
ing, microfilming, and recording, or in any information storage or retrieval system, without written permission from the
publishers.
For permission to photocopy or use material electronically from this work, please access www.copyright.com (http://
www.copyright.com/) or contact the Copyright Clearance Center, Inc. (CCC), 222 Rosewood Drive, Danvers, MA 01923,
978-750-8400. CCC is a not-for-profit organization that provides licenses and registration for a variety of users. For
organizations that have been granted a photocopy license by the CCC, a separate system of payment has been arranged.
Trademark Notice: Product or corporate names may be trademarks or registered trademarks, and are used only for
identification and explanation without intent to infringe.
Visit the Taylor & Francis Web site at
http://www.taylorandfrancis.com
and the CRC Press Web site at
http://www.crcpress.com
Contents
Preface..........................................................................................................................................xxvii
Acknowledgments..........................................................................................................................xxix
Introduction....................................................................................................................................xxxi
Authors........................................................................................................................................xxxvii
v
vi Contents
3.6.1.9 CSignalGeneratorBlock::DrawBlock()................ 86
3.6.1.10 CSubsystemBlock::DrawBlock()............................. 88
3.6.1.11 CSubsystemInBlock::DrawBlock()......................... 89
3.6.1.12 CSubsystemOutBlock::DrawBlock()......................92
3.6.1.13 CTransferFnBlock::DrawBlock()...........................94
3.6.2 Display of Block Graphics..................................................................96
3.7 Summary..........................................................................................................96
References...................................................................................................................96
5.3.4 Attach Event-Handler Functions for the Context Menu Entries....... 131
5.3.4.1 Item Deletion..................................................................... 132
5.3.4.2 Set Item Properties............................................................ 134
5.3.5 System Model–Based Object Deletion.............................................. 134
5.4 Summary........................................................................................................ 135
References................................................................................................................. 135
9.3.5.3
Attach Variables to the Dialog Window Controls............. 221
9.3.5.4
Add Functionality to the Dialog Window Buttons............ 223
9.3.5.5
Add Functionality to Initialize Variables.......................... 223
9.3.5.6
Add the Overriding
BlockDlgWndParameterInput() Function..............224
9.3.6 Linear Function Block Dialog...........................................................224
9.3.6.1 Insert a New Dialog Window and Add Controls...............224
9.3.6.2 Attach a Class to the Dialog Window...............................224
9.3.6.3 Attach Variables to the Dialog Window Controls............. 225
9.3.6.4 Add Functionality to the Dialog Window Buttons............ 226
9.3.6.5 Add Functionality to Initialize Variables.......................... 227
9.3.6.6 Add the Overriding
BlockDlgWndParameterInput() Function.............. 227
9.3.7 Output Block Dialog.......................................................................... 228
9.3.7.1 Insert a New Dialog Window and Add Controls............... 228
9.3.7.2 Attach a Class to the Dialog Window............................... 228
9.3.7.3 Attach Variables to the Dialog Window Controls............. 229
9.3.7.4 Add Functionality to the Dialog Window Buttons............ 229
9.3.7.5 Add Functionality to Initialize Variables.......................... 230
9.3.7.6 Add the Overriding
BlockDlgWndParameterInput() Function.............. 230
9.3.8 Signal Generator Block Dialog......................................................... 231
9.3.8.1 Insert a New Dialog Window and Add Controls............... 231
9.3.8.2 Attach a Class to the Dialog Window............................... 231
9.3.8.3 Attach Variables to the Dialog Window Controls............. 231
9.3.8.4 Add Functionality to the Dialog Window Buttons............ 233
9.3.8.5 Add Functionality to Initialize Variables.......................... 233
9.3.8.6 Add the Overriding
BlockDlgWndParameterInput() Function.............. 234
9.3.9 Subsystem Block Dialog.................................................................... 235
9.3.9.1 Insert a New Dialog Window and Add Controls............... 235
9.3.9.2 Attach a Class to the Dialog Window............................... 235
9.3.9.3 Attach Variables to the Dialog Window Controls............. 236
9.3.9.4 Add Functionality to the Dialog Window Buttons............ 236
9.3.9.5 Add Functionality to Initialize Variables.......................... 237
9.3.9.6 Add the Overriding
BlockDlgWndParameterInput() Function.............. 237
9.3.10 Subsystem In Block Dialog............................................................... 237
9.3.10.1 Insert a New Dialog Window and Add Controls............... 238
9.3.10.2 Attach a Class to the Dialog Window............................... 238
9.3.10.3 Attach Variables to the Dialog Window Controls............. 238
9.3.10.4 Add Functionality to the Dialog Window Buttons............ 239
9.3.10.5 Add Functionality to Initialize Variables.......................... 239
9.3.10.6 Add the Overriding
BlockDlgWndParameterInput() Function..............240
9.3.11 Subsystem Out Block Dialog.............................................................240
9.3.11.1 Insert a New Dialog Window and Add Controls...............240
9.3.11.2 Attach a Class to the Dialog Window...............................240
9.3.11.3 Attach Variables to the Dialog Window Controls............. 241
9.3.11.4 Add Functionality to the Dialog Window Buttons............ 241
Contents xi
25.5.6.11 CSubsystemOutBlock::ReadBlockData
FromFile()������������������������������������������������������������������ 805
25.5.6.12 CSumBlock::ReadBlockDataFromFile()............806
25.5.6.13 CTransferFnBlock::ReadBlockDataFrom
File()����������������������������������������������������������������������������806
25.6 Saving the Initial Output Signal for the Divide Block...................................807
25.6.1 Add Member Variables to the CDivideBlock Class..........................808
25.6.2 Amend the CDivideBlock Constructor and Destructor Functions...... 808
25.6.3 Add the Overriding AssignBlockOutputSignal()
Function to the CDivideBlock Class.................................................809
25.6.4 Amend the CDivideBlock Class Serialization Functions................. 813
25.6.5 Amend the PreliminarySignalPropagation() Function........814
25.7 Adding Data-Writing Functionality to the Output Block............................... 819
25.7.1 Add a Save Data Button to the Output Block Dialog Resource........ 819
25.7.2 Add an Event-Handler Function for the Save Data Button............... 819
25.8 Summary........................................................................................................824
References................................................................................................................. 825
Part III Refinement
A TUTORIAL
This book has been written as a set of steps that can be followed to develop a Visual C++ demon-
stration application addressing the main features of Windows-based applications provided for by
Visual C++ 6.0 and later editions of C++. The step-by-step instructions show how the development
of an application is progressive and that careful consideration is required upon implementation,
since all decisions will affect the structure and maintainability of the software at later stages. The
software developed here is titled DiagramEng. It is a block-diagram-based engineering applica-
tion used to model and simulate dynamical systems and is similar to what one may encounter in
control engineering. The mathematics and computer code has intentionally been kept simple and
easy-to-understand, and the detailed explanations clarify the underlying concepts and problems
encountered. All computer code is included in this learn-by-example approach. Once the developer
has gone through this extensive tutorial, he or she would be aptly prepared for real-world Visual
C++ software application development and be ready to take the next developmental step forward.
WRITE IT DOWN
Always be sure to think on paper. Write things down. There is something that happens between the
brain and the hand when you write. You get a greater sense of clarity and understanding with regard to
the issues involved.
Brian Tracy [1]
Software development starts with turning off the screen, putting the keyboard aside, and writing
ideas down with a pen on paper. Reactionary behavior is then replaced by thoughtfulness and peace
of mind, allowing ideas to surface and flow from one problem to the next. Diagrams are drawn, text
is used to explain the desired software functionality, key nouns and verbs are highlighted, candidate
classes are identified, and header files may be written, and these then form an initial basic design.
To implement all the necessary application functionality, write every idea down on paper first,
plan the software development steps, discuss them with colleagues, formulate them as instructions,
and then finally code them in easy-to-understand and easy-to-remember progressive actions.
PREREQUISITES
The prerequisites to be able to follow the instructions in this book are enthusiasm and hard work.
The reader should also have an understanding of how to program in C/C++, and this may be devel-
oped by working through texts similar to “Teach Yourself C++ in 21 Days” [2] and “Teach Yourself
C++ in 24 Hours” [3]. However, as this book is a tutorial on Visual C++ software demonstration
application development, the reader is encouraged to read and work through Chapters 1 through 8
and 10 through 13 of “Teach Yourself Visual C++ 6 in 21 Days” [4]. These chapters cover the essen-
tial fundamentals of using the Microsoft® Visual C++ 6.0 and Visual Studio™ 6.0 Development
xxvii
xxviii Preface
System (integrated development environment [IDE]) [5], and provide instructions on how the user
can build Visual C++ applications similar to the Windows-based applications ubiquitous in modern
personal computing environments.
However, if the reader would prefer to simply start with the material in this text, Software
Application Development: A Visual C++®, MFC, and STL Tutorial, then that is certainly possible,
since the instructions herein are detailed, progressive, complete, and supported by many figures,
tables, and error-resolving notes. Moreover, the source code, including all source and header files
necessary to compile, link, and run the software application is included; nothing is omitted.
Additional materials are available for download from http://www.crcpress.com/product/
isbn/9781466511002
MATLAB ® is a registered trademark of The MathWorks, Inc. For product information, please
contact:
The MathWorks, Inc.
3 Apple Hill Drive
Natick, MA, 01760-2098 USA
Tel: 508-647-7000
Fax: 508-647-7001
E-mail: [email protected]
Web: www.mathworks.com
Microsoft products for which there exist trademarks is presented here:
http://www.microsoft.com/about/legal/en/us/intellectualProperty/Trademarks/EN-US.aspx
Acknowledgments
We would like to acknowledge the following people and organizations that have directly or indi-
rectly influenced the content of this book:
• The Institute of High Performance Computing (IHPC) [6]—for supporting the Interactive
Engineering Research Group through the duration of the project
• Dr. Zsolt Szabo (IHPC)—for providing useful guidelines about computation of nonlinear
systems using the Newton method
• Dr. Wang Binfang (IHPC)—for her advice concerning various implementation issues
throughout the software development process
• Jeanne, Chow Hui Hsien (IHPC)—for her enthusiastic and friendly computer systems
support
• Wayne, Lee Choon Hiang (IHPC)—for his efficient computer systems support and will-
ingness to help proactively whenever problems arose
• Evelyn Lau (Director, Science and Engineering Research Council [7] Shared Services)—for
her encouragement towards people development.
• Sharon Ee Chew Suan (Director, IHPC Corporate Services)—for her encouragement
towards people development and its implementation within IHPC.
• Microsoft Corporation—for Visual C++ 6.0, Visual Studio 6.0, Windows-based applica-
tions, and the Microsoft Developer Network (MSDN) [8] for a valuable source of informa-
tion on software development, Microsoft Foundation Classes (MFC), and the Standard
Template Library (STL)
• The MathWorks—for two very powerful, useful, high-level software applications,
MATLAB® and SIMULINK® [9], that have been an inspiration to us and have provided
us with an example and standard of interactive mathematical modeling and computation
software.
• Jesse J. Pepper and Daniel Paull (Think Bottom Up Pty. Ltd. [10])—for their helpful insight
into improving the developed software application DiagramEng
• Dr. Roy M. Howard (Curtin University of Technology)—for his insight concerning the
classification of signals and systems
• Professor Leslie S. Jennings (The University of Western Australia)—for his important
input concerning the computation of nonlinear systems using Newton’s method
• Professor David J. Lilja (University of Minnesota)—for his thoughtful comments toward
improving the manuscript
• Professor Albert Y. Zomaya (The University of Sydney)—for his input toward professional
publication
• Professor Michael A. Small (The University of Western Australia)—for his feedback
concerning improvement of the manuscript
• Assistant Professor Karl Erik Birgersson (National University of Singapore)—for his
enthusiasm and helpful suggestions in making the book a valuable teaching aid for aca-
demics introducing the discipline of computer science to students
• Project manager Arunkumar Aranganathan (SPi Global)—for overseeing the production,
including typesetting and editing of the text.
xxix
xxx Acknowledgments
We would sincerely like to thank Ms Randi Cohen, acquisitions editor, Computer Science,
Chapman & Hall/CRC Press, for her belief in the subject matter; persistence and patience with
legal, business and administration issues; efficiency in communication; and tireless hard work
in managing the acquisition, production and promotion of this book that shares the software
development process in detail with the reader.
REFERENCES
1. Tracy, B., Goals: How to Get Everything You Want – Faster than You Ever Thought Possible, Berrett-
Koehler, San Francisco, CA, 2004.
2. Liberty, J., Teach Yourself C++ in 21 Days, SAMS Publishing, Indianapolis, IN, 2001.
3. Liberty, J. and Horvath, D. B., Teach Yourself C++ in 24 Hours, SAMS Publishing, Indianapolis, IN,
2005.
4. Chapman, D., Teach Yourself Visual C++ 6 in 21 Days, SAMS Publishing, Indianapolis, IN, 1998.
5. Microsoft Visual C++ 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
6. Institute of High Performance Computing, www.ihpc.a-star.edu.sg
7. Science and Engineering Research Council, www.a-star.edu.sg
8. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
9. The MathWorks Inc., www.mathworks.com
10. Think Bottom Up, Pty. Ltd., www.thinkbottomup.com.au/site
Introduction
Most often, people start something new, only to give up at the very beginning. The sense of shame
encroaches and leaves the entrant to a field feeling overwhelmed, where failure appears inevitable.
All that is needed is a thorough tutorial from which one may learn by example and move forward.
This book is that tutorial!
CONTENTS
The text is structured in a progressive manner: a software application idea is introduced, a design
method ensues, a preliminary Win32 Console Application is written, and then the actual Visual
C++ coding commences. Thereafter, code modules are added incrementally, problems are encoun-
tered, and methods to solve them are suggested and implemented. Each step of the development
procedure is engaging and rewarding: it involves the reader and provides instructions to move from
one step to the next at a smooth pace. It is similar to following a recipe for making a coffee cake.
The following is a brief description of the material covered in each chapter of the book: it is
designed to convey to the reader in a brief manner all the steps to be expected toward developing
the end software product titled, DiagramEng. There are three main parts in this book: (1) User
Interaction, (2) Model Computation and Data, and (3) Refinement.
Part I consists of Chapters 1 through 17 and provides instructions to first design the software and
then discusses key graphical interface elements and user-interaction sequences. Menus, toolbars,
dialog windows, controls, string conversion, blocks, connections, item movement, and presentation
are all thoroughly covered to allow the user to set up and draw a model diagram containing feed-
back loops that represent a real-world engineering problem.
xxxi
xxxii Introduction
Part II, Model Computation and Data, consists of Chapters 18 through 25 and leads the reader
carefully through the preparation and implementation of block-based data operations and a Newton-
method-based nonlinear equation solver to compute a system model containing feedback/algebraic
loops that represent a set of simultaneous equations. Six key examples are explored and graphical
output displayed, confirming the underlying physics of the problems concerned. Finally, serialization
is performed to allow the user to save and restore system model data and save graphical output data.
Part III, Refinement, consisting of Chapters 26 through 34, completes the software development
process, firstly by reviewing what remaining functionality is to be added and then implementing
the necessary features typically found in Windows-based applications, including printing and print
preview, a scrolling view, editing actions, annotations, diagnostic information, and the presentation
of a help-related document, before finalizing the project.
and unsnapped to and from block input and output ports, respectively, where the tail point can also
emanate from another connection’s bend point. Finally, the deletion of individual connection-based
bend points and whole connection objects is implemented.
Chapter 7, Automatic Block Placement, addresses placement of blocks on the palette by compar-
ing two possible methods. Blocks are automatically placed in an array-like manner, horizontally and
vertically, in the earliest available position on the palette, such that they are sufficiently spaced apart.
Chapter 8, Connection-Based Bend Points, explores the implementation of functionality concern-
ing a primary connection’s bend points to which other, secondary connections may be attached and
remain connected. The deletion of bend points, to which other connections are attached, requires
the disconnection of secondary connection tail points prior to primary connection-based bend point
deletion. The automatic connection of connection end points to ports and bend points allows the
user to simultaneously draw connections and link diagram entities together. In addition, the deletion
of a block invokes automatic disconnection of any connected objects from the block ports.
Chapter 9, Block Dialog Windows, adds block-based dialog windows to the project that may be
used to enter block-specific parameters by double-left-clicking the block. To add dialog window-
based functionality for each block type, six key steps are pursued: (1) insertion of a dialog window
resource and all necessary controls, (2) creation of a class for the dialog window, (3) attachment
of variables to the dialog window controls, (4) the addition of functionality to the dialog window
buttons, (5) the addition of functionality to initialize the class variables, and (6) the addition of an
overriding block dialog window parameter input function to each derived block class that creates an
instance of the appropriate block dialog window.
Chapter 10, Conversion of String Input to Double Data, details all the steps required to transform
CString user input, entered through a block’s parameter-input dialog window, to double member
data. The steps involve stripping a string of unwanted leading and trailing characters, determining
the number of rows and columns of input data, and conversion of the string into a matrix or vector.
The Constant, Gain, Integrator and Transfer Function blocks all require string processing.
Chapter 11, Moving Multiple Items, introduces the simultaneous movement of multiple diagram
entities, including blocks and connection objects, through the use of a CRectTracker object and a
rectangular rubber-band tracking region. The user can circumscribe the items, followed by left-
clicking within the region and subsequently drag the entities to another location on the palette.
Chapter 12, Addition of a Tree View Control, discusses the addition of a Tree View control for
the display of a block directory tree, the leaves of which may be double-clicked to add blocks to
a system model. The key steps involve adding a dialog resource, attaching a class to the dialog,
attaching a variable to the dialog window control to be used to display Tree View items, performing
of initialization, and the adding of icons for the Tree View leaves. Finally, the docking of the Tree
dialog window to the Main frame is performed manually according to a nine-step procedure, since
the CTreeDialog class is inherited from the CDialog base class rather than the CDialogBar class.
Chapter 13, A Review of Menu and Toolbar-Based Functionality—Part 1, reviews the existing
menu and toolbar-based functionality to determine the necessary features to be added and involve
(1) the Main frame–based and Child frame–based menus and (2) the Main frame, Common Blocks,
and Common Operations toolbars.
Chapter 14, Context Menu Extension, pursues the extension of the Context menu to allow the user
to easily delete multiple grouped items using a CRectTracker object and to set block and numeri-
cal solver properties through a Set Properties entry that invokes the corresponding entity-specific
dialog window and updates user input to the appropriate class.
Chapter 15, Setting Port Properties, introduces a port properties dialog window to allow the user
to specifically configure block input and output ports via an entry on the Context menu. The number
of input ports for the Sum and Divide blocks may be adjusted via their corresponding block dialog
parameter input windows. The drawing of block ports, depending on their connection status, and
the drawing of port signs, is also added. Finally, the mechanism to delete a port via an entry on the
Context menu is implemented.
xxxiv Introduction
Chapter 16, Key-Based Item Movement, provides instructions for adding functionality for the
keyboard-based, fine-scale movement of blocks and connection-based bend points to the project.
An entry is added to the Context menu whose event-handler function sets a flag-like member vari-
able used to control four possible movement-related states: no movement, recording of the address
of the diagram entity to be moved, moving a block, and moving a bend point.
Chapter 17, Reversing Block Direction, presents a method to reverse the orientation of a block
and its ports, such that diagrams can be drawn in a more flexible manner with connections, denot-
ing the path of signal flow, being able to be drawn entering a block in both the forward and reverse
directions: this allows the correct drawing of feedback loops.
to allow the user to set the initial output signals of a block if required. Finally, a method to terminate
a simulation prematurely is added using message-related functions.
Chapter 23, Feedback-Based Signal Propagation, implements a Newton-method-based solver to
compute a system of nonlinear equations representing a model with feedback loops to determine
the output signal vectors of all diagram blocks at each time point of the simulation. Six problem
types involving feedback loops are explored to test the accuracy of the nonlinear solver: (1) a linear
problem, (2) a first order linear ordinary differential equation, (3) a second order linear ordinary
differential equation representing a mechanical/electrical problem transformed into the state-space
equations, (4) a coupled linear system, (5) the Lotka–Volterra system consisting of two coupled
first order nonlinear differential equations representing population dynamics, and (6) the nonlinear
dynamical Lorenz equations used to model the atmosphere, showing chaotic dynamical motion on
the strange attractor set.
Chapter 24, Placing an Edit Box on a Toolbar, demonstrates how a read-only Edit box control,
created using a pointer-to-CEdit variable, may be placed on a toolbar that allows the user to dynami-
cally see the simulation time, t (s), of a running experiment and then the total execution time, texe (s),
at the end of the simulation. The Edit box control is updated through a chain of function calls that
begins in the signal propagation functions of the system model class.
Chapter 25, Serialization, extends the application by implementing serialization, i.e., the writing
and reading of key class data, to and from a file, using the output (“ofstream”) and input (“ifstream”)
file stream objects, respectively. Event-handler functions are added to the Main frame–based and
Child frame–based windows to initiate the serialization process, through, e.g., the Save and Save
As (File) menu entries. The system model data, including block-based and connection-based data,
are written to and read from a simple text file that contains string identifiers organizing the output.
In addition, changes are made to the project such that the initial output signal of a multiply/divide
block, which forms a loop-repeated node in a feedback loop, can also be serialized. Finally, the
output block is extended to allow the user to save numerical data to a specified output file.
Chapter 29, Edit Menu, provides the instructions to add functionality for the Undo, Redo, Cut,
Copy, Paste, and Select All entries of the Edit menu. A clipboard object is introduced and used to
make copies of blocks and connections upon selection of the Cut and Copy entries of the Edit menu.
Upon pasting of diagram entities, the contents of the clipboard lists are merged with the correspond-
ing lists of the system model. The copying of objects is performed using a class copy constructor.
The undoing and redoing of editing actions involves creating a list of system model pointers used to
record their addresses, and saving or retrieving the addresses of system models to and from the list.
The copying of system models is performed using a class copy constructor, and the class assign-
ment operator is used to assign the system model retrieved from the list to the system model object
of the CDocument-derived class. Updating of the user interface is also implemented, indicating the
applicability of the appropriate undoing or redoing editing action.
Chapter 30, Annotations, completes the diagram editing process by adding an annotation class
and functionality that allows the user to annotate a system model through the use of an annotation
dialog invoked from the Context menu. A list of the available system fonts is displayed in the dia-
log window, and font creation is performed for the CFont member annotation class-based variable.
Annotations are displayed on the screen as individual system-model-based entities, and may be
moved, edited, and deleted: in addition, they may be shown or hidden via an entry under the Format
menu. The Cut, Copy, and Paste Edit menu-based actions require the introduction of an annota-
tion class copy constructor, and the Undo and Redo actions require changes to be made to existing
functions. Finally, the serialization of the annotation class data is performed and existing methods
augmented to cater for a system-model-based annotation list.
Chapter 31, Tools Menu, adds the functionality required to present diagnostic information,
via a dialog window, to the user concerning memory usage information. Two key structures are
used: (1) PROCESS_MEMORY_COUNTERS contains memory statistics for a process and
(2) MEMORYSTATUS retains information about the current state of the system physical and vir-
tual memory [1]. The “working set size” is an important statistic that denotes the physical memory
used by a process—here the DiagramEng application itself.
Chapter 32, Help Menu, provides instructions to display a portable document format (PDF) Help-
like document named, “UsingDiagramEng.pdf”, explaining how the DiagramEng application is to
be used for block diagram–based mathematical modeling and engineering simulation. A process is to
be created, requiring a complete command line argument, which is a concatenation of the executable
file name, “Acrobat.exe”, the file path of the PDF document, and its name “UsingDiagramEng.pdf”.
The topics covered in the Help document include menus, toolbars, examples, and forms of output.
Chapter 33, Finalizing the Project, finalizes the software development process and involves the
following: (1) disabling of non-functional elements, (2) checking the application for memory leaks
using a Debug-build configuration of the application followed by running the program with a debug-
ger and observing the Debug output window for memory leak messages, and (3) preparing the final
Open Source and executable code by including any required modules in the Release-build configu-
ration of the application.
Chapter 34, Conclusion, briefly reviews the entire software development process, summarizing
all the design and implementation steps taken to arrive at the final DiagramEng demonstration
application prototype to be used for block-diagram-based, applied mathematics-oriented engineer-
ing simulation. Mistakes made, lessons learned, and suggestions for improving the software are
provided so that the passionate developer may extend the application to solve specific engineering
problems of interest.
REFERENCE
1. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
Authors
Dr. Bud FOX received his BSc (Hons) in applied mathematics (1996) and his PhD, majoring in
electrical and electronic engineering (2000), from The University of Western Australia. He has been
working for the Institute of High Performance Computing (IHPC) (http://www.ihpc.a-star.edu.sg)
in Singapore since 2003 and is currently a senior research engineer in the Engineering Software
Group, pursuing research in interactive engineering. His other areas of interest include computa-
tional multibody dynamics, operations research, parallel computing, and software development. He
has published numerous conference and IEEE and ASME journal articles and is the coauthor of the
book titled Constrained Dynamics Computations: Models and Case Studies (ISBN: 981-02-4368-5).
Dr. FOX has been engaged in computer programming since 1990 and has used the following
languages and applications: C/C++, Visual C++, Fortran, MATLAB®, Message Passing Interface
(MPI), Open Multi-Processing (OpenMP), and X-Windows. He has written a UNIX-based software
application in the area of computational multibody dynamics titled “Multibody System,” involving
Basic Linear Algebra Subprograms (BLAS), C, Linear Algebra Package (LAPACK), Livermore
Solver of Ordinary Differential Equations with Automatic Method Switching and Root Finding
(LSODAR), and X-Windows.
Dr. FOX has contributed the following titles to the IHPC Open Source Software Downloads
website, http://software.ihpc.a-star.edu.sg/software.html:
Dr. FOX has tutored first year university applied mathematics and statistics students and third
year university computer systems engineering students. He recognizes that students and practitio-
ners require all the underlying fine detail in order to make complex applied-mathematics-oriented
software development projects work as designed. Hence, the tutorial-like nature of this book
will lead the reader through all the difficult aspects of a software project, including compilation
and run-time error–resolving notes and information on the importance and effective use of the
debugger. The book is designed to be practically helpful and is written by software developers,
for software developers.
Dr. Wenzu ZHANG received his BSc (Hons) in mechanical engineering (1985) from the Wuhan
University of Hydraulic and Electrical Engineering, his MSc in computer graphics and computer-
aided design (1989), and his PhD, majoring in intelligent manufacturing (1993), from the Huazhong
University of Science and Technology (HUST), China. He joined HUST in 1993 as a lecturer and
then as an associate professor. Thereafter, from 1997, he worked at the National University of
Singapore as a research fellow. He has been an employee of IHPC since 1999 as a senior research
engineer and then as a research scientist, and is currently in the Engineering Software Group, pur-
suing research in interactive engineering and electronic design automation (EDA). He has interests
in computer graphics, interactive technology, numerical algorithms, and software development for
electronic packaging simulation.
Dr. Zhang brings a wealth of modern Visual C++ software development experience, acquired
from more than ten large-scale, well-capitalized, software development projects conducted
xxxvii
xxxviii Authors
throughout more than ten years of service at IHPC, which were completed to increase productivity
for multinational engineering companies operating in Singapore.
Ms. May Ling TAN received her BSc in computational science and physics (1999) from the National
University of Singapore (NUS) and her MSc in high performance computation for engineered sys-
tems (2002) from the Singapore-MIT Alliance. She has worked at the Singaporean Defence Science
Organization (DSO) National Laboratories from 1999 to 2001 as a simulation software engineer
and from 2002 to 2006 as an operations analyst. She has been working at IHPC since 2007 and is
currently a research officer in the Engineering Software Group.
Ms TAN is an excitingly energetic, interactive engineering software developer with a passion for
delivering practical, easy-to-use, and intuitive Visual C++ software applications for the purpose of
increasing productivity of scientists and engineers working in industrial and research environments.
She has additional interests in computer graphics, computer games, operations research, algorithm
development, and interactive and intuitive interfaces for portable devices.
Part I
User Interaction
INTRODUCTION
There are three main parts to the book that organize the contained chapters into developmental
themes: (1) User Interaction, (2) Model Computation and Data, and (3) Refinement. Part I consists
of Chapters 1 through 17, and presents material to instruct the user on how to interactively draw a
diagram representing a system of equations that describe a mathematical model that will ultimately
be computed. Initially, the software application is designed, leading to a class structure represent-
ing the key components of the modeling environment, including the application, document, and
view classes, and those of the system model representing, blocks, ports, connections, and signals.
Then typical graphical user interface (GUI) features are added, including menus, toolbars, dialog
windows, controls, and mouse-based actions, which allow the user to construct blocks joined by
connections on the palette that may be organized as desired. The assigning of data values to dia-
gram entity attributes is facilitated through a dialog and its associated class working in conjunction
with the class of the underlying entity. The net result of Part I is that a block diagram model, with
user-specified data attributes, may be drawn, which represents a system of equations that may be
validated and then computed in Part II.
The developer should read each chapter of instructions at least once, prior to commencing the
implementation, in order to gain a broader perspective of the developmental steps and to know what
results to expect. In addition, the chapters are tutorial-like in nature and hence involve numerous
numbered lists that should be followed in an ordered and progressive, step-by-step manner to imple-
ment each application feature. Significant explanatory detail is added for each set of steps, and
all code modules are carefully explained to lead the developer through the entire developmental
procedure. The aim of the instruction is to present the material at a fast but easy-to-follow pace, and
to engage and educate the developer.
1
1 Object-Oriented
Analysis and Design
1.1 INTRODUCTION
At the initial stage of the development procedure, an object-oriented analysis and design phase is
required to aid in expressing what it is that the final software application should accomplish. This
may involve considering various resources, including Object-Oriented Analysis and Design with
Applications by Booch et al. [1] and Object Design—Roles, Responsibilities, and Collaborations
by Wirfs-Brock and McKean [2], for examples of structured and methodical approaches to initial
design and to decide upon an analysis and design approach that is suitable.
The design method that was chosen toward the current Visual C++ demonstration application devel-
opment consisted of the following steps, some of which are covered in Ref. [2]:
Once the key classes and objects are defined, a hierarchical class diagram may be drawn to
succinctly and visually present the key classes and their association relationships. A preliminary
program involving a basic set of source and header files organizing and implementing the class
structure may then be written, e.g., as a Win32 Console Application, to express the initial set of
ideas about how the program should function. This includes basic member methods, variables, and
data structures to manage the flow of information throughout the program: this is intentionally very
brief and concerns only the developer’s initial structure, rather than any additional system-provided
structure and is designed to be exploratory in nature.
However, after the coding has begun, there are numerous unforeseen changes that need to be
made to both the class structure and the manner in which data are managed within and between
classes. It was found that the production of diagrams had only limited use, but the overall planning
process involving the aforementioned steps including brief diagrams forces the developer to think
through as many stages of the design and development process as possible, before the commence-
ment of the implementation of the greater Visual C++ application.
The Visual C++ demonstration application development pursued here has been performed
under tight time constraints, and this forced the development to continually move forward
according to the initial software design. Various changes were made as the project evolved, but
in general once the initial class structure was decided upon, the project then grew upon that
foundation. In addition, as the current development is a Microsoft Visual C++® 6.0 [3] multiple
3
4 Software Application Development: A Visual C++ ® , MFC, and STL Tutorial
document interface (MDI) application, the Microsoft Foundation Classes (MFC) were used and
involved the key provided classes [4]:
The current chapter pursues a Win32 Console Application, named ControlEng, and Chapter 2
involves the setting up of the Visual C++ application, titled DiagramEng. Then, in Chapter 3, the
Visual C++ and the Win32 Console applications are merged, laying the initial class foundation, in
preparation for adding further block-diagram-based engineering objects to the project.
In addition, the software application should allow the user to model and compute a selection of arche-
typical scientific problems from the domains of mathematics, physics, and control engineering, e.g.:
The requirements specification is a general overview of what the application should allow the user
to perform, and this guideline is then explored in detail in the following sections concerning use
case narratives, scenarios, and conversations, which reveal more specific functionality and behavior
to be brought together toward the initial class design.
The DiagramEng software application should allow an engineer to model and compute basic engineer-
ing problems using a visual workflow style involving model blocks and signal flow lines, to determine
and visualize system states. The GUI including menus, toolbars, a Tree View control and a drawing
palette, should allow the user to drag blocks and connectors from various block or functional libraries
onto the palette, to completely represent the system being modeled. Typical blocks to be used in model
building may include: constant, derivative, gain, integrator, output, signal generator, sum, and the trans-
fer function block. The user should be able to create systems and subsystems through block grouping,
to form an hierarchical system model, and be able to subsequently compute the system states over a
specified time frame using toolbar-based commands.
This narrative provides a brief indication of the typical system components and user actions in
interacting with the system to model and compute a block-diagram-based engineering model. The
next step is to analyze various use case scenarios and list user actions and the corresponding system
responsibilities.
3. Simulating models
a. Simulating the current model
b. Visualizing simulation results
c. Pausing and resuming a simulation
Here, just one example will be analyzed that of building a simple linear function model using
some basic blocks. Consider the modeling of the equation of a line, y = mx + c, where the depen-
dent variable y = f(x), x is the independent variable, m is the gradient, and c, a constant, is the
y intercept. The blocks that are required to model this equation are constant, gain, linear function
generator, output, and sum. The typical block diagram that the developer may draw (with pen on
paper initially) to assist in the development process is shown in Figure 1.1: this diagram has actu-
ally been drawn with the to-be-developed, DiagramEng, Visual C++ application discussed in the
following chapters.
A very brief but logical scenario for the construction of the block diagram model of this linear
equation and its computation may be simply recorded in a numbered list. It could be more expres-
sive, but here just the essential details are required and, when presented in a listed form, makes the
conversion to a conversation, as presented in Table 1.1, easier.
1.5.1 Scenario
The scenario for the construction of a linear mathematical model and execution of its simulation:
TABLE 1.1
A Conversation Listing the Interactions between the User and the
Software, for Building the Linear Equation Model
No. User Actions System Responsibilities
1. User invokes the software application by System presents a model editor and a
double-clicking on the application icon. block library.
2. User navigates the block library for the System presents all available blocks
required blocks. through the block library.
3. User selects–moves–releases blocks from System allows clearly visible placement
the block library to the model editor of blocks on the model editor palette.
workspace palette.
4. User double clicks each block on the System spawns a dialog window, on a
palette in an attempt to assign values/ double-click event, with all necessary
parameters/properties. fields allowing user input.
5. User enters data and clicks “OK” to System checks, warns, and if OK,
update the system. assigns values to variables as
appropriate.
6. User clicks the output port of a block and System generates an arrow on
drags a mouse cursor to the input port of output-port-click and draws a
another block. connector to the cursor and then to the
input port.
7. User checks the model by clicking the System recognizes connections/signals,
“check model” button. checks for invalid loops, and builds the
equivalent mathematical equation
representation.
8. User views the actual mathematical System accepts user’s acknowledgment
equations in the system-refreshed that the equations are in the correct
“check model” dialog window and form and are to be computed.
clicks “OK”.
9. User clicks “start simulation” on the System simulates the model by
model editor’s menu bar or toolbar performing appropriate mathematical
button. operations as specified by the
mathematical blocks.
10. User double clicks an “output” block to System presents a window displaying
view the output. graphical and/or numerical results.
17. User clicks “start simulation” on the model editor’s menu bar or toolbar button.
18. System simulates the model by performing appropriate mathematical operations as speci-
fied by the mathematical blocks.
19. User double clicks an “output” block to view the output.
20. System presents a window displaying graphical and/or numerical results.
1.5.2 Conversation
The conversation shown in Table 1.1 divides the interactions between the user and the software
application into user actions and the related system responsibilities.
Table 1.1 divides the user actions and system responsibilities for clarity and is helpful in identify-
ing the nouns and verbs used to describe the interactive process in a typical usage operation: this
then leads to possible candidate classes.
TABLE 1.2
Key Nouns and Related Nouns, Extracted from the Use
Case Scenario
Nouns (Key) Nouns (Related)
Block Constant, data, Derivative, dialog window values/parameters/
properties, Gain, input/output data (e.g., numerical results),
Integrator, Linear Function Generator, Output, Sum, variables.
Block library Directory structure, icons
Connector Arrow, mouse cursor, line
Mathematical equation Display of equations in a dialog window by the system
Model System model on the canvas, i.e., the block diagram
Model editor Button, canvas, context menu, menu bar, status bar, toolbar
Port Input port, output port, port multiplicity
Signal Connections (linking) blocks
Object-Oriented Analysis and Design 9
TABLE 1.3
Key Verbs and the Nouns Affected, Extracted from the Use
Case Scenario
Verbs (Key) Nouns Affected
Assign Variables/values/parameters/properties to a block
Build Math equations (from model)
Check and Warn Model
Click Port, context menu, button of model editor menu bar,
toolbar
Double-click Block, connector
Generate/Draw Connector (from output to input port), port
Navigate Block library, model and sub-models represented by
block(s)
Present/Display Model editor, block library, block to show numerical
graphical output
Refresh Dialog window (with math equations or graphical output)
Select–move–release Block, connector, port
Simulate Model represented by connections adjoining blocks
Spawn Dialog window
Update Block, model
when the developer thinks in more general terms, more may come to mind, and some may be omitted:
there need not be a one-to-one correspondence between nouns and verbs and what appears on the cards.
This initial set of candidate classes, responsibilities, and collaborators allows the developer to
explore the relationships between the various entities of the project, and assists in organizing hier-
archical object relationships in a preliminary class design. It may not be known at the current stage
where some of the candidate classes fit in, if at all, in the whole structure, but it provides ideas as to
how a first step toward coding may be taken. It is assumed at this stage that this preliminary design
will change substantially, but it is an important step to take toward a more stable structure.
TABLE 1.4
CRC Cards for the Initial Object Analysis Listed
in Alphabetical Order
Object Responsibilities Collaborators
Block BlockPropertiesParameters SystemModel
BlockIOPort BlockLibrary
BlockDlgWnd Signal
BlockAssignParameters
GetListOfInputPorts
GetListOfOutputPorts
BlockLibrary BrowseLibraryStructure Block
DisplayDirStructure ModelEditor
DisplayDirBlocks
BlockDragDrop
ComputeEngine Compile SystemModel
Link ModelEditor
Simulate/Compute Solver
SingularityDetection
SourceCodeGeneration
Connection GetState Signal
GetFromPort
GetToPort
Update
ModelEditor Palette Block
ContextMenu SystemModel
MenuBar BlockLibrary
StatusBar ComputeEngine
ToolBar Signal
BlockBind
BlockDragDrop
NumericalMethod ComputeSysState ComputeEngine
ComputeTimeStepSize
Port GetName Block
SetName Signal
GetPosn
SetPosn
GetParameters
SetParameters
KnowsItsBlock
TransferSignal
Signal ConnectIOPorts Block
CreateBranchLine Connection
CreateSignalLine Port
DetermineTimeStep SystemModel
ModelEditor
Object-Oriented Analysis and Design 11
control is expected to be used in the actual implementation to present the block groups in folders and
their associated blocks as the individual tree leaves.
A CBlockShape class, contained within the CBlock class, is required to encapsulate the geo-
metrical information of a block, e.g., its width, height, and primitive shape: an ellipse, rectangle, or
triangle. The CBlock and CBlockShape classes would then be closely associated as far as setting up
basic block properties is concerned.
A CConnection object is the signal flow line that connects various block ports together. Hence,
the main CConnection class member variables are a reference-from port of type pointer-to-CPort,
a reference-to port of type pointer-to-CPort, and a signal of type pointer-to-CSignal that are propa-
gated along the connection itself. Hence, the CConnection and CSignal classes are closely related.
The model editor facilitates the drawing of a block diagram using the GUI. In the Win32 Console
Application that follows, the CModelEditor class is absent as no GUI is explored. Hence, it is envis-
aged that this class may not actually exist, but rather be represented by the Visual C++ MFC-based
GUI-specific classes, i.e., CDocument, CMDIChildWnd, CMDIFrameWnd, CView, and CWinApp,
all working together.
An input/output port, represented by the CPort class, is associated with the block upon which it
resides and to which connection objects are connected. Some blocks consist of only input ports, e.g.,
the Output block used for graphical display, others consist of only output ports, e.g., the Constant
block and function generation blocks, and the rest consist of both input and output ports, e.g., the
Gain and Sum blocks. The main member variables of the CPort class are its name, position, shape,
and a reference to the block to which it belongs, of type pointer-to-CBlock.
The CSignal class is used to model a signal that acts like an electrical pulse and is propagated
down a wire, where the wire may be considered as the connection object. At this stage, the only
member variable for the CSignal class is its name. However, other types of signals may be derived
from this base CSignal class, e.g., double, matrix, and vector signals, represented by CDoubleSignal,
CMatrixSignal, and CVectorSignal respectively. This class will be revisited at the system model
compilation stage, when signals are propagated down a connection from one block port to another:
for now, its details are sparse, and it may even be appropriate to make it very simple, allowing the
signal to be the more general, matrix, data structure only.
The actual block diagram model, or system model, is represented by the CSystemModel
class and, at this stage, contains a block list, a list of pointers-to-CBlock (list<CBlock*>
m_lstBlock), and a connection list, a list of pointers-to-CConnection (list<CConnection*>
m_lstConnection). In the ControlEng Win32 Console Application presented in Appendix A, the
12 Software Application Development: A Visual C++ ® , MFC, and STL Tutorial
TABLE 1.5
Class Containment Relationships Organized in Five Levels
Class Level 1 Level 2 Level 3 Level 4 Level 5
CModelEditor
CSystemModel
CBlock
CBlockShape
CPort
CBlock *p
CSystemModel *p
CConnection
CPort
CBlock *p
CSignal
CBlockLib
CBlock
CBlockShape
CPort
CBlock *p
CSystemModel *p
The classes highlighted in bold are of particular interest toward generating an initial
working program, elements shaded are pointers.
“system_model” object, of type CSystemModel, is the main object in the program, as it contains the
lists of other entities, i.e., blocks and connections. However, in the DiagramEng Visual C++ appli-
cation discussed later, the CDiagramEngDoc class, which is responsible for holding the document
object and all the necessary data structures, has as a member variable, the “m_SystemModel” object
of type CSystemModel. For now, CSystemModel objects can be considered to hold the whole block
diagram model representing the engineering problem.
Once the main classes have been decided upon, a hierarchical structure is required to organize
them in a coherent and manageable fashion to cater for extension as the project grows. Table 1.5
presents an initial class structure with five levels of containment. Two main hierarchies exist:
(1) CSystemModel and (2) CBlockLib. The former involves the structure to manage all system
model entities, which are used to build a block diagram representing the modeled system. The lat-
ter concerns the classification of block entities in a directory-like structure for ease of managing the
available drawing entities. The “*p” notation signifies a pointer-to-Class to remind the developer
that the contained class has a mechanism of referring to its parent class. Figure 1.2 is a class asso-
ciation tree diagram that reflects the structure in Table 1.5. Here, the left branch of the tree ema-
nating from CModelEditor is of particular interest toward generating an initial working program.
It is interesting to note that the block library structure shown in the right branch of Figure 1.2
is actually completely ignored for now, indicating that the data structure represented by the left
branch of the tree is more important. These types of organizational patterns are typical of a design
procedure and indicate where attention should be focused. In Chapter 3, the class association dia-
gram will be revisited and refined specifically for the conjoining of the Win32 Console Application,
ControlEng, and the Visual C++ application, DiagramEng.
Now that an initial association structure is in place, any class inheritance (class CDerived
public CBase) relationships should be listed as shown in Table 1.6: here only the CBlock,
CBlockLib, and CSignal classes appear to have derived classes. The remaining classes, at least at
this stage, do not appear to be parent classes from which other classes may be derived.
Object-Oriented Analysis and Design 13
CModelEditor
CSystemModel CBlockLib
FIGURE 1.2 Initial class association relationships for block diagram model building: the left tree is of par-
ticular interest and the right tree will be considered later.
TABLE 1.6
Derived Classes (Inheritance) for the CBlock,
CBlockLib, and CSignal Base Classes
CBlock CBlockLib CSignal
CConstantBlock CCtsBlockDir CDoubleSignal
CDerivativeBlock CMathOpsBlockDir CMatrixSignal
CDivideBlock CSinkBlockDir CVectorSignal
CGainBlock CSourceBlockDir
CIntegratorBlock CSubsystemBlockDir
CLinearFnBlock
COutputBlock
CSignalGeneratorBlock
CSubsystemBlock
CSubsystemInBlock
CSubsystemOutBlock
CSumBlock
CTransferFnBlock
TABLE 1.7
ControlEng Win32 Console Application Header Files
and Contained Classes in Order of Appearance in the Actual
Header Files
Block.h ControlEng.h Signal.h SystemModel.h
CBlockShape No class definition CSignal CSystemModel
CPort CDoubleSignal
CBlock CMatrixSignal
CConstantBlock CVectorSignal
CDerivativeBlock CConnection
CDivideBlock
CGainBlock
CIntegratorBlock
CLinearFnBlock
COutputBlock
CSignalGeneratorBlock
CSubsystemBlock
CSubsystemInBlock
CSubsystemOutBlock
CSumBlock
CTransferFnBlock
FIGURE 1.3 Console-based output concerning basic object construction and destruction for the Win32
Console Application, ControlEng.
“control engineering,” distinct from the DiagramEng Visual C++ application to be developed in
the following chapters, and compile and run the executable, to see simple constructor and destruc-
tor statements being displayed in an output console window as shown in Figure 1.3. The developer
will notice that the following objects are constructed (listed in the order of their construction):
CSystemModel, CBlockShape, CBlock, CPort, CConnection, and CSignal.
Object-Oriented Analysis and Design 15
1.10 SUMMARY
To make a start with the software development process, initial background research is necessary to
understand the problem that is to be solved and the requirements of the application to be developed.
Then, use case narratives, scenarios, and conversations can be written down to determine the typi-
cal user actions and system responsibilities. A noun and verb analysis is then used to identify the
candidate objects in the domain of the problem, and CRC cards may be used to list the possible
objects, their responsibilities, and collaborators and identify their interrelationships. Preliminary
diagrams reflecting the initial class structure may then be drawn, and basic header files can be used
to capture all the preliminary class definitions. A Win32 Console Application may be written to
simply show that objects can be constructed and destructed properly. This is done in anticipation
of porting the code into a MDI Visual C++ application later and need not be perfect, but simply
presents an initial expression of exploratory ideas allowing the developer to make insightful pre-
liminary mistakes. The developer can rest assured that the design and implementation will change
as it evolves, and it is impossible to foresee a perfectly stable structure from the outset: the key is to
simply make a start.
REFERENCES
1. Booch, G., Maksimchuk, R. A., Engle, M. W., Young, B. J., Conallen, J., and Houston, K. A., Object-
Oriented Analysis and Design with Applications, 3rd edn., Pearson Education, Boston, MA, 2007.
2. Wirfs-Brock, R. and McKean, A., Object Design: Roles, Responsibilities and Collaborations, Addison
Wesley, Boston, MA, 2003.
3. Microsoft Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
4. Chapman, D., Teach Yourself Visual C++ 6 in 21 Days, Sams Publishing, Indianapolis, IN, 1998.
5. Ogata, K., Modern Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002,
http://www.mathworks.com/
6. Getting Started with Simulink® 6, MATLAB® & Simulink®, The MathWorks, Natick, MA, 2007,
http:// www.mathworks.com/
7. Simulink® 6, Using Simulink®, MATLAB® & Simulink®, The MathWorks, Natick, MA, 2007.
8. Microsoft Corporation, www.microsoft.com/en/us/default.aspx
2 Initial Graphical User Interface
2.1 INTRODUCTION
The previous chapter discussed the design of the software application and presented typical use
case scenarios detailing user interaction and system responsibilities. The next step involves the
implementation of an initial graphical user interface to display typical Window-based application
features, such as child windows, menus with entries, and toolbars with buttons. The Microsoft
Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System [1] (integrated development
environment [IDE]), allows the developer to easily set up the interactive features of the application
and the key topics covered here are the Application Wizard, menus, icons, toolbars, dialog windows,
and attaching functionality to the menu entries and toolbar buttons.
1. Create a new “MFC AppWizard (exe)” project with the name, DiagramEng.
2. On step 1 of the AppWizard select “Multiple documents”.
3. Use the default settings for steps 2 and 3, retaining support for ActiveX Controls.
4. On step 4 of the AppWizard, click the Advanced button: use “txt” as the three-letter file
extension to denote a project text file, named, e.g., “project.txt”.
5. Choose the default settings on step 5.
6. Finally, on step 6 of the AppWizard, leave the base class as CView* and select Finish.
The AppWizard will then present a summary of the setup steps in a dialog window titled, “New
Project Information” including a summary of the classes to be created: upon clicking OK, the devel-
oper is then taken to the newly constructed project ready to start adding features to the application.
2.3 MENUS
The application, once set up, has two key menus: (1) the Main frame-based menu, with ID,
IDR_MAINFRAME, available when no child window is open and (2) the Child frame-based menu,
with sample ID, IDR_CHILDMENU, intended for the child window only and relates to documents
that the user works on. Tables 2.1 and 2.2 show the Main frame and Child frame menus and their
menu entries. Table 2.3 shows the Child frame-based menu augmented with the intended entries
to be added shown in italics, for the DiagramEng application being built. The new menus for the
Child window frame to be inserted to the right of the View menu are as follows: Model, Simulation,
Format, and Tools. Table 2.4 shows the specific menu entry objects, properties, and settings. The
developer will notice that the ID, ID_EDIT_SELECTALL, is used, rather than, e.g., ID_EDIT_
SELECT_ALL: this is the case since the latter is a system default ID, and using it in conjunction
* Later in the project a Scrolling View will be implemented using the CScrollView base class: but leave CView as the base
class for now, as specific conversion instructions will be provided later.
17
18 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 2.1
Main Frame–Based Menus Provided
by the Development Environment
File View Help
New Toolbar About DiagramEng
Open Status bar
Print Setup …
Recent File
Exit
TABLE 2.2
Child Frame–Based Menus Provided by the Development
Environment
File Edit View Window Help
New Undo Toolbar New Window About DiagramEng
Open … Cut Statusbar Cascade
Close Copy Tile
Save Paste Arrange Icons
Save As … (previous files)
Print
Print Preview
Print Setup
Recent File
Exit
TABLE 2.3
DiagramEng Child Frame–Based Menu Items to be Added by the Developer, Shown in Italics
File Edit View Model Simulation Format Tools Window Help
New Cut Toolbar Build Model Start Show Diagnostic New About
Open Copy Status Build Stop Annotations Info. Window DiagramEng
Close Paste Bar Subsystem Numerical Cascade Using
Save Delete Auto Fit Solver Tile DiagramEng
Save Select All Diagram Arrange
As Add Zoom In Icons
Print Multiple Zoom Close All
Print Blocks Out Documents
Setup (previous
Recent files)
File
Exit
Initial Graphical User Interface 19
TABLE 2.4
Child Frame–Based Menu Entry Objects, IDs, Captions, Prompts
(Status Bar and Tooltips)a, and Settings
Object Property Setting
Edit/Delete ID ID_EDIT_DELETE
Caption &Delete
Prompts Delete the selection\nDelete
Edit/Select All ID ID_EDIT_SELECTALL
Caption Select &All
Prompts Selection of all content\nSelect All
Edit/Add Multiple Blocks ID ID_EDIT_ADD_MULTI_BLOCKS
Caption Add &Multiple Blocks
Prompts Add multiple blocks\nAdd Multiple Blocks
View/Auto Fit Diagram ID ID_VIEW_AUTO_FIT_DIAGRAM
Caption Auto Fit Diagram
Prompts Auto fit diagram to view\nAuto Fit Diagram
View/Zoom In ID ID_VIEW_ZOOM_IN
Caption Zoom &In
Prompts Zoom in to detail\nZoom In
View/Zoom Out ID ID_VIEW_ZOOM_OUT
Caption Zoom &Out
Prompts Zoom out of detail\nZoom Out
Model/Build Model ID ID_MODEL_BUILD
Caption &Build Model
Prompts Build model\nBuild Model
Model/Build Subsystem ID ID_MODEL_BUILD_SUBSYS
Caption Build &Subsystem
Prompts Build model subsystem\nBuild Subsystem
Simulation/Start ID ID_SIM_START
Caption S&tart
Prompts Start simulation\nStart Simulation
Simulation/Stop ID ID_SIM_STOP
Caption Sto&p
Prompts Stop simulation\nStop Simulation
Simulation/Numerical Solver ID ID_SIM_NUM_SOLVER
Caption &Numerical Solver
Prompts Numerical solver settings\nNumerical Solver
Settings
Format/Show Annotations ID ID_FORMAT_SHOW_ANNOTATIONS
Caption &Show Annotations
Prompts Show annotations\nShow Annotations
Tools/Diagnostic Info. ID ID_TOOLS_DIAGNOSTIC_INFO
Caption Dia&gnostic Info.
Prompts Diagnostic information\nDiagnostic Information
(continued)
20 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
a The developer should be aware that the Prompts property field involves both the status bar and
tooltips text, where the two are separated by the “\n” characters (Tables 2.4 through 2.7).
with user-defined functionality would override its default-context-based operation. Hence, insert the
additional menu items in the project according to Tables 2.3 and 2.4.
1. Go to the resources tab of the workspace pane and select the Icon menu.
2. Double-click IDR_MAINFRAME to view both the 16 × 16 (pixel) and 32 × 32 (pixel) icons.
3. Redesign the icons to reflect the nature of the application, as shown, e.g., in Figure 2.1b and d.
(a) (b)
(c) (d)
FIGURE 2.1 Application icons (units denote pixel dimensions): (a) MFC (32 × 32), (b) DiagramEng (32 × 32),
(c) MFC (16 × 16), and (d) DiagramEng (16 × 16).
Initial Graphical User Interface 21
FIGURE 2.2 The DiagramEng application icon shown upon the About DiagramEng dialog window.
Then, upon selecting, About DiagramEng, under the Help menu, the dialog window shown in
Figure 2.2 will appear, displaying the application icon.
2.5 TOOLBARS
Toolbars are bars usually found beneath the drop-down menus that contain button-based graphical
icons representing application operations that are associated with a frame, e.g., the Main frame or
Child frame, and relate to the underlying document. An example of the Main frame-based Standard
toolbar is shown in Figure 2.3, with the default icons: New, Open, Save, Cut, Copy, Paste, Print,
and About. The enabled buttons (New, Open, and About) relate to Main frame-based functionality,
and the disabled items become enabled when a Child window is present, since these relate to the
underlying child document-based content.
1. Right-click on the toolbar folder and select Insert Toolbar from the pop-up menu.
2. Draw icons on the toolbar to represent the functionality, which is to be provided by the
corresponding menus: see Table 2.5 and Figure 2.4 as a guide.
3. Invoke the properties dialog and enter the ID and prompts of the corresponding menu entry
whose associated functionality is to be triggered by the toolbar button.
4. Finally, right-click on the toolbar and change the toolbar ID via the properties dialog to
a descriptive name: e.g., IDR_TB_COMMON_OPS, denoting the ID of the Common
Operations toolbar resource.
The developer will notice that the final toolbar button named Track Multiple Items is not associated
with a menu entry: the details for this will be described later when moving multiple items on the
palette, in Chapter 11.
FIGURE 2.3 Main frame-based Standard toolbar provided by default, with buttons: New, Open, Save, Cut,
Copy, Paste, Print, and About.
22 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 2.5
Common Operations Toolbar (IDR_TB_COMMON_OPS) Buttons:
IDs, Prompts (Status Bar and Tooltips), Settings, and Icons
Button and
Corresponding Menu
Entry Property Setting Icon
Edit/Select All ID ID_EDIT_SELECTALL
Prompts Selection of all contentnSelect All
Edit/Add Multiple ID ID_EDIT_ADD_MULTI_BLOCKS
Blocks Prompts Add multiple blocksnAdd Multiple
Blocks
View/Auto Fit Diagram ID ID_VIEW_AUTO_FIT_DIAGRAM
Prompts Auto fit diagram to viewnAuto Fit
Diagram
Model/Build ID ID_MODEL_BUILD
Prompts Build modelnBuild Model
Simulation/Start ID ID_SIM_START
Prompts Start simulationnStart Simulation
Simulation/Stop ID ID_SIM_STOP
Prompts Stop simulationnStop Simulation
Simulation/Numerical ID ID_SIM_NUM_SOLVER
Solver Prompts Numerical solver settingsnNumerical
Solver Settings
Format/Show ID ID_FORMAT_SHOW_
Annotations Prompts ANNOTATIONS
Show annotationsnShow Annotations
(No menu)/Track ID ID_INIT_TRACK_MULTIPLE_
Multiple Items Prompts ITEMS
Initiate tracking of multiple
itemsnTrack Multiple Items
FIGURE 2.4 The Common Operations and Common Blocks toolbars docked to the left side of the Main
frame window.
1. Right-click on the toolbar folder and select Insert Toolbar from the pop-up menu.
2. Right-click on the toolbar and change the toolbar ID via the properties dialog to a descrip-
tive name: e.g., IDR_TB_COMMON_BLOCKS, denoting the ID of the Common Blocks
toolbar resource.
Initial Graphical User Interface 23
TABLE 2.6
Common Blocks Toolbar (IDR_TB_COMMON_OPS) Buttons: IDs, Prompts (Status Bar
and Tooltips), Settings, and Icons
Object group and block Property Setting Icon
Continuous/Derivative Block ID ID_BLOCK_CTS_DERIVATIVE
Prompts Continuous blocks: Derivative BlocknDerivative Block
Continuous/Integrator Block ID ID_BLOCK_CTS_INTEGRATOR
Prompts Continuous blocks: Integrator BlocknIntegrator Block
Continuous/TransferFn Block ID ID_BLOCK_CTS_TRANSFERFN
Prompts Continuous blocks: TransferFn BlocknTransferFn Block
Math Operations/Divide Block ID ID_BLOCK_MATHOPS_DIVIDE
Prompts Math operations blocks: Divide BlocknDivide Block
Math Operations/Gain Block ID ID_BLOCK_MATHOPS_GAIN
Prompts Math operations blocks: Gain BlocknGain Block
Math Operations/Sum Block ID ID_BLOCK_MATHOPS_SUM
Prompts Math operations blocks: Sum BlocknSum Block
Sink/Output Block ID ID_BLOCK_SINK_OUTPUT
Prompts Sink blocks: Ouput BlocknOutput Block
Sink/SubsystemOut Block ID ID_BLOCK_SUBSYS_SUBSYSOUT
Prompts Sink blocks: SubsystemOut BlocknOutput Block
Source/Constant Block ID ID_BLOCK_SOURCE_CONST
Prompts Source blocks: Constant BlocknConstant Block
Source/LinearFn Block ID ID_BLOCK_SOURCE_LINEARFN
Prompts Source blocks: LinearFn BlocknLinearFn Block
Source/SignalGenerator Block ID ID_BLOCK_SOURCE_SIGNALGEN
Prompts Source blocks: SignalGenerator BlocknSignalGenerator Block
Source/SubsystemIn Block ID ID_BLOCK_SUBSYS_SUBSYSIN
Prompts Source blocks: SubsystemIn BlocknSubsystemIn Block
Subsystem/Subsystem Block ID ID_BLOCK_SUBSYS_SUBSYS
Prompts Subsystem blocks: Subsystem BlocknSubsystem Block
3. Draw icons on the toolbar to represent the block-based functionality as shown in Table 2.6,
which will also be made available by clicking on the corresponding blocks of a to-be-
added block library dialog window and later a block library Tree View directory.
4. Invoke the properties dialog and enter the ID and prompts for the buttons representing the
blocks.
5. Place separators on the toolbar where desired and check to see that they appear upon appli-
cation execution.
1. Add a protected member variable of type CToolBar to the CMainFrame class to hold the
Common Operations toolbar, named “m_wndTBCommonOps”.
2. Add a protected member variable of type CToolBar, to the CMainFrame class to hold the
Common Blocks toolbar with name, “m_wndTBCommonBlocks”.
24 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
3. Add code to the OnCreate() function of the CMainFrame class to add the toolbars and
attach them to the frame, as shown in bold in the following: the developer will notice
the “DiagramEng (start)” and “DiagramEng (end)” comments surrounding DiagramEng-
specific code introduced by the current instructions.
{
TRACE0(“Failed to create status bar\n”);
return −1; // fail to create
}
// TODO: Delete these three lines if you don’t want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
// DiagramEng (start)
// -- ENABLE DOCKING OF TOOLBARS
// Enable docking for the Common Ops toolbar (IDR_TB_COMMON_OPS)
// NOTE: enables the toolbar for docking with the frame wnd.
m_wndTBCommonOps.EnableDocking(CBRS_ALIGN_ANY);
// Enable docking for the Common Blocks toolbar (IDR_TB_COMMON_BLOCKS)
// NOTE: enables the toolbar for docking with the frame wnd.
m_wndTBCommonBlocks.EnableDocking(CBRS_ALIGN_ANY);
// DiagramEng (end)
EnableDocking(CBRS_ALIGN_ANY); // called for the frame wnd.
DockControlBar(&m_wndToolBar); // frame wnd fn passed & of toolbar
var. physically docks the toolbar to the frame wnd.
// DiagramEng (start)
// -- DOCK TOOLBARS
// Dock the Common Ops toolbar.
DockControlBar(&m_wndTBCommonOps);
// Dock the Common Blocks toolbar.
DockControlBar(&m_wndTBCommonBlocks);
// DiagramEng (end)
return 0;
}
Rebuild the application by selecting Rebuild All from the Build menu to obtain the toolbars docked
to the left hand side of the window as shown in Figure 2.4: if no functionality has been associated
with the toolbar buttons (as will actually be the case since this is to be added later), then they will
appear in a disabled state.
TABLE 2.7
Menu Entry, Object ID, Caption, Prompts (Status Bar and Tooltips),
and Settings
Object Property Setting
View/Common Ops. Toolbar ID ID_VIEW_TB_COMMON_OPS
Caption &Common Ops. Toolbar
Prompts Common operations toolbar\nCommon Ops.
Toolbar
View/Common Blocks Toolbar ID ID_VIEW_TB_COMMON_BLOCKS
Caption &Common Blocks Toolbar
Prompts Common blocks toolbar\nCommon Blocks Toolbar
Add an event-handler function, to the CMainFrame class, for the COMMAND event message of
the ID_VIEW_TB_COMMON_OPS menu entry. Edit the code as shown in the following to show
or hide the toolbar (see also Listing 12.3 of Ref. [2]).
void CMainFrame::OnViewTbCommonOps()
{
// TODO: Add your command handler code here
// DiagramEng (start)
BOOL bShow;
// Check state of Common Ops. toolbar
bShow = ( (m_wndTBCommonOps.GetStyle() & WS_VISIBLE) != 0);
// Switch state
// NOTE: & of toolbar, bool show/hide toolbar, delay showing toolbar
(FALSE => no delay)
ShowControlBar(&m_wndTBCommonOps, !bShow, FALSE);
// Recalculate Layout
RecalcLayout();
// DiagramEng (end)
}
Add an event-handler function to the CMainFrame class, for the UPDATE_COMMAND_UI event
message of the ID_VIEW_TB_COMMON_BLOCKS menu entry. Edit the code as shown in the
following, to set the check mark (✓) next to the View menu entry denoting the visibility of the cor-
responding toolbar.
Initial Graphical User Interface 27
// DiagramEng (start)
// DiagramEng (end)
}
Add an event-handler function to the CMainFrame class, for the COMMAND event message of the
ID_VIEW_TB_COMMON_BLOCKS menu entry. Edit the code as shown in the following to show
or hide the toolbar.
void CMainFrame::OnViewTbCommonBlocks()
{
// TODO: Add your command handler code here
// DiagramEng (start)
BOOL bShow;
// Recalculate Layout
RecalcLayout();
// DiagramEng (end)
}
Now the Common Ops. Toolbar and Common Blocks Toolbar entries under the View menu may be
selected to show or hide the corresponding toolbar.
1. Navigate to the Menu folder in the Workspace pane and double-click the
IDR_CHILDMENU to make the menu visible.
2. Right-click the editor area (right hand side) and select the ClassWizard from the pop-up menu.
28 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 2.8
Menu Entry Objects, IDs, Classes, and COMMAND Event-Handler Functions
Object ID Class COMMAND Event-Handler
Edit/Delete ID_EDIT_DELETE CDiagramEngDoc OnEditDelete()
Edit/Select All ID_EDIT_SELECTALL CDiagramEngDoc OnEditSelectAll()
Edit/Add Multiple Blocks ID_EDIT_ADD_MULTI_ CDiagramEngDoc OnEditAddMultipleBlocks()
BLOCKS
View/Auto Fit Diagram ID_VIEW_AUTO_FIT_ CDiagramEngView OnViewAutoFitDiagram()
DIAGRAM
View/Zoom In ID_VIEW_ZOOM_IN CDiagramEngView OnViewZoomIn()
View/Zoom Out ID_VIEW_ZOOM_OUT CDiagramEngView OnViewZoomOut()
Model/Build Model ID_MODEL_BUILD CDiagramEngDoc OnModelBuild()
Model/Build Subsystem ID_MODEL_BUILD_SUBSYS CDiagramEngDoc OnModelBuildSubsystem()
Simulation/Start ID_SIM_START CDiagramEngDoc OnSimStart()
Simulation/Stop ID_SIM_STOP CDiagramEngDoc OnSimStop()
Simulation/Numerical Solver ID_SIM_NUM_SOLVER CDiagramEngDoc OnSimNumericalSolver()
Format/Show Annotations ID_FORMAT_SHOW_ CDiagramEngDoc OnFormatShowAnnotations()
ANNOTATIONS
Tools/Diagnostic Info. ID_TOOLS_DIAGNOSTIC_ CDiagramEngDoc OnToolsDiagnosticInfo()
INFO
Window/Close All ID_WND_CLOSE_ALL_ CDiagramEngDoc OnWndCloseAllDocs()
Documents DOCS
Help/Using DiagramEng ID_HELP_USING_DIAENG CDiagramEngDoc OnHelpUsingDiagramEng()
void CDiagramEngDoc::OnEditDelete()
{
// TODO: Add your command handler code here
// DiagramEng (start)
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg: 0 indicates the
app’s default help context will be used
Initial Graphical User Interface 29
// Display a msg.
sMsg.Format(“\n CDiagramEngDoc::OnEditDelete()\n\n”);
AfxMessageBox(sMsg, nType, nIDhelp);
// DiagramEng (end)
}
The developer may prefer to simply use a one-line statement, rather than declare three local variables,
for the message-box-based display as follows: AfxMessageBox(“\n message content\n”,
MB_OK, 0), where the arguments, “message content”, “MB_OK”, and “0”, represent the message
to be displayed, the style of the message box and the help context ID of the message, respectively [3].
2.5.6 Add Event-Handler Functions for the Common Blocks Toolbar Buttons
Event-handler functions may now be added for the buttons on the Common Blocks toolbar that, at
this stage, display a simple message: more functional detail will be added to these later.
1. Right-click the editor area (right hand side) and select the ClassWizard from the pop-up
menu.
2. Select as the class name, CDiagramEngDoc, for document-based action, since all of the
event-handler functions relate to inserting blocks in the system model held by the underly-
ing document.
3. Find the IDs of the buttons for which functionality should be added for the COMMAND
event message, e.g., ID_BLOCK_CTS_DERIVATIVE, etc.
4. Select the ID, highlight COMMAND, and click Add Function to add the event-handler
function.
TABLE 2.9
Common Blocks Toolbar Buttons, IDs, Classes, and the Corresponding Functions
Toolbar Buttons and Their COMMAND
Block Groups ID Class Event-Handler
Continuous/Derivative Block ID_BLOCK_CTS_DERIVATIVE CDiagramEngDoc OnBlockCtsDerivative()
Continuous/Integrator Block ID_BLOCK_CTS_INTEGRATOR CDiagramEngDoc OnBlockCtsIntegrator()
Continuous/TransferFn Block ID_BLOCK_CTS_TRANSFERFN CDiagramEngDoc OnBlockCtsTransferfn()
Math Operations/Divide Block ID_BLOCK_MATHOPS_DIVIDE CDiagramEngDoc OnBlockMathopsDivide()
Math Operations/Gain Block ID_BLOCK_MATHOPS_GAIN CDiagramEngDoc OnBlockMathopsGain()
Math Operations/Sum Block ID_BLOCK_MATHOPS_SUM CDiagramEngDoc OnBlockMathopsSum()
Sink/Output Block ID_BLOCK_SINK_OUTPUT CDiagramEngDoc OnBlockSinkOutput()
Sink/SubsystemOut Block ID_BLOCK_SUBSYS_ (see Subsys) (see Subsys)
SUBSYSOUT
Source/Constant Block ID_BLOCK_SOURCE_CONST CDiagramEngDoc OnBlockSourceConst()
Source/LinearFn Block ID_BLOCK_SOURCE_ CDiagramEngDoc OnBlockSourceLinearfn()
LINEARFN
Source/SignalGenerator Block ID_BLOCK_SOURCE_ CDiagramEngDoc OnBlockSourceSignalgen()
SIGNALGEN
Source/SubsystemIn Block ID_BLOCK_SUBSYS_SUBSYSIN (see Subsys) (see Subsys)
Subsystem/Subsystem ID_BLOCK_SUBSYS_SUBSYS CDiagramEngDoc OnBlockSubsysSubsys()
Subsystem/SubsystemIn ID_BLOCK_SUBSYS_ CDiagramEngDoc OnBlockSubsysSubsysin()
SUBSYSIN
Subsystem/SubsystemOut ID_BLOCK_SUBSYS_ CDiagramEngDoc OnBlockSubsysSubsysout()
SUBSYSOUT
30 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
5. Omit adding the event-handler function for the UPDATE_COMMAND_UI event mes-
sage, since no updating of the UI is necessary here.
6. Add event-handlers for all the IDs in Table 2.9, showing the toolbar button objects, IDs,
classes, and the corresponding functions.
7. Place a simple AfxMessageBox() call in each event-handler function to check its opera-
tion on menu entry and toolbar button selection. For example, the Derivative block, a type
of continuous mathematical operation block, has a COMMAND event-handler function
named “OnBlockCtsDerivative()” as shown in the following. All other functions
will have a similar appearance.
void CDiagramEngDoc::OnBlockCtsDerivative()
{
// TODO: Add your command handler code here
// DiagramEng (start)
AfxMessageBox(“\n CDiagramEngDoc::OnBlockCtsDerivative()\n\n”,
MB_OK, 0);
// DiagramEng (end)
}
5. The title “Dialog” will appear (when the executable runs) in the actual title portion of the
pane. Right-click on the top of the Dialog window to change the title-properties of the
dialog window to reflect the purpose of the window: e.g., “Block Library Dialog”.
6. Add maximize and minimize buttons and scroll bars to the Block Library Dialog window
if desired: select the dialog window, right click on the dialog to access the dialog proper-
ties, select the styles tab, and activate the maximize and minimize buttons.
Initial Graphical User Interface 31
FIGURE 2.5 Block Library Dialog window showing check boxes for all model block types.
7. Specify the control tab order: choose Layout/Tab Order and click the numbers in the
desired order and then choose Layout/Tab Order to return to the layout editor.
8. Check mnemonics: right-click on the dialog window and click check mnemonics (leave
duplicates if they represent the same dialog control).
The Block Library Dialog window is shown with all group boxes and their relevant check boxes.
Two check boxes are repeated: i.e., Subsystem Out (Sink Blocks) and Subsystem In (Source Blocks).
The primary versions of these are found in the Subsystem Blocks group box. However, the IDs for the
repeated check boxes will link to the same event-handler function if enabled. These check boxes may
be disabled altogether, as shown, in which case they would serve as an implicit comment to the user
that they may be considered as Sink/Source blocks, but are found in the Subsystem Blocks group box.
TABLE 2.10
Dialog Objects, Properties, and Settings for the BlockLibDlg Dialog
Window (IDD_BLOCKLIBDLG): All Objects Have Unique IDs
Object Property Setting
Group Box ID ID_BLOCKLIBDLG_GB_CTS
Caption Continuous Blocks
Check Box ID ID_BLOCKLIBDLG_CB_DERIVATIVE
Caption &Derivative Block
Check Box ID ID_BLOCKLIBDLG_CB_INTEGRATOR
Caption Inte&grator Block
Check Box ID ID_BLOCKLIBDLG_CB_TRANSFERFN
Caption &TransferFn Block
Group Box ID ID_BLOCKLIBDLG_GB_MATHOPS
Caption Math Operations Blocks
Check Box ID ID_BLOCKLIBDLG_CB_DIVIDE
Caption Di&vide Block
Check Box ID ID_BLOCKLIBDLG_CB_GAIN
Caption G&ain Block
Check Box ID ID_BLOCKLIBDLG_CB_SUM
Caption &Sum Block
Group Box ID ID_BLOCKLIBDLG_GB_SINK
Caption Sink Blocks
Check Box ID ID_BLOCKLIBDLG_CB_OUTPUT
Caption &Output Block
Check Box ID ID_BLOCKLIBDLG_CB_SUBSYSOUT2
Caption SubsystemO&ut Block
Group Box ID ID_BLOCKLIBDLG_GB_SOURCE
Caption Source Blocks
Check Box ID ID_BLOCKLIBDLG_CB_CONST
Caption &Constant Block
Check Box ID ID_BLOCKLIBDLG_CB_LINEARFN
Caption &LinearFn Block
Check Box ID ID_BLOCKLIBDLG_CB_SIGNALGEN
Caption Sig&nalGenerator Block
Check Box ID ID_BLOCKLIBDLG_CB_SUBSYSIN2
Caption Subsystem&In Block
Group Box ID ID_BLOCKLIBDLG_GB_SUBSYS
Caption Subsystem Blocks
Check Box ID ID_BLOCKLIBDLG_CB_SUBSYS
Caption Su&bsystem Block
Check Box ID ID_BLOCKLIBDLG_CB_SUBSYSIN
Caption Subsystem&In Block
Check Box ID ID_BLOCKLIBDLG_CB_SUBSYSOUT
Caption SubsystemO&ut Block
Button ID ID_BLOCKLIBDLG_BTN_ADDALLBLOCKS
Caption Add All Blocks
Button ID ID_BLOCKLIBDLG_BTN_UNCHECKALLBLOCKS
Caption Uncheck All Blocks
Button ID IDCANCEL (default provided setting)
Caption Close
Initial Graphical User Interface 33
void CDiagramEngDoc::OnEditAddMultipleBlocks()
{
// TODO: Add your command handler code here
// DiagramEng (start)
// Local var declaration
int display_item = 1; // used to display msg box or dlg wnd.
// Display a msg.
if(display_item == 0)
{
AfxMessageBox(“\n CDiagramEngDoc::OnEditAddMultipleBlocks()\n”,
MB_OK, 0);
}
else if(display_item == 1)
{
CBlockLibDlg oDlg; // create a dlg obj. of class CBlockLibDlg :
public CDialog
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
//AfxMessageBox(“\n CDiagramEngDoc::OnEditAddMultipleBlocks()\n,”
MB_OK, 0);
}
}
// DiagramEng (end)
}
TABLE 2.11
Dialog Window Control IDs, Variable Names, Categories, and Types,
for the IDD_BLOCKLIBDLG (Dialog Window) Resource
Control Variable Name Category Type
ID_BLOCKLIBDLG_CB_DERIVATIVE m_bChkBoxDerivativeBlock Value BOOL
ID_BLOCKLIBDLG_CB_INTEGRATOR m_bChkBoxIntegratorBlock Value BOOL
ID_BLOCKLIBDLG_CB_TRANSFERFN m_bChkBoxTransferFnBlock Value BOOL
ID_BLOCKLIBDLG_CB_DIVIDE m_bChkBoxDivideBlock Value BOOL
ID_BLOCKLIBDLG_CB_GAIN m_bChkBoxGainBlock Value BOOL
ID_BLOCKLIBDLG_CB_SUM m_bChkBoxSumBlock Value BOOL
ID_BLOCKLIBDLG_CB_OUTPUT m_bChkBoxOutputBlock Value BOOL
ID_BLOCKLIBDLG_CB_SUBSYSOUT2 m_bChkBoxSubsysOut2Block Value BOOL
ID_BLOCKLIBDLG_CB_CONST m_bChkBoxConstBlock Value BOOL
ID_BLOCKLIBDLG_CB_LINEARFN m_bChkBoxLinearFnBlock Value BOOL
ID_BLOCKLIBDLG_CB_SIGNALGEN m_bChkBoxSignalGenBlock Value BOOL
ID_BLOCKLIBDLG_CB_SUBSYSIN2 m_bChkBoxSubsysIn2Block Value BOOL
ID_BLOCKLIBDLG_CB_SUBSYS m_bChkBoxSubsysBlock Value BOOL
ID_BLOCKLIBDLG_CB_SUBSYSIN m_bChkBoxSubsysInBlock Value BOOL
ID_BLOCKLIBDLG_CB_SUBSYSOUT m_bChkBoxSubsysOutBlock Value BOOL
TABLE 2.12
Block Library Dialog Resource Button Objects, IDs, Classes, and Event-Handler
Functions
Button ID Class COMMAND Event-Handler
Add All Blocks ID_BLOCKLIBDLG_BTN_ CBlockLibDlg OnBlocklibdlgBtnAddallblocks()
ADDALLBLOCKS
Uncheck All Blocks ID_BLOCKLIBDLG_BTN_ CBlockLibDlg OnBlocklibdlgBtnUncheckallblocks()
UNCHECKALLBLOCKS
Cancel IDCANCEL (default) CBlockLibDlg OnCancel()
Initial Graphical User Interface 35
the dialog control item, from within the OnInitDialog() function of the CBlockLibDlg class.
The latter is performed as follows:
1. Invoke the ClassWizard and click on the “Message Maps” tab: the “Class Name” is
CBlockLibDlg.
2. Click on the CBlockLibDlg under “Object IDs”.
3. Select the WM_INITDIALOG message under the “Messages” section.
4. Click Add Function to add the initialization function named
CBlockLibDlg::OnInitDialog(): the initialization cannot be done within the con-
structor, since the constructor can only handle variable initialization, not dialog function-
call-based initialization.
5. Edit the CBlockLibDlg::OnInitDialog() function and disable the dupli-
cated check boxes with IDs, “ID_BLOCKLIBDLG_CB_SUBSYSIN2” and
“ID_BLOCKLIBDLG_CB_SUBSYSOUT2”.
BOOL CBlockLibDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
// DiagramEng (start)
// Disable the duplicate check boxes
GetDlgItem(ID_BLOCKLIBDLG_CB_SUBSYSOUT2)->EnableWindow(FALSE); // False
disables the control
GetDlgItem(ID_BLOCKLIBDLG_CB_SUBSYSIN2)->EnableWindow(FALSE); // False
disables the control
// DiagramEng (end)
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
1. Declare the enumerated type variable at the top of the “DiagramEngDoc.h” header file,
after the “#endif // _MSC_VER > 1000” line, as follows: enum EBlockType {eConstBlock,
eDerivativeBlock, eDivideBlock, eGainBlock, eIntegratorBlock, eLinearFnBlock, eOut-
putBlock, eSignalGenBlock, eSubsysBlock, eSubsysInBlock, eSubsysOutBlock, eSum-
Block, eTransferFnBlock}
2. Include the “DiagramEngDoc.h” header file at the top of the “BlockLibDlg.cpp” source file,
since the CBlockLibDlg-based member function uses the enumerated type, “EBlockType”.
36 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CBlockLibDlg::OnBlocklibdlgBtnAddallblocks()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
EBlockType e_block_type; // enum block type defined in
DiagramEngDoc.cpp
CDiagramEngDoc *pDoc = NULL; // declare pDoc to be a ptr to
CDiagramEngDoc.
// -- Display a msg. box
sMsg.Format(“\n CBlockLibDlg::OnBlocklibdlgBtnAddallblocks()\n\n”);
AfxMessageBox(sMsg, nType, nIDhelp);
// -- Update the dlg. wnd. control vals. to the vars.
UpdateData(TRUE);
pDoc = GetDocumentGlobalFn();
// Invoke a general block creation fn passing an enum EBlockType var.
if(m_bChkBoxConstBlock)
{
e_block_type = eConstBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxDerivativeBlock)
{
e_block_type = eDerivativeBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxDivideBlock)
{
Initial Graphical User Interface 37
e_block_type = eDivideBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxGainBlock)
{
e_block_type = eGainBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxIntegratorBlock)
{
e_block_type = eIntegratorBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxLinearFnBlock)
{
e_block_type = eLinearFnBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxOutputBlock)
{
e_block_type = eOutputBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxSignalGenBlock)
{
e_block_type = eSignalGenBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxSubsysBlock)
{
e_block_type = eSubsysBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxSubsysInBlock)
{
e_block_type = eSubsysInBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxSubsysOutBlock)
{
e_block_type = eSubsysOutBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxSumBlock)
{
e_block_type = eSumBlock;
pDoc->ConstructBlock(e_block_type);
}
if(m_bChkBoxTransferFnBlock)
{
e_block_type = eTransferFnBlock;
pDoc->ConstructBlock(e_block_type);
}
// DiagramEng (end)
}
38 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The developer will have noticed the call to ConstructBlock() called upon the pointer-to-
CDiagramEngDoc, “pDoc”. Hence, add a public member function to the CDiagramEngDoc
class with the prototype, int CDiagramEngDoc::ConstructBlock(EBlockType
e_block_type), and edit it as shown in the following to use a switch statement to call the appro-
priate block construction function. At present, no blocks are actually constructed (the calls are com-
mented out), but this will completed in a following chapter.
int CDiagramEngDoc::ConstructBlock(EBlockType e_block_type)
{
// General ConstructBlock() fn which calls specific block
construction fns.
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// -- Display a msg. box
sMsg.Format(“\n CDiagramEngDoc::ConstructBlock()\n\n”);
AfxMessageBox(sMsg, nType, nIDhelp);
// -- Filter out the approp. partic. block consturction fn. to be called
switch(e_block_type)
{
case eConstBlock:
//ConstructConstantBlock();
break;
case eDerivativeBlock:
//ConstructDerivativeBlock();
break;
case eDivideBlock:
//ConstructDivideBlock();
break;
case eGainBlock:
//ConstructGainBlock();
break;
case eIntegratorBlock:
//ConstructIntegratorBlock();
break;
case eLinearFnBlock:
//ConstructLinearFnBlock();
break;
case eOutputBlock:
//ConstructOutputBlock();
break;
case eSignalGenBlock:
//ConstructSignalGeneratorBlock();
break;
case eSubsysBlock:
//ConstructSubsystemBlock();
break;
case eSubsysInBlock:
//ConstructSubsystemInBlock();
break;
Initial Graphical User Interface 39
case eSubsysOutBlock:
//ConstructSubsystemOutBlock();
break;
case eSumBlock:
//ConstructSumBlock();
break;
case eTransferFnBlock:
//ConstructTransferFnBlock();
break;
default:
// Inform user of switch default
sMsg.Format(“\n CDiagramEngDoc::ConstructBlock(): switch default\
n\n”);
AfxMessageBox(sMsg, nType, nIDhelp);
break;
}
return 0; // return int for error checking
}
CDiagramEngDoc* GetDocumentGlobalFn(void)
{
// Get a pointer to the main frame window
CMDIFrameWnd *pFrame = (CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;
// Get the active MDI child window.
CMDIChildWnd *pChild = (CMDIChildWnd*)pFrame->GetActiveFrame();
// Get the active document associated with the active child window
CDiagramEngDoc *pDoc = (CDiagramEngDoc*)pChild->GetActiveDocument();
// Return the pDoc
if(pDoc != NULL)
{
return pDoc;
}
// If pDoc != NULL then it would have already been returned, o/wise
return NULL.
return NULL;
}
40 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The GetDocumentGlobalFn() returns the address of the active document that contains the
complete data structure for a system model, including its list of blocks and connections. The first
line gets a pointer to the Main frame window and assigns it to the pointer-to-CMDIFrameWnd,
“pFrame”. Then, using this pointer, a pointer to the active MDI Child window, “pChild”, is obtained
via the call to GetActiveFrame(). The active document associated with the active Child win-
dow is then retrieved using the “pChild” pointer and the call to GetActiveDocument() and
is assigned to the pointer-to-CDiagramEngDoc, “pDoc”. Finally, if “pDoc” is not NULL, it is
returned; otherwise, NULL is explicitly returned.
Note that instead of using “AfxGetApp()->m_pMainWnd” given earlier, the alternative
“theApp.m_pMainWnd” could have been used. However, in this case, the application, “theApp”,
would need to be accessed from within the “DiagramEngDoc.cpp” file, although it is declared in the
“DiagramEng.cpp” file. Hence, if this is preferred (and here it is not implemented), place the state-
ment “extern CDiagramEngApp theApp”; at the top of the “DiagramEngDoc.cpp” file, as follows,
to allow its use within the GetDocumentGlobalFn().
// theApp needs to be accessed from within the DiagramEngDoc.cpp file,
although it’s declared in the DiagramEng.cpp file.
// It is used in the fn GetDocumentGlobalFn() below.
extern CDiagramEngApp theApp;
This global GetDocumentGlobalFn() will be used extensively throughout the project, since
on numerous occasions, CDiagramEngDoc functions are required to be called from outside of
CDiagramEngView functions, within which CDiagramEngView::GetDocument()could oth-
erwise be called.
m_bChkBoxSumBlock = FALSE;
m_bChkBoxTransferFnBlock = FALSE;
UpdateData(FALSE); // FALSE => var vals updated to the dlg. wnd.
// DiagramEng (end)
}
2.8 SUMMARY
The Application Wizard [1] is used to set up an MFC-based MDI Windows application, named
DiagramEng. Menus are added to the GUI to allow selection of operations relating to the under-
lying document. A Common Operations toolbar and a Common Blocks toolbar are set up under
the default toolbar, and event-handler functions are associated with the toolbar buttons and cor-
responding menu entries. A Block Library Dialog window is constructed in preparation for add-
ing a selection of blocks to the model, and variables are attached to the controls of the dialog
window. A ConstructBlock() function is added that will ultimately invoke the constructor
of the appropriate derived-block class to construct a model block: this function is called from
OnBlocklibdlgBtnAddallblocks() and the event-handler functions for the Common
Blocks toolbar buttons. An important global function named GetDocumentGlobalFn()
is introduced, which allows a pointer-to-CDiagramEngDoc to be obtained from a scope out-
side the View class: the View class has its own GetDocument() function to retrieve a
pointer-to-CDiagramEngDoc.
REFERENCES
1. The Microsoft Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional
Edition, Microsoft Corporation, 1998.
2. Chapman, D., Teach Yourself Visual C++ 6 in 21 Days, Sams Publishing, Indianapolis, IN, 1998.
3. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
3 Constructing Blocks
3.1 INTRODUCTION
The ControlEng Win32 Console Application developed in Chapter 1 involved brief class declara-
tions and definitions for the classes shown in the following class hierarchical diagram (Figure 3.1).
The key association relationships between the objects/classes are (1) “Generalization,” model-
ing the “is-a” relationship, used for class derivation, shown by the arrowhead ( ) notation and
(2) “Composition,” modeling the “has-a” or “containment” relationship, illustrated by the diamond-
head (⬧) notation.
The developer may rebuild and rerun the ControlEng application (see Appendix A for the code)
to see the construction of the following objects (in order): CSystemModel, CBlockShape, CBlock,
CPort, CConnection, and CSignal. Now the ControlEng Win32 Console Application project mate-
rial needs to be merged with the DiagramEng Visual C++ [1] (initial GUI) application built in
Chapter 2.
Step 2: Now copy the initial DiagramEng Visual C++ application from the following sample location
into a new directory, indicating that the GUI will have the aforementioned class structure defined in
the ControlEng Win32 Console Application, inserted within it, e.g.,
Step 3: Copy the source and header files, “Block.h/cpp”, “SystemModel.h/cpp”, and “Signal.h/cpp”,
from the ControlEng Win32 Console Application, located, e.g., in the following directory
to the location of the new DiagramEng Visual C++ application with the current GUI and the to-be-
added classes
43
44 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
CDiagramEngDoc
CSystemModel
CBlock CConnection
CConstantBlock COutputBlock
CDerivativeBlock CSignalGeneratorBlock
CDivideBlock CSubsystemBlock
CGainBlock CSubsystemInBlock
CIntegratorBlock CSubsystemOutBlock
CLinearFnBlock CSumBlock
CTransferFnBlock
FIGURE 3.1 Class hierarchical diagram showing association relationships: generalization ( ) and
composition (⬧).
Note that here the source and header files, “ControlEng.cpp” and “ControlEng.h”, respectively, are
omitted, since they constitute the main line of the ControlEng Win32 Console Application, and this
main line will be assumed by a main() function that calls a WinMain() function but is not visible
via the IDE [1] in the DiagramEng Visual C++ application.
Step 4: Add the copied-and-pasted files (“Block.h/cpp”, “SystemModel.h/cpp”, and “Signal.h/cpp”)
to the DiagramEng project by first selecting the menu entry, Project/Add To Project/Files, and then
navigating to the current project directory, found, e.g., at the following location
and select the (recently copied) files to be added, i.e., “Block.h/cpp”, “SystemModel.h/cpp”,
and “Signal.h/cpp”. As a result, the following files and their contained classes exist in the new
DiagramEng Visual C++ project as shown in Table 3.1.
Now compile the code without declaring a CSystemModel variable to check the compilation.
The compiler yields the following error:
The compiler looks for the directive: #include “stdafx.h”. If #include “stdafx.h” is not present in
the newly added source files, add this directive at the very top of the file before all other #include
statements. See the following code excerpt as an example. The reason for this compilation error is
Constructing Blocks 45
TABLE 3.1
Combined DiagramEng Source Files (.cpp), Header Files (.h), and Classes,
Augmented with the ControlEng Files and Classes
DiagramEng Files DiagramEng Classes
BlockLibDlg.cpp/h CBlockLibDlg
ChildFrm.cpp/h CChildFrame
DiagramEng.cpp/h CDiagramEngApp
DiagramEng.rc NA
DiagramEngDoc.cpp/h CDiagramEngDoc
DiagramEngView.cpp/h CDiagramEngView
MainFrm.cpp/h CMainFrame
Resource.h NA
StdAfx.cpp/h NA
ControlEng Files ControlEng Classes
Block.cpp/h CBlockShape, CPort, CBlock, CConstantBlock, CDerivativeBlock,
CGainBlock, CIntegratorBlock, CLinearFnBlock, COutputBlock,
CSignalGeneratorBlock, CSubsystemBlock, CSubsystemInBlock,
CSubsystemOutBlock, CSumBlock, CTransferFnBlock
Signal.cpp/h CSignal, CDoubleSignal, CMatrixSignal, CVectorSignal, CConnection
SystemModel.cpp/h CSystemModel
due to the fact that the inclusion of the precompiled header is set through the Application Wizard
when creating a new Visual C++ application. However, Application Framework, or “Afx” support,
was not present for the Win32 Console Application, ControlEng.
// Title: Block.cpp
// Purpose: Contains all Block related code.
Step 5: Add a private member variable to the CDiagramEngDoc class, specifying the type as
CSystemModel and the name as “m_SystemModel”. Compile the code again. Error messages
concerning <vector> and <list> appear. These may be resolved by inserting #include <vector>,
#include <list>, and “using namespace std” (to avoid having to use the prefix “std::”) in the source
and header files where appropriate, as shown in the code excerpts (“Block.cpp”) and (“Block.h”).
// Title: Block.h
#ifndef BLOCK_H
#define BLOCK_H // inclusion guard
#include <vector> // rqd. for vector (below)
using namespace std;
…
#endif
46 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Step 6: Now to display the object construction statements that were printed through the use of
“cout” statements in the Console window of the ControlEng Win32 Console Application,
AfxMessageBox() calls may be used as shown in the following code excerpt in the CSystemModel
constructor.
// Message Box
CString sMsg; // main msg string
CString sMsgTemp; // temp msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
sMsgTemp.Format(“\n CSystemModel::CSystemModel()\n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
Alternatively, if a one-line statement is preferred, without the need to declare additional vari-
ables, then the following may be used instead: AfxMessageBox(“\n CSystemModel::
CSystemModel()\n,” MB_OK, 0).
Now when the code is built and run, a message box appears indicating that a CSystemModel
object has been constructed, including all its contained member objects, i.e., a list of CBlock point-
ers and a list of CConnection pointers. CBlock contains a CBlockShape object and a vector of
CPort pointers, and CConnection contains pointers to CPort and CSignal, as is reflected by the class
association diagram shown in Figure 3.1. Once the construction is shown to work as expected, the
AfxMessageBox() calls may be commented out.
the CString member variable, “m_strModelName”. Finally, a new function can be added with
the prototype, void SetModelName(CString name), as shown, to set the member variable.
CSystemModel::CSystemModel(void)
{
m_strModelName = “model_name”;
t_start = 0.0; // sim start time
t_stop = 10.0;
}
CString CSystemModel::GetModelName(void) const
{
return m_strModelName;
}
void CSystemModel::SetModelName(CString name)
{
m_strModelName = name;
}
Later in the development, more changes will be made to the CSystemModel class and new member
variables added when required.
void CDiagramEngDoc::ConstructConstantBlock()
{
// NOTE
// CConstantBlock : public CBlock
// CBlock contains a m_BlockShape obj. of type CBlockShape
// Hence rqe. constructor args for CConstantBlock and CBlock.
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructConstantBlock()\n”,
MB_OK, 0);
// Constructor args. for CBlock
block_posn.x = 1;
block_posn.y = 1;
// AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_rectangle;
// Construct a new constant block
//CBlock *p_block = new CConstantBlock();
CBlock *p_block = new CConstantBlock(&(GetSystemModel() ), block_posn,
e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
1. Remove the “double block_position [2]” member declaration in the CBlock class, and
replace this with “CPoint m_ptBlockPosition” by adding a new private member variable.
2. Remove the “double geometry [3]” member variable statement in the CBlockShape
class, and replace this with two new member variables: “double m_dBlockWidth” and
“EBlockDirection m_eBlockDirection”. Modify the “Block.h” header file and change
“EDirection” to “EBlockDirection” for clarity, as shown in the following.
Constructing Blocks 49
// Title: Block.h
#ifndef BLOCK_H
#define BLOCK_H // inclusion guard
// Block
class CSystemModel; // predefining CSystemModel as it’s rqd. by
CBlock, additionally CSystemModel rqes. CBlock
class CBlock
{
public:
CBlock(void);
~CBlock(void);
50 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Accessor methods
char* GetName(void);
private:
CPoint m_ptBlockPosition; // ordered pair denoting
(x,y) location of CofM of block
CString m_strBlockName; // block name
CBlockShape m_BlockShape; // member obj of type
CBlockShape
vector<CPort*> m_vecInputPorts; // vector of ptrs to CPort
vector<CPort*> m_vecOutputPorts; // vector of ptrs to CPort
CSystemModel *m_pParentSystemModel; // ptr to parent system model
CSystemModel *m_pSubSystemModel; // ptr = NULL => no contained
system/sub model, ptr != NULL => system model
};
…
#endif
CBlockShape::CBlockShape(void)
{
m_eBlockDirection = e_right; // default direc is to the right.
m_dBlockWidth = (GetDocumentGlobalFn()->GetDeltaLength() )*2.0;
m_dBlockHeight = m_dBlockWidth;
}
The developer will have noticed the call to set the block shape type in the CBlock() constructor
given earlier. Hence, add a public member function to the CBlockShape class with the prototype, void
CBlockShape::SetBlockShapeType(EBlockShape e_blk_shape), and edit it as shown.
void CBlockShape::SetBlockShapeType(EBlockShape e_blk_shape)
{
m_eBlockShape = e_blk_shape;
}
Now add a new constructor function to the CConstantBlock class with prototype,
CConstantBlock::CConstantBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape), and member initialization:
CBlock(pParentSystemModel, blk_posn, e_blk_shape), as shown in the follow-
ing code (the constructor has an empty body for now).
CConstantBlock::CConstantBlock(CSystemModel *pParentSystemModel, CPoint
blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
// empty for now
}
For completeness, add a public constant accessor method to the CBlock class to retrieve a
pointer to the parent system model to which the block belongs, with the following prototype:
CSystemModel *CBlock::GetParentSystemModel(void) const. Edit the code as
shown in the following. This function is not used until later in the project, but it is useful to keep in
mind that a block can “know” its parent system model.
CSystemModel* CBlock::GetParentSystemModel() const
{
// Return the parent system model that contains the list of blocks
and connections
return m_pParentSystemModel;
}
Finally, a new Constant block can be constructed from within the CDiagramEngDoc::
ConstructConstantBlock() function as introduced earlier, by the line:
CBlock *p_block = new CConstantBlock(&(GetSystemModel() ), block_posn,
e_block_shape);
CSystemModel &CDiagramEngDoc::GetSystemModel()
{
return m_SystemModel;
}
Now add a public member function to the CSystemModel class to get the contained block list, with
prototype, list<CBlock*> &GetBlockList(void), and edit the code as shown in the follow-
ing. The ClassWizard indicates that “Template declarations or definitions cannot be added”, so use
“int &” as the return type, then manually change the source and header files to have the following
definition: list<CBlock*> &GetBlockList(void).
list<CBlock*>& CSystemModel::GetBlockList()
{
return m_lstBlock;
}
Now these two functions may be successively called to retrieve the system model and then the mod-
el’s block list, before adding the block to the end of the list, as shown in bold in the following code.
void CDiagramEngDoc::ConstructConstantBlock()
{
// NOTE
// CConstantBlock : public CBlock
// CBlock contains a m_BlockShape obj. of type CBlockShape
// Hence rqe. constructor args for CConstantBlock and CBlock.
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructConstantBlock()\n”,
MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_rectangle;
// Construct a new constant block
//CBlock *p_block = new CConstantBlock();
CBlock *p_block = new CConstantBlock(&(GetSystemModel() ), block_posn,
e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
Build and run the project: there will likely be a bug on program termination. Hence, do the following:
• Make the CBlock() destructor virtual, i.e., write virtual ∼CBlock(); in the “Block.h”
file, as upon destruction, a derived block object, e.g., CConstantBlock is being destroyed,
not a CBlock object.
• Check the original ∼CBlock() destructor, and make sure that what is being
destroyed was actually constructed in the newly created CBlock(CSystemModel
*pParentSystemModel, CPoint blk_ posn, EBlockShape e_blk_shape)
constructor taking three arguments. If something was omitted, then add all things that
were constructed in the original CBlock() constructor to the new constructor taking three
arguments (this was indicated earlier and should not actually be a problem here).
Constructing Blocks 53
Now upon running the code, there should be no erroneous operation, but the object construction
needs to be checked to be certain.
void CDiagramEngDoc::CheckSystemModel()
{
int n_in_ports = 0;
int n_out_ports = 0;
sMsgTemp.Format(“\n CDiagramEngDoc::CheckSystemModel()\n”);
sMsg += sMsgTemp;
// Get the vectors of input and output ports and determine their
sizes.
//n_in_ports = (*it_blk)->GetVectorOfInputPorts().size();
//n_out_ports = (*it_blk)->GetVectorOfOutputPorts().size();
The developer will notice the call to GetBlockName() given earlier. Hence, change the CBlock
member function with prototype, char *CBlock::GetName(void), to the constant mem-
ber function with prototype, CString CBlock::GetBlockName() const, to make it clear
that it is the block name, “m_strBlockName” of type CString, that is being retrieved. For com-
pleteness, add a new public member function to the CBlock class with the prototype, void
CBlock::SetBlockName(CString blk_name), and set the CBlock member variable
“m_strBlockName” to the incoming argument as follows.
54 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Now upon repeated selection of the Constant Block button on the Common Blocks toolbar, addi-
tional CConstantBlock objects will be added to the block list. This will then be evident through the
message box output displayed by the CheckSystemModel() function.
In addition, make the function call to CDiagramEngDoc::CheckSystemModel() from
within the CDiagramEngDoc::ConstructBlock(), directly after the switch statement: this
reduces the need to place the CheckSystemModel() function in every individual block con-
struction function.
Finally, add the statement SetModifiedFlag(TRUE) just after the switch statement in the
CDiagramEngDoc::ConstructBlock() function: this causes the document to be marked as
having been modified and prompts the user to save the document upon closing or exiting the appli-
cation. This will be discussed in more depth in Chapter 25.
Now add a new public member function to the CSystemModel class with the prototype: void
CSystemModel::DrawSystemModel(CDC *pDC). Edit the function as shown in the follow-
ing to iterate through the block list calling DrawBlock() on the pointer-to-CBlock.
void CSystemModel::DrawSystemModel(CDC *pDC)
{
// Iterates through the SystemModel lists drawing the stored entities.
// -- Draw Blocks
list<CBlock*>::iterator it_blk; // local iterator
list<CBlock*> blk_list; // local block list
blk_list = GetBlockList();
// Iterate through the list
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
(*it_blk)->DrawBlock(pDC); // DrawBlock() called on the
ptr-to-CBlock
}
// -- Draw Connections (to do)
/*list<CConnection*>::iterator it_con; // local iterator
list<CConnection*> con_list; // local connection list
con_list = GetConnectionList();
// Iterate through the list
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
(*it_con)->DrawConnection(pDC);
}
*/
}
Now, add a new public member function to the CBlock class with the prototype: void
CBlock::DrawBlock(CDC *pDC). This function will be made virtual and needs to be overrid-
den later, but for now edit it as follows. Its contents will be commented out later.
void CBlock::DrawBlock(CDC *pDC)
{
// WARNING! THIS FN WILL BE MADE VIRTUAL AND IS TO BE OVERRIDDEN BY
ALL DERIVED CLASSES THAT DRAW OBJS.
EBlockShape e_blk_shape; // enum block shape: ellipse, rectangle,
triangle.
//AfxMessageBox(“\n CBlock::DrawBlock()\n”, MB_OK, 0);
// Get the EBlockShape of the current block via the member obj.
CBlockShape m_BlockShape;
e_blk_shape = m_BlockShape.GetBlockShapeType();
switch(e_blk_shape)
{
case e_ellipse:
DrawEllipseBlock(pDC);
break;
case e_rectangle:
DrawRectangleBlock(pDC);
break;
56 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
case e_triangle:
DrawTriangleBlock(pDC);
break;
default:
// Inform user of switch default
DrawRectangleBlock(pDC);
//AfxMessageBox(“\n CBlock::DrawBlock(), switch(e_blk_shape)
default case\n”, MB_OK, 0);
break;
}// end switch
}
The developer will notice the call to GetBlockShapeType() in the DrawBlock() function
given earlier. Hence, add a public constant member function to the CBlockShape class with the
prototype: EBlockShape CBlockShape::GetBlockShapeType(void) const. Edit this
function as follows.
The developer will notice that the block width is obtained by a call to GetBlockWidth(). Hence,
add a public constant member function to the CBlockShape class, with the prototype: double
CBlockShape::GetBlockWidth(void) const. Edit the function as shown in the following.
Constructing Blocks 57
The developer will also see that a pen is created using the statement: CPen lpen(PS_SOLID,
m_iPenWidth, m_iPenColor). Because “m_iPenWidth” and “m_iPenColor” are new inte-
ger variables, they must be added (privately) to the CBlock class. Default initialization of the
“m_iPenWidth” and “m_iPenColor” member variables can be done in the CBlock constructor,
i.e., “m_iPenWidth = 1”, and “m_iPenColor = RGB(0,0,255)”.
In addition, the following constant functions need to be added to the CBlock class, int
GetPenWidth() const and int GetPenColor() const, to return the “m_iPenWidth” and
“m_iPenColor” variable values, respectively. Edit these functions as follows.
Note that as “m_iPenWidth” and “m_iPenColor” are both member variables of CBlock, then
GetPenWidth() and GetPenColor() do not need to be called prior to the pen creation state-
ment given earlier, since DrawEllipseBlock() is itself a member function of the CBlock class:
these accessor functions are added for possible future use.
Note also that the new pen is set as the drawing object as follows, i.e., CPen *pOldPen =
pDC->SelectObject(&lpen). The Microsoft Developer Network (MSDN) Library Help
feature indicates that the return type of the SelectObject() function is a “pointer to the
object being replaced” [2]: i.e., “pOldPen” holds a pointer to the previous pen. Later, after all
drawing is completed using the new “lpen” object, the old pen is restored through the call:
pDC->SelectObject(pOldPen).
B.x = m_ptBlockPosition.x − h;
B.y = m_ptBlockPosition.y;
C.x = m_ptBlockPosition.x + d;
C.y = m_ptBlockPosition.y − length*0.5;
break;
case e_up: // vertex B pts. up
A.x = m_ptBlockPosition.x − length*0.5;
A.y = m_ptBlockPosition.y + d;
B.x = m_ptBlockPosition.x;
B.y = m_ptBlockPosition.y − h;
C.x = m_ptBlockPosition.x + length*0.5;
C.y = m_ptBlockPosition.y + d;
break;
case e_right: // vertex B pts. to the right
A.x = m_ptBlockPosition.x − d;
A.y = m_ptBlockPosition.y − length*0.5;
B.x = m_ptBlockPosition.x + h;
B.y = m_ptBlockPosition.y;
C.x = m_ptBlockPosition.x − d;
C.y = m_ptBlockPosition.y + length*0.5;
break;
case e_down: // vertex B pts. down
A.x = m_ptBlockPosition.x + length*0.5;
A.y = m_ptBlockPosition.y − d;
B.x = m_ptBlockPosition.x;
B.y = m_ptBlockPosition.y + h;
C.x = m_ptBlockPosition.x − length*0.5;
C.y = m_ptBlockPosition.y − d;
break;
default: // assign same values as for e_right assuming default
direc. vertex B pointing to the right.
A.x = m_ptBlockPosition.x − d;
A.y = m_ptBlockPosition.y − length*0.5;
B.x = m_ptBlockPosition.x + h;
B.y = m_ptBlockPosition.y;
C.x = m_ptBlockPosition.x − d;
C.y = m_ptBlockPosition.y + length*0.5;
break;
}// end switch
// Create a pen
CPen lpen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
// Draw the triangle (A to B to C)
pDC->MoveTo(A);
pDC->LineTo(B);
pDC->LineTo(C);
pDC->LineTo(A);
// Reset the prev. pen
pDC->SelectObject(pOldPen);
}
Figure 3.2 shows an equilateral triangle representing the Gain block, with vertices, A, B, and C,
where vertex B points in the direction in which the block is oriented: here to the right. The center
60 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
π
6
l/2 h
O π6
l B
d h block direction
FIGURE 3.2 A triangle block of side length, l, where h, is the hypotenuse of a subtriangle, with opposite side
length d, and base length, l/2.
of the block, denoted O, defining the block’s position, “m_ptBlockPosition”, is considered to be that
where the three side bisectors intersect. The triangle has a side length, l, where h is the hypotenuse
of a subtriangle, with opposite side length d, and base length l/2. The “length”, “d”, and “h” argu-
ments in the code correspond to l, d, and h in Figure 3.2, respectively, where l is the default block
width, d = (l/2)tan(π/6) and h = l/2 cos(π/6).
The developer will also notice the call to GetBlockDirection() to get the direction in which the
triangle block is facing. Hence, add a new public constant member function to the CBlockShape class,
with prototype: EBlockDirection CBlockShape::GetBlockDirection(void) const.
Edit this function as follows.
CBlockShape::CBlockShape(void)
{
m_eBlockDirection = e_right; // default direc is to the right.
Constructing Blocks 61
CDiagramEngDoc::CDiagramEngDoc()
{
// TODO: add one-time construction code here
// DiagramEng (start)
m_dDeltaLength = 50.0;
// DiagramEng (end)
}
Now add a public constant accessor method to the CDiagramEngDoc class with the prototype,
double CDiagramEngDoc::GetDeltaLength(void) const, and edit it as shown in the
following to simply return the member variable.
// Assign block_posn
n_blocks = GetSystemModel().GetBlockList().size();
This AssignBlockPosition() function call should be made in all block construction func-
tions that require it in a similar manner to its use in the ConstructConstantBlock()
function.
void CDiagramEngDoc::ConstructGainBlock()
{
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructGainBlock()\n”, MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_triangle;
// Construct a new constant block
//CBlock *p_block = new CGainBlock();
CBlock *p_block = new CGainBlock(&(GetSystemModel() ), block_posn,
e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
void CDiagramEngDoc::ConstructSumBlock()
{
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructSumBlock()\n”, MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_ellipse;
// Construct a new constant block
//CBlock *p_block = new CSumBlock();
CBlock *p_block = new CSumBlock(&(GetSystemModel() ), block_posn,
e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
Now, ConstructSumBlock() needs to be called from within the switch statement inside the
function CDiagramEngDoc::ConstructBlock().
Finally, add a new constructor function to the CSumBlock class with prototype, CSumBlock::
CSumBlock(CSystemModel *pParentSystemModel, CPoint blk_posn, EBlockShape
e_blk_shape), and member initialization: CBlock(pParentSystemModel, blk_posn,
e_blk_shape). Add the statement, SetBlockName(“sum_block”), within the constructor
as shown in the following.
“At last a confirmation!” Upon building and running the application and clicking on a Constant,
Gain, or Sum block toolbar button, the blocks appear on the palette as shown in Figure 3.3. Blocks
are automatically assigned positions to fill up to 10 places per row: across the rows and down the
64 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 3.3 Constant, Gain, and Sum blocks (repeated) placed in automatically assigned positions.
columns as if they were in a matrix. Here, the Constant, Gain, and Sum blocks are repeatedly
pressed one after the other to show the automatic display action.
TABLE 3.2
Blocks to Be Constructed and Drawn
Block Name Class Name Construction Status
Constant CConstantBlock
Derivative CDerivativeBlock ☒
Divide CDivideBlock ☒
Gain CGainBlock
Integrator CIntegratorBlock ☒
Linear Function CLinearFnBlock ☒
Output COutputBlock ☒
Signal Generator CSignalGeneratorBlock ☒
Subsystem CSubsystemBlock ☒
Subsystem In CSubsystemInBlock ☒
Subsystem Out CSubsystemOutBlock ☒
Sum CSumBlock
The symbols and ☒ denote whether a block has been constructed thus
far or not, respectively.
Constructing Blocks 65
void CDiagramEngDoc::ConstructDerivativeBlock()
{
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructDerivativeBlock()\n”,
MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_rectangle;
// Construct a new derivative block
//CBlock *p_block = new CDerivativeBlock();
CBlock *p_block = new CDerivativeBlock(&(GetSystemModel() ),
block_posn, e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
Add a new constructor function to the CDerivativeBlock class, with prototype, CDerivative
Block::CDerivativeBlock(CSystemModel *pParentSystemModel, CPoint blk_posn,
EBlockShape e_blk_shape), and member initialization: CBlock(pParentSystemModel,
blk_posn, e_blk_shape). Edit the function as shown.
CDerivativeBlock::CDerivativeBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
SetBlockName(“derivative_block”);
}
void CDiagramEngDoc::ConstructDivideBlock()
{
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructDivideBlock()\n”,
MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_rectangle;
// Construct a new divide block
//CBlock *p_block = new CDivideBlock();
CBlock *p_block = new CDivideBlock(&(GetSystemModel() ), block_posn,
e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
66 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Add a new constructor function to the CDivideBlock class with prototype, CDivideBlock::
CDivideBlock(CSystemModel *pParentSystemModel, CPoint blk_posn, EBlock
Shape e_blk_shape), and member initialization, CBlock(pParentSystemModel,
blk_posn, e_blk_shape). Edit the function as shown.
CDivideBlock::CDivideBlock(CSystemModel *pParentSystemModel, CPoint
blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
SetBlockName(“divide_block”);
}
void CDiagramEngDoc::ConstructIntegratorBlock()
{
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructIntegratorBlock()\n”,
MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_rectangle;
// Construct a new integrator block
//CBlock *p_block = new CIntegratorBlock();
CBlock *p_block = new CIntegratorBlock(&(GetSystemModel() ),
block_posn, e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
void CDiagramEngDoc::ConstructLinearFnBlock()
{
CPoint block_posn;
Constructing Blocks 67
//AfxMessageBox(“\n CDiagramEngDoc::ConstructLinearFnBlock()\n”,
MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_rectangle;
// Construct a new linear fn block
//CBlock *p_block = new CLinearFnBlock();
CBlock *p_block = new CLinearFnBlock(&(GetSystemModel() ), block_posn,
e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
Add a new constructor function to the CLinearFnBlock class with prototype, CLinearFnBlock::
CLinearFnBlock(CSystemModel *pParentSystemModel, CPoint blk_ posn,
EBlockShape e_blk _shape), and member initialization: CBlock(pParentSystemModel,
blk_ posn, e_blk_shape). Edit the function as shown.
CLinearFnBlock::CLinearFnBlock(CSystemModel *pParentSystemModel, CPoint
blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
SetBlockName(“linear_fn_block”);
}
Add a new constructor function to the COutputBlock class with prototype, COutputBlock::
COutputBlock(CSystemModel *pParentSystemModel, CPoint blk_ posn,
EBlockShape e_blk_shape), and member initialization: CBlock(pParentSystemModel,
blk_posn, e_blk_shape). Edit the constructor as shown.
68 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CDiagramEngDoc::ConstructSignalGeneratorBlock()
{
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructSignalGeneratorBlock()\n”,
MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_rectangle;
// Construct a new signal generator block
//CBlock *p_block = new CSignalGeneratorBlock();
CBlock *p_block = new CSignalGeneratorBlock(&(GetSystemModel() ),
block_posn, e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
CSignalGeneratorBlock::CSignalGeneratorBlock(CSystemModel
*pParentSystemModel, CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
SetBlockName(“signal_generator_block”);
}
void CDiagramEngDoc::ConstructSubsystemBlock()
{
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructSubsystemBlock()\n”,
MB_OK, 0);
Constructing Blocks 69
void CDiagramEngDoc::ConstructSubsystemInBlock()
{
CPoint block_posn;
//AfxMessageBox(“\n CDiagramEngDoc::ConstructSubsystemInBlock()\n”,
MB_OK, 0);
// Constructor args. for CBlock
AssignBlockPosition(block_posn);
// Constructor args. for CBlockShape
EBlockShape e_block_shape = e_rectangle;
// Construct a new subsystem in block
//CBlock *p_block = new CSubsystemInBlock();
CBlock *p_block = new CSubsystemInBlock(&(GetSystemModel() ),
block_posn, e_block_shape);
// Add the new block to the system model block list.
GetSystemModel().GetBlockList().push_back(p_block);
}
CSubsystemInBlock::CSubsystemInBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
SetBlockName(“subsystem_in_block”);
}
Add a new constructor function to the CSubsystemOutBlock class with prototype, CSubsys
temOutBlock::CSubsystemOutBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape), and member initialization:
CBlock(pParentSystemModel, blk_posn, e_blk_shape). Edit the constructor as
shown in the following.
CSubsystemOutBlock::CSubsystemOutBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
SetBlockName(“subsystem_out_block”);
}
Finally, all the particular block-construction methods may be called from within CDiagramEngDoc::
ConstructBlock() function as shown in the following.
int CDiagramEngDoc::ConstructBlock(EBlockType e_block_type)
{
// General ConstructBlock() fn which calls specific block
construction fns.
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
switch(e_block_type)
{
case eConstBlock:
ConstructConstantBlock();
break;
case eDerivativeBlock:
ConstructDerivativeBlock();
break;
case eDivideBlock:
ConstructDivideBlock();
break;
case eGainBlock:
ConstructGainBlock();
break;
72 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
case eIntegratorBlock:
ConstructIntegratorBlock();
break;
case eLinearFnBlock:
ConstructLinearFnBlock();
break;
case eOutputBlock:
ConstructOutputBlock();
break;
case eSignalGenBlock:
ConstructSignalGeneratorBlock();
break;
case eSubsysBlock:
ConstructSubsystemBlock();
break;
case eSubsysInBlock:
ConstructSubsystemInBlock();
break;
case eSubsysOutBlock:
ConstructSubsystemOutBlock();
break;
case eSumBlock:
ConstructSumBlock();
break;
case eTransferFnBlock:
ConstructTransferFnBlock();
break;
default:
// Inform user of switch default
sMsg.Format(“\n CDiagramEngDoc::ConstructBlock(): switch
default\n\n”);
AfxMessageBox(sMsg, nType, nIDhelp);
break;
}
SetModifiedFlag(TRUE); // prompt the user to save after adding a
block to the system model
CheckSystemModel(); // check the syste model
UpdateAllViews(NULL); // this fn calls OnDraw to redraw the
system model.
return 0; // return int for error checking
}
FIGURE 3.4 Primitive drawing functionality with the CheckSystemModel()-based message box dialog
to check for correct block construction.
1. Make the existing CBlock member function DrawBlock(CDC *pDC) virtual, i.e., use
the prototype, virtual void CBlock::DrawBlock(CDC *pDC), in the CBlock
class declaration.
2. Finally, comment out the internals of the existing CBlock::DrawBlock() function so
that it performs no action for now, as shown in the following code.
3.6.1.1 CConstantBlock::DrawBlock()
Add a public virtual member function to the CConstantBlock class with prototype, virtual void
CConstantBlock::DrawBlock(CDC *pDC), and edit it as follows.
{
pDC->LineTo(pt_array[i]);
}
// Reset the prev. pen
pDC->SelectObject(pOldPen);
}
The developer will have noticed the use of the functions GetBlockPosition() and
GetBlockShape() to obtain the block position and shape, respectively. Hence, add a new public
constant member function to the CBlock() class, as shown in the following, with the prototype,
CPoint CBlock::GetBlockPosition(void) const, that returns a CPoint object.
In addition, add a new public member function to the CBlock() class with the prototype,
CBlockShape &CBlock::GetBlockShape(void), that returns a reference-to-a-CBlock-
Shape object, i.e., the contained, “m_BlockShape”, object: this is then used in the call to obtain the
block width. Edit the function as shown.
CBlockShape &CBlock::GetBlockShape()
{
return m_BlockShape; // return a ref-to-a-CBlockShape obj.
m_BlockShape (contained obj. of CBlock)
}
Finally, test that a CConstantBlock object can in fact be drawn by running the program and insert-
ing only one Constant block in the system model’s block list: this consists of a rectangle block,
drawn from within the function, CBlock::DrawRectangleBlock(), and the letter “C,” drawn
from within the function, CConstantBlock::DrawBlock().
3.6.1.2 CGainBlock::DrawBlock()
Add a public virtual member function to the CGainBlock class with the prototype, virtual void
CGainBlock::DrawBlock(CDC *pDC), and edit it as follows.
Test that a CGainBlock can in fact be drawn by running the program and inserting only one Gain
block in the system model’s block list: this consists of a triangle block, drawn from within the func-
tion, CBlock::DrawTriangleBlock(), and the letter “K,” denoting the gain constant, drawn
from within the function, CGainBlock::DrawBlock().
3.6.1.3 CSumBlock::DrawBlock()
Add a public virtual member function to the CSumBlock class with the prototype, virtual void
CSumBlock::DrawBlock(CDC *pDC), and edit it as follows.
Test that a CSumBlock can be drawn by running the program and adding a Sum block to the
system model’s block list: this consists of an ellipse block, drawn from within the function,
CBlock::DrawEllipseBlock(), and the character “+,” denoting summation, drawn from
within the function, CSumBlock::DrawBlock().
3.6.1.4 CDerivativeBlock::DrawBlock()
Add a public virtual member function to the CDerivativeBlock class, with the prototype, virtual
void CDerivativeBlock::DrawBlock(CDC *pDC), and edit it as follows.
3.6.1.5 CDivideBlock::DrawBlock()
Add a public virtual member function to the CDivideBlock class, with the prototype, virtual
void CDivideBlock::DrawBlock(CDC *pDC), and edit it as follows.
void CDivideBlock::DrawBlock(CDC *pDC)
{
// Note: pDC is a ptr to the device context of “Class Device Context”
(CDC)
int pen_color; // color of pen
int pen_width; // width of pen
double width; // width of block
CPoint pt[8]; // pts of line segments used to draw
“divide”, “multiply”, and “line-separator” symbols.
CPoint blk_posn; // local blk_posn var. that is assigned
m_ptBlockPosition of CBlock
CPoint lower_dot_top_left; // lower dot used in divide sign
CPoint lower_dot_bottom_right;
CPoint upper_dot_top_left; // upper dot used in divide sign
CPoint upper_dot_bottom_right;
//AfxMessageBox(“\n CDivideBlock::DrawBlock()\n”, MB_OK, 0);
// Get CBlock and CBlockShape vars.
blk_posn = GetBlockPosition();
width = GetBlockShape().GetBlockWidth();
// Assign vals to “divide” sign vinculum end pts.
pt[0].x = blk_posn.x − 0.35*width;
pt[0].y = blk_posn.y − 0.25*width;
pt[1].x = blk_posn.x − 0.05*width;
pt[1].y = pt[0].y;
// Assign vals to “multiply (X)” sign line segment end pts.
pt[2].x = blk_posn.x + 0.05*width;
pt[2].y = blk_posn.y + 0.05*width;
pt[3].x = blk_posn.x + 0.35*width;
pt[3].y = blk_posn.y + 0.35*width;
pt[4].x = pt[3].x;
pt[4].y = pt[2].y;
pt[5].x = pt[2].x;
pt[5].y = pt[3].y;
// Assign vals to the “line separator” symbol bw the “divide” and
“multiply” signs
pt[6].x = blk_posn.x − 0.25*width;
pt[6].y = blk_posn.y + 0.20*width;
pt[7].x = blk_posn.x + 0.20*width;
pt[7].y = blk_posn.y − 0.25*width;
// Assign vals to ellipse-bounding rectangles for upper and lower dots
upper_dot_top_left.x = (int)(blk_posn.x − 0.24*width);
upper_dot_top_left.y = (int)(blk_posn.y − 0.40*width);
upper_dot_bottom_right.x = (int)(blk_posn.x − 0.16*width);
upper_dot_bottom_right.y = (int)(blk_posn.y − 0.32*width);
80 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
lower_dot_top_left.x = (int)(upper_dot_top_left.x);
lower_dot_top_left.y = (int)(blk_posn.y − 0.18*width);
lower_dot_bottom_right.x = (int)(upper_dot_bottom_right.x);
lower_dot_bottom_right.y = (int)(blk_posn.y − 0.10*width);
// Create a pen
pen_width = GetPenWidth();
pen_color = GetPenColor();
CPen lpen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
// Draw the vinculum of the “divide” sign.
pDC->MoveTo(pt[0]);
pDC->LineTo(pt[1]);
3.6.1.6 CIntegratorBlock::DrawBlock()
Add a public virtual member function to the CIntegratorBlock class, with the prototype, virtual
void CIntegratorBlock::DrawBlock(CDC *pDC), and edit it as follows.
// Create a pen
pen_width = GetPenWidth();
pen_color = GetPenColor();
CPen lpen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
3.6.1.7 CLinearFnBlock::DrawBlock()
Add a public virtual member function to the CLinearFnBlock class with the prototype, virtual
void CLinearFnBlock::DrawBlock(CDC *pDC), and edit it as follows.
// Create a pen
pen_width = GetPenWidth();
//pen_color = GetPenColor();
pen_color = RGB(0,255,0);
CPen lpen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
3.6.1.8 COutputBlock::DrawBlock()
Add a public virtual member function to the COutputBlock class with the prototype, virtual
void COutputBlock::DrawBlock(CDC *pDC), and edit it as follows.
void COutputBlock::DrawBlock(CDC *pDC)
{
// Note: pDC is a ptr to the device context of “Class Device Context”
(CDC)
int i;
int pen_color; // color of pen
int pen_width; // width of pen
double width; // width of block
CPoint blk_posn; // local blk_posn var. that is assigned
m_ptBlockPosition of CBlock
CPoint dot_top_left; // green dot top left
CPoint dot_bottom_right; // green dot bottom right
CPoint pt_array[20]; // array of CPoint
//AfxMessageBox(“\n COutputBlock::DrawBlock()\n”, MB_OK, 0);
// Get CBlock and CBlockShape vars.
blk_posn = GetBlockPosition();
width = GetBlockShape().GetBlockWidth();
// Assign vals to graphic pts.
// Outer box pts
pt_array[0].x = −0.4*width;
pt_array[0].y = −0.4*width;
pt_array[1].x = 0.4*width;
pt_array[1].y = pt_array[0].y;
pt_array[2].x = pt_array[1].x;
pt_array[2].y = 0.3*width;
pt_array[3].x = pt_array[0].x;
pt_array[3].y = pt_array[2].y;
// Inner box pts
pt_array[4].x = −0.35*width;
pt_array[4].y = −0.35*width;
pt_array[5].x = 0.35*width;
pt_array[5].y = pt_array[4].y;
pt_array[6].x = pt_array[5].x;
pt_array[6].y = 0.25*width;
pt_array[7].x = pt_array[4].x;
pt_array[7].y = pt_array[6].y;
// Stand pts
pt_array[8].x = −0.1*width;
pt_array[8].y = 0.3*width;
pt_array[9].x = 0.1*width;
pt_array[9].y = pt_array[8].y;
pt_array[10].x = pt_array[9].x;
pt_array[10].y = 0.37*width;
pt_array[11].x = pt_array[8].x;
pt_array[11].y = pt_array[10].y;
pt_array[12].x = −0.2*width;
pt_array[12].y = pt_array[11].y;
pt_array[13].x = 0.2*width;
pt_array[13].y = pt_array[12].y;
Constructing Blocks 85
pt_array[14].x = pt_array[13].x;
pt_array[14].y = 0.45*width;
pt_array[15].x = pt_array[12].x;
pt_array[15].y = pt_array[14].y;
// Pseudo text pts.
// Line 1
pt_array[16].x = −0.3*width;
pt_array[16].y = −0.25*width;
pt_array[17].x = −0.15*width;
pt_array[17].y = pt_array[16].y;
// Line 2
pt_array[18].x = −0.3*width;
pt_array[18].y = −0.175*width;
pt_array[19].x = −0.05*width;
pt_array[19].y = pt_array[18].y;
// Add the blk_posn to the pts.
for(i=0; i<20; i++)
{
pt_array[i].x = blk_posn.x + pt_array[i].x;
pt_array[i].y = blk_posn.y + pt_array[i].y;
}
// Assign vals to ellipse-bounding rectangles for green dot
dot_top_left.x = (int)(blk_posn.x + 0.25*width);
dot_top_left.y = (int)(blk_posn.y + 0.25*width);
dot_bottom_right.x = (int)(blk_posn.x + 0.35*width);
dot_bottom_right.y = (int)(blk_posn.y + 0.35*width);
// Chain up to CBlock for primitive drawing
CBlock::DrawRectangleBlock(pDC);
// Create a pen
pen_width = GetPenWidth();
pen_color = GetPenColor();
CPen lpen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
// Draw all pts of the monitor graphic.
// Outer bounding box
pDC->MoveTo(pt_array[0]);
for(i=1; i<4; i++)
{
pDC->LineTo(pt_array[i]);
}
pDC->LineTo(pt_array[0]);
// Inner bounding box
/*pDC->MoveTo(pt_array[4]);
for(i=4; i<8; i++)
{
pDC->LineTo(pt_array[i]);
}
pDC->LineTo(pt_array[4]);
*/
// Stand box
pDC->MoveTo(pt_array[8]);
86 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
3.6.1.9 CSignalGeneratorBlock::DrawBlock()
Add a public virtual member function to the CSignalGeneratorBlock class with the prototype,
virtual void CSignalGeneratorBlock::DrawBlock(CDC *pDC), and edit it as follows.
blk_posn = GetBlockPosition();
width = GetBlockShape().GetBlockWidth();
// Generate functional eval of pts for sine and cosine curves
for(i=0; i<361; i++)
{
degree = i;
radian = (pi/180)*degree;
fn1[i].x = ( (degree − 180)/360)*width; // ( (degree − 180)/360)
scales the angle val bw. [−0.5,0.5]
fn1[i].y = sin(radian)*(−0.4)*width; // (−0.4)*width scales
the curve the right way around
fn2[i].x = ( (degree − 180)/360)*width; // ( (degree − 180)/360)
scales the angle val bw. [−0.5,0.5]
fn2[i].y = cos(radian)*(−0.4)*width; // (−0.4)*width scales the
curve the right way around
}
// Add the blk_posn to the pts.
for(i=0; i<361; i++)
{
fn1[i].x = blk_posn.x + fn1[i].x;
fn1[i].y = blk_posn.y + fn1[i].y;
fn2[i].x = blk_posn.x + fn2[i].x;
fn2[i].y = blk_posn.y + fn2[i].y;
}
// Chain up to CBlock for primitive drawing
CBlock::DrawRectangleBlock(pDC);
// Create a pen
pen_width = GetPenWidth();
//pen_color = GetPenColor();
pen_color = RGB(0,255,0); // green
CPen lpen(PS_SOLID, pen_width, pen_color);
// Create a new pen
pen_color = RGB(255,0,0); // red
CPen lnew_pen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
// Draw the sine curve
pDC->MoveTo(fn1[0]);
for(i=1; i<361; i++)
{
pDC->LineTo(fn1[i]);
}
// Draw the cosine curve
pDC->SelectObject(&lnew_pen); // select the pen that’s red.
pDC->MoveTo(fn2[0]);
for(i=1; i<361; i++)
{
pDC->LineTo(fn2[i]);
}
// Reset the prev. pen
pDC->SelectObject(pOldPen);
}
88 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
3.6.1.10 CSubsystemBlock::DrawBlock()
Add a public virtual member function to the CSubsystemBlock class, with the prototype, virtual
void CSubsystemBlock::DrawBlock(CDC *pDC), and edit it as follows.
void CSubsystemBlock::DrawBlock(CDC *pDC)
{
// Note: pDC is a ptr to the device context of “Class Device Context”
(CDC)
int i;
int pen_color; // color of pen
int pen_width; // width of pen
double width; // width of block
CPoint pt_array[18]; // array of CPoints to draw the block
graphics
CPoint blk_posn; // local blk_posn var. that is assigned
m_ptBlockPosition of CBlock
//AfxMessageBox(“\n CSubsystemBlock::DrawBlock()\n”, MB_OK, 0);
// Get CBlock and CBlockShape vars.
blk_posn = GetBlockPosition();
width = GetBlockShape().GetBlockWidth();
// Pt arrays coords.
pt_array[0].x = −0.4*width;
pt_array[0].y = −0.4*width;
pt_array[1].x = −0.1*width;
pt_array[1].y = pt_array[0].y;
pt_array[2].x = pt_array[1].x;
pt_array[2].y = −0.1*width;
pt_array[3].x = pt_array[0].x;
pt_array[3].y = pt_array[2].y;
pt_array[4].x = 0.1*width;
pt_array[4].y = 0.1*width;
pt_array[5].x = 0.4*width;
pt_array[5].y = pt_array[4].y;
pt_array[6].x = pt_array[5].x;
pt_array[6].y = 0.4*width;
pt_array[7].x = pt_array[4].x;
pt_array[7].y = pt_array[6].y;
pt_array[8].x = −0.1*width;
pt_array[8].y = −0.25*width;
pt_array[9].x = 0.25*width;
pt_array[9].y = pt_array[8].y;
pt_array[10].x = pt_array[9].x;
pt_array[10].y = 0.1*width;
pt_array[11].x = 0.1*width;
pt_array[11].y = 0.25*width;
pt_array[12].x = −0.25*width;
pt_array[12].y = pt_array[11].y;
pt_array[13].x = pt_array[12].x;
pt_array[13].y = −0.1*width;
pt_array[14].x = pt_array[0].x;
pt_array[14].y = −0.25*width;
pt_array[15].x = −0.5*width;
pt_array[15].y = pt_array[14].y;
pt_array[16].x = pt_array[5].x;
pt_array[16].y = 0.25*width;
Constructing Blocks 89
pt_array[17].x = 0.5*width;
pt_array[17].y = pt_array[16].y;
// Add the blk_posn to the pts*width.
for(i=0; i<18; i++)
{
pt_array[i].x = blk_posn.x + pt_array[i].x;
pt_array[i].y = blk_posn.y + pt_array[i].y;
}
// Chain up to CBlock for primitive drawing
CBlock::DrawRectangleBlock(pDC);
// Create a pen
pen_width = GetPenWidth();
pen_color = GetPenColor();
CPen lpen(PS_SOLID, pen_width, pen_color);
// Create a new pen
pen_color = RGB(0,255,0); // green
CPen lnew_pen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
// Draw the i/o blocks
pDC->MoveTo(pt_array[0]);
for(i=1; i<4; i++)
{
pDC->LineTo(pt_array[i]);
}
pDC->LineTo(pt_array[0]);
pDC->MoveTo(pt_array[4]);
for(i=4; i<8; i++)
{
pDC->LineTo(pt_array[i]);
}
pDC->LineTo(pt_array[4]);
// Draw the connecting wires
pDC->SelectObject(&lnew_pen); // select the pen that’s red.
pDC->MoveTo(pt_array[8]);
pDC->LineTo(pt_array[9]);
pDC->LineTo(pt_array[10]);
pDC->MoveTo(pt_array[11]);
pDC->LineTo(pt_array[12]);
pDC->LineTo(pt_array[13]);
pDC->MoveTo(pt_array[14]);
pDC->LineTo(pt_array[15]);
pDC->MoveTo(pt_array[16]);
pDC->LineTo(pt_array[17]);
// Reset the prev. pen
pDC->SelectObject(pOldPen);
}
3.6.1.11 CSubsystemInBlock::DrawBlock()
Add a public virtual member function to the CSubsystemInBlock class with the prototype, virtual
void CSubsystemInBlock::DrawBlock(CDC *pDC), and edit it as follows.
90 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Pt arrays coords.
pt_array[0].x = −0.4*width;
pt_array[0].y = −0.4*width;
pt_array[1].x = −0.1*width;
pt_array[1].y = pt_array[0].y;
pt_array[2].x = pt_array[1].x;
pt_array[2].y = −0.1*width;
pt_array[3].x = pt_array[0].x;
pt_array[3].y = pt_array[2].y;
pt_array[4].x = 0.1*width;
pt_array[4].y = 0.1*width;
pt_array[5].x = 0.4*width;
pt_array[5].y = pt_array[4].y;
pt_array[6].x = pt_array[5].x;
pt_array[6].y = 0.4*width;
pt_array[7].x = pt_array[4].x;
pt_array[7].y = pt_array[6].y;
pt_array[8].x = −0.1*width;
pt_array[8].y = −0.25*width;
pt_array[9].x = 0.25*width;
pt_array[9].y = pt_array[8].y;
pt_array[10].x = pt_array[9].x;
pt_array[10].y = 0.1*width;
pt_array[11].x = 0.1*width;
pt_array[11].y = 0.25*width;
pt_array[12].x = −0.25*width;
pt_array[12].y = pt_array[11].y;
// modified from CSubsystemBlock::DrawBlock() to be on side of
“input” triangle
pt_array[13].x = pt_array[12].x;
pt_array[13].y = −(0.1+0.075)*width;
// end modification
pt_array[14].x = pt_array[0].x;
pt_array[14].y = −0.25*width;
pt_array[15].x = −0.5*width;
pt_array[15].y = pt_array[14].y;
pt_array[16].x = pt_array[5].x;
pt_array[16].y = 0.25*width;
Constructing Blocks 91
pt_array[17].x = 0.5*width;
pt_array[17].y = pt_array[16].y;
// Add the blk_posn to the pts*width.
for(i=0; i<18; i++)
{
pt_array[i].x = blk_posn.x + pt_array[i].x;
pt_array[i].y = blk_posn.y + pt_array[i].y;
}
// Chain up to CBlock for primitive drawing
CBlock::DrawRectangleBlock(pDC);
// Create a pen
pen_width = GetPenWidth();
//pen_color = GetPenColor();
pen_color = RGB(180,180,180);
CPen lpen(PS_SOLID, pen_width, pen_color);
// Create a new pen
pen_color = RGB(0,255,0);
CPen lnew_pen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
// Draw the i/o blocks
pDC->MoveTo(pt_array[0]);
/*for(i=1; i<4; i++)
{
pDC->LineTo(pt_array[i]);
}
pDC->LineTo(pt_array[0]);
*/
pDC->MoveTo(pt_array[4]);
for(i=4; i<8; i++)
{
pDC->LineTo(pt_array[i]);
}
pDC->LineTo(pt_array[4]);
// Draw the connecting wires
pDC->MoveTo(pt_array[8]);
pDC->LineTo(pt_array[9]);
pDC->LineTo(pt_array[10]);
pDC->MoveTo(pt_array[11]);
pDC->LineTo(pt_array[12]);
pDC->LineTo(pt_array[13]);
pDC->MoveTo(pt_array[16]);
pDC->LineTo(pt_array[17]);
// Draw the input graphics denoted by green
pDC->SelectObject(&lnew_pen);
pDC->MoveTo(pt_array[0]);
pDC->LineTo(pt_array[8]);
pDC->LineTo(pt_array[3]);
pDC->LineTo(pt_array[0]);
pDC->MoveTo(pt_array[14]);
pDC->LineTo(pt_array[15]);
92 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
3.6.1.12 CSubsystemOutBlock::DrawBlock()
Add a public virtual member function to the CSubsystemOutBlock class with the prototype,
virtual void CSubsystemOutBlock::DrawBlock(CDC *pDC), and edit it as follows.
pt_array[12].y = pt_array[11].y;
pt_array[13].x = pt_array[12].x;
pt_array[13].y = −0.1*width;
pt_array[14].x = pt_array[0].x;
pt_array[14].y = −0.25*width;
pt_array[15].x = −0.5*width;
pt_array[15].y = pt_array[14].y;
pt_array[16].x = pt_array[5].x;
pt_array[16].y = 0.25*width;
pt_array[17].x = 0.5*width;
pt_array[17].y = pt_array[16].y;
pDC->MoveTo(pt_array[16]);
pDC->LineTo(pt_array[17]);
// Draw the output graphics denoted by green
pDC->SelectObject(&lnew_pen);
pDC->MoveTo(pt_array[4]);
pDC->LineTo(pt_array[16]);
pDC->LineTo(pt_array[7]);
pDC->LineTo(pt_array[4]);
pDC->MoveTo(pt_array[16]);
pDC->LineTo(pt_array[17]);
// Reset the prev. pen
pDC->SelectObject(pOldPen);
}
3.6.1.13 CTransferFnBlock::DrawBlock()
Add a public virtual member function to the CTransferFnBlock class, with the prototype, virtual
void CTransferFnBlock::DrawBlock(CDC *pDC), and edit it as follows.
// Vinculum
pt_array[8].x = −0.2*width;
pt_array[8].y = 0.0;
pt_array[9].x = 0.2*width;
pt_array[9].y = 0.0;
// Add the blk_posn to the pts*width.
for(i=0; i<10; i++)
{
pt_array[i].x = blk_posn.x + pt_array[i].x;
pt_array[i].y = blk_posn.y + pt_array[i].y;
}
// Chain up to CBlock for primitive drawing
CBlock::DrawRectangleBlock(pDC);
// Create a pen
pen_width = GetPenWidth();
pen_color = GetPenColor();
CPen lpen(PS_SOLID, pen_width, pen_color);
// Set the new pen as the drawing obj.
CPen *pOldPen = pDC->SelectObject(&lpen);
// Draw the transfer fn. graphic curve
pDC->MoveTo(pt_array[0]);
pDC->LineTo(pt_array[2]);
pDC->MoveTo(pt_array[1]);
pDC->LineTo(pt_array[2]);
pDC->MoveTo(pt_array[2]);
pDC->LineTo(pt_array[3]);
pDC->MoveTo(pt_array[4]);
pDC->LineTo(pt_array[5]);
pDC->MoveTo(pt_array[6]);
pDC->LineTo(pt_array[7]);
pDC->MoveTo(pt_array[8]);
pDC->LineTo(pt_array[9]);
// Reset the prev. pen
pDC->SelectObject(pOldPen);
}
FIGURE 3.5 All blocks available from the Common Blocks toolbar presented in the form of primitive
shapes with block-specific graphics.
96 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
3.7 SUMMARY
The Win32 Console application, ControlEng, is merged with the initial Visual C++ application,
DiagramEng, to result in an application whose structure is presented in a class hierarchical diagram
(Figure 3.1) showing the association relationships between the classes of the project. Changes are
made to the CBlock and CBlock-derived class constructors in preparation for the complete construc-
tion of concrete blocks. The ConstructBlock() method of the CDiagramEngDoc class calls
the particular block-construction functions in which a block is constructed and added to the system
model block list: thereafter, the system model is checked for correctness. The DrawBlock() func-
tion of the base CBlock class is then made virtual, and overriding methods to draw specific block
graphics are provided for each of the derived blocks, where a chaining to the base class is made for
the drawing of the primitive ellipse, rectangle, and triangle shapes.
REFERENCES
1. Microsoft Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
4 Constructing Block Ports
4.1 INTRODUCTION
In Chapter 3, blocks were constructed, added to the system model, and then drawn with specific
block-defining graphics on the palette. However, at this stage, no block input or output ports have
been constructed, and hence, they are not visible on the block icons. Ports are particular to the
blocks upon which they reside and hence should be constructed within the derived block class
constructor and their properties set prior to their insertion into the block-based vectors of input or
output ports.
The structure of the present chapter is centered around the manner in which ports are to be drawn
on a block and predominantly involves the CPort, CBlock, CBlock-derived, and CBlockShape
classes. Initially, the mathematical details concerning determining a port’s position on a face of a
block using a location-defining position angle are introduced. Then, the necessary additions and
alterations are made to the CPort, CBlock, CBlock-derived, and CBlockShape classes to construct
and set the properties of the input and output ports. Finally, the drawing of ports is implemented
by adding drawing-related code to the CBlock and CPort classes.
The instructions here could be presented in a function-by-function approach, where changes are
made to successive functions in the order that a developer may make them if coding one step at a
time. However, here, a class-centric approach is taken to convey the material in a more structured
manner. It may not be entirely clear as to why individual changes are being made to certain classes
and their member methods and variables until the collective process is completed: in this case, the
developer is encouraged to read the whole chapter first prior to adding code to the project.
97
98 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
θ
y x
Ellipse block
FIGURE 4.1 An ellipse block showing a port location vector of length r at an angle θ.
w
xport = xblock + (4.2a)
2
For θ ∈ (θcrit, π − θcrit), the local body coordinates of a port located on the boundary of the rectangle
block are x = r cos(θ) and y = h/2, where sin(θ) = h/2r, ⇒ r = h/2 sin(θ). Hence,
h
yport = yblock − (4.3b)
2
π–θ θcrit
y
x
Rectangle block
w/2
FIGURE 4.2 A rectangle block showing a port location vector of length r at an angle θ.
Constructing Block Ports 99
For θ ∈ [π − θcrit, π + θcrit], the local body coordinates of a port located on the boundary of the
rectangle block are x = −w/2 and y = r sin(θ), where cos(π − θ) = w/2r, ⇒ r = −w/2 cos(θ). Hence,
w
xport = xblock − (4.4a)
2
For θ ∈ (π + θcrit, 2π − θcrit), the local body coordinates of a port located on the boundary of the
rectangle block are x = r cos(θ) and y = −h/2, where sin(θ − π) = h/2r, ⇒ r = −h/2 sin(θ). Hence,
h
yport = yblock + (4.5b)
2
For θ ∈ [2π − θcrit, 2π], the local body coordinates of a port located on the boundary of the rectangle
block are x = w/2 and y = r sin(θ), where cos(2π − θ) = w/2r, ⇒ r = w/2 cos(θ). Hence,
w
xport = xblock + (4.6a)
2
w/ 2 w (4.7)
h= =
cos(π / 6) 3
⎛ w⎞ ⎛ π⎞
d = ⎜ ⎟ tan ⎜ ⎟ (4.8)
⎝ 2⎠ ⎝ 6⎠
For the purpose of writing C++ code, the development that follows is divided into three angular
domains: θ ∈ [0, 2π/3), θ ∈ [2π/3, 4π/3], and θ ∈ (4π/3, 2π].
100 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
A
Screen coord. sys.
π (0, 0) x
6
y
w2 h
y
B
c a
θ π6
B
d x h
c
E
Triangle block
FIGURE 4.3 A triangle block showing a port location vector of length c or c′ at an angle θ.
For θ ∈ [0, 2π/3), the law of sines allows one to determine the length of the side length c, i.e.,
h (4.9b)
⇒c=
2 sin(5π / 6 − θ)
where the denominator is nonzero for θ ∈ [0, 2π/3). The x and y components, with respect to the
local triangle coordinate system, of the port on the boundary of the block, are x = c cos(θ) and
y = c sin(θ), and hence, the global port position is
For θ ∈ [2π/3, 4π/3], the x and y components of the port located on the boundary of the block are,
x = −d and y = d tan(π − θ), where π − θ ≠ ±π/2 for θ ∈ [2π/3, 4π/3], and hence, the global port
position is
For θ ∈ (4π/3, 2π], the law of sines allows one to determine the length of the side length c′, i.e.,
h (4.12b)
⇒ cʹ =
2 sin(θ − 7π / 6)
where the denominator is nonzero for θ ∈ (4π/3, 2π]. The local x and y components of the position
of the port on the boundary of the block are x = c′ cos(θ) and y = c′ sin(θ), and hence, the global
port position is
A similar analysis may be performed for a triangle block facing to the left rather than to the right
(as pictured earlier). For the purpose of writing C++ code, the development that follows is divided
into four angular domains: θ ∈ [0, π/3], θ ∈ (π/3, π], θ ∈ [π, 5π/3), and θ ∈ [5π/3, 2π].
For θ ∈ [0, π/3], the x and y components of a port located on the boundary of the block are x = d
and y = d tan(θ), and hence, the global port position is
For θ ∈ (π/3,π], the law of sines allows one to determine the length of the side length c, i.e.,
h (4.15b)
⇒c=
2 sin(θ − π / 6)
where the denominator is nonzero for θ ∈ (π/3, π]. The x and y components, with respect to the local
triangle coordinate system, of the position of the port on the boundary of the block are x = c cos(θ)
and y = c sin(θ), and hence, the global port position is
For θ ∈ [π, 5π/3), the law of sines allows one to determine the length of the side length c′, i.e.,
h (4.17b)
⇒ cʹ =
2 sin(11π / 6 − θ)
102 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
where the denominator is nonzero for θ ∈ [π, 5π/3). The local x and y components of the position of
the port on the boundary of the block are x = c′ cos(θ) and y = c′ sin(θ), and hence, the global port
position is
For θ ∈ [5π/3, 2π], the x and y components of the port located on the boundary of the block are
x = d and y = d tan(2π − θ), and hence, the global port position is
The implementation that follows is structured to support the setting up of a port whose location
is defined by a positioning angle, as described earlier: the developer should keep this general idea
in mind.
Add a public member function to the CPort class to set the port position, with the proto-
type, void CPort::SetPortPosition(CPoint port_posn), and edit the function as shown.
The original Win32 Console Application had a GetName() member method of the CPort class.
However, to make it clear that it is the port name that is to be retrieved, delete the existing,
GetName() method, and replace it with a new CPort public constant member function with the
prototype, CString CPort::GetPortName(void) const, and edit it as follows.
Now, add a public member function to the CPort class to set the port name, with prototype,
void CPort::SetPortName(CString name), and edit it as follows.
The port position angle variable, “m_ptPortPositionAngle”, used to define the position of
a port on the side face of a block requires its own accessor functions. Hence, add a public
constant member function to the CPort class to get the port position angle, with prototype,
double CPort::GetPortPositionAngle(void) const, and edit the code as follows.
Add a public member function to the CPort class to set the port position angle, with prototype,
void CPort::SetPortPositionAngle(angle), and edit the code as shown.
Finally, for completeness, add a new public member method to the CPort class with the prototype,
void CPort::SetPortArrowDirection(EPortArrowDirection arrow_direc),
and edit it as follows.
CPoint CPort::CalculatePortPosition()
{
int count; // no. of times 360 divides into m_dPortPositionAngle.
double c; // length of ray from triangle CM to port posn on bndy of
triangle.
double d; // opp. side length of a subtriangle with hypotenuse h,
and adjacent side length width/2.
double eps = 0.0087; // epsilon value (radians) to allow setting of
angle at 2*pi; (0.0087 radians = 0.5 degrees)
double h; // hypotenuse of a subtriangle with opp. side
length d, and adjacent side length width/2.
double height = m_rRefToBlock.GetBlockShape().GetBlockHeight();
double pi = PI;
double r; // length of port posn vector.
double theta; // m_dPortPositionAngle converted to an angle
in radians.
Constructing Block Ports 105
if( (theta >= (pi − theta_crit) ) && (theta <= (pi + theta_crit) ) )
{
r = −width/(2*cos(theta) );
port_posn.x = blk_posn.x − width*0.5;
port_posn.y = blk_posn.y − r*sin(theta);
}
if( (theta > (pi + theta_crit) ) && (theta < (2*pi − theta_crit) ) )
{
r = −height/(2*sin(theta) );
port_posn.x = blk_posn.x + r*cos(theta);
port_posn.y = blk_posn.y + height*0.5;
}
if( (theta >= (2*pi − theta_crit) ) && (theta <= (2*pi + eps) ) )
{
r = width/(2*cos(theta) );
port_posn.x = blk_posn.x + width*0.5;
port_posn.y = blk_posn.y − r*sin(theta);
}
break;
case e_triangle:
// Get lengths of subtriangle sides: hypotenuse h, opp. side
length d., and base side length width/2.
d = (width/2)*tan(pi/6);
h = width/sqrt(3);
if(e_blk_direc == e_right) // triangle pointing to the right.
{
if( (theta >= 0) && (theta < 2*pi/3) )
{
c = h/(2*sin(5*pi/6 − theta) ); // length of ray from
CM to port posn on bndy of triangle.
port_posn.x = blk_posn.x + c*cos(theta);
port_posn.y = blk_posn.y − c*sin(theta);
}
if( (theta >= 2*pi/3) && (theta <= 4*pi/3) )
{
port_posn.x = blk_posn.x − d;
port_posn.y = blk_posn.y − d*tan(pi − theta);
}
if( (theta > 4*pi/3) && (theta <= (2*pi + eps) ) )
{
c = h/(2*sin(theta − 7*pi/6) );
port_posn.x = blk_posn.x + c*cos(theta);
port_posn.y = blk_posn.y − c*sin(theta);
}
}
else if(e_blk_direc == e_left) // triangle pointing to the
left.
{
if( (theta >= 0) && (theta <= pi/3) )
{
port_posn.x = blk_posn.x + d;
port_posn.y = blk_posn.y − d*tan(theta);
}
if( (theta > pi/3) && (theta <= pi) )
Constructing Block Ports 107
{
c = h/(2*sin(theta − pi/6) ); // length of ray
from CM to port posn on bndy of triangle.
port_posn.x = blk_posn.x + c*cos(theta);
port_posn.y = blk_posn.y − c*sin(theta);
}
if( (theta > pi) && (theta < 5*pi/3) )
{
c = h/(2*sin(11*pi/6 − theta) );
port_posn.x = blk_posn.x + c*cos(theta);
port_posn.y = blk_posn.y − c*sin(theta);
}
if( (theta >= 5*pi/3) && (theta <= (2*pi + eps) ) )
{
port_posn.x = blk_posn.x + d;
port_posn.y = blk_posn.y + d*tan(2*pi − theta);
}
}
else
{
// Print msg.
sMsg.Format(“\n CPort::CalculatePortPosition()\n e_blk_direc
for triangles should be e_left or e_right only!”);
AfxMessageBox(sMsg, nType, nIDhelp);
}
break;
default:
// no code for now
break;
}// end switch
// Set port position within CalculatePortPosition() to prevent an
additional call.
SetPortPosition(port_posn);
return port_posn;
}
The developer will notice, in the previous code, the use of the constant double value, “PI”, to set the
numerical value of π. Hence, add the definition of this constant to the “Block.h” header file as shown
in the following (the ellipsis, “…”, denotes omitted, unchanged code).
// Title: Block.h
#ifndef BLOCK_H
#define BLOCK_H // inclusion guard
#include <vector> // rqd. for vector (below)
using namespace std;
// User defined consts/vars.
const double PI = 3.14159265359; // pi in radians (rounded)
// User defined types
…
#endif
108 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
In addition, search for all occurrences of “pi” in the code and initialize, “pi = PI”, in place of any
numerical initialization that may be present. Note that, “PI” is in radians, and code using trigono-
metric functions will require arguments in radians.
After the variable declaration section, the port position angle, θ, represented by, “m_dPort-
PositionAngle”, has its domain bounded, such that θ ∈ [0, 2π], to allow the user setting up
the port to specify any value of the angle. The developer will notice the use of the function,
“double fabs(double x)”, to obtain the floating point absolute value of the operand: this is
used since the argument is a floating point number. If an integer-valued argument is used, then
the function, “int abs(int x)”, should be used instead: these two versions of the absolute
value function should not be confused.
The main switch statement concerns the block shape: an ellipse, a rectangle, or a triangle.
Thereafter, the port position is determined with respect to the global screen coordinate system,
where the mathematical details reflect those provided earlier. The developer will notice that the
triangular Gain block can be oriented to the right (default) or left (although the enumerated type
EBlockDirection, does cater for the additional “up” and “down” directions, these are not consid-
ered here: the interested reader may add this functionality if desired). Finally, after the switch
statement, a call to SetPortPosition() is made to set the port position member variable,
“m_ptPortPosition”.
The developer will notice that an epsilon value, ε = 0.0087c = 0.5°, was added to the domain-
checking code for the rectangle block for the port position angle θ ∈ [2π − θcrit, 2π + ε] and the right
and left facing triangle blocks for the port position angle, θ ∈ [4π/3, 2π + ε] and θ ∈ [5π/3, 2π + ε],
respectively. This was done to allow setting of the angle at 360.00° rather than 0.00° if desired by
the user. Finally, when testing the code, the developer may choose values of θ in all critical subdo-
mains and on the domain boundaries specified in the earlier mathematical development.
CPort::CPort(CBlock &block_ref):
m_rRefToBlock(block_ref) // must init the m_rRefToBlock rather than
assign on CPort obj. creation
{
// Init
m_strPortName = “actual_port_name”;
m_ptPortPosition.x = 0.0; // generalized coords not rel.
coords.
m_ptPortPosition.y = 0.0; // generalized coords not rel.
coords.
m_ePortArrowDirec = e_right_arrow; // in general port arrows point
to the right.
}
Add a public member function to the CBlock class with the prototype, vector<CPort*> &CBlock::
GetVectorOfOutputPorts(), to return “m_vecOutputPorts” by reference, and edit the code as
follows.
vector<CPort*> &CBlock::GetVectorOfOutputPorts()
{
return m_vecOutputPorts;
}
Make sure the “Block.h” header file has the correct function prototypes for the recently added
accessor functions, i.e., the return type is “vector<CPort*> &”.
Add a public member function to the CBlockShape class, to set the variable, “m_dBlockHeight”,
with prototype, void CBlockShape::SetBlockHeight(double blk_height), and edit
the code as follows.
void CBlockShape::SetBlockHeight(double blk_height)
{
// Set the member var m_dBlockHeight
m_dBlockHeight = blk_height;
}
The developer should make sure that the width member variable, “m_dBlockWidth”, also has the
relevant accessor methods. The GetBlockWidth() function was introduced in Chapter 3, but for
convenience, it is repeated here.
110 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Add a public member function to the CBlockShape class to set the block width, with prototype,
void CBlockShape::SetBlockWidth(double blk_width), and edit the code as follows.
Finally, make sure that the width and height member variables are correctly set in the CBlockShape
constructor function as shown in the following.
CBlockShape::CBlockShape(void)
{
m_eBlockDirection = e_right; // default direc is to the right.
m_dBlockWidth = (GetDocumentGlobalFn()->GetDeltaLength() )*2.0;
m_dBlockHeight = m_dBlockWidth;
}
p_output_port->CalculatePortPosition();
p_output_port->SetPortArrowDirection(e_right_arrow);
// Add the port to the vector of ports
GetVectorOfOutputPorts().push_back(p_output_port);
}
The developer will notice that the accessor functions introduced earlier are used to set the port
name, position angle, and arrow direction. The CalculatePortPosition() function does not
require arguments, since as it is a function of the CPort class, it has access to the class member vari-
ables: in particular, “m_dPortPositionAngle”, as shown in the function definition provided earlier.
Finally, after the port has been constructed and set up, it is added to the vector of output ports that
are retrieved by the CBlock function, GetVectorOfOutputPorts().
void CDiagramEngDoc::CheckSystemModel()
{
int n_in_ports = 0;
int n_out_ports = 0;
// -- Message box vars.
CString sMsg; // main msg string
CString sMsgTemp; // temp msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
sMsgTemp.Format(“\n CDiagramEngDoc::CheckSystemModel()\n”);
sMsg += sMsgTemp;
// -- Block list vars.
list<CBlock*> blk_list;
list<CBlock*>::iterator it_blk; // iterator for list of CBlock-ptrs
blk_list = GetSystemModel().GetBlockList();
// -- Check Block List
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
sMsgTemp.Format(“ %s: ”,(*it_blk)->GetBlockName() );
sMsg += sMsgTemp;
// Get the vectors of input and output ports and determine their
sizes.
n_in_ports = (*it_blk)->GetVectorOfInputPorts().size();
n_out_ports = (*it_blk)->GetVectorOfOutputPorts().size();
sMsgTemp.Format(“ n_in_ports = %d, n_out_ports = %d\n”,
n_in_ports, n_out_ports);
sMsg += sMsgTemp;
}
AfxMessageBox(sMsg, nType, nIDhelp);
}
112 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 4.4 CheckSystemModel() displays a message box showing the block type and the number of
input and output ports: here, a Constant Block is constructed with no input ports and one output port.
Compile and run the code to check the numbers of input and output ports for the block concerned
(here a Constant block): the following message box indicates that the numbers of input and output
ports are zero and one, respectively (Figure 4.4).
TABLE 4.1
CBlock-Derived Block Classes Whose Constructors Should Be
Augmented to Construct Ports with the Following Settings
Class Name Input Port θ Port Direction Output Port θ Port Direction
CConstantBlock NA NA 0.0 Right
CDerivativeBlock 180.0 Right 0.0 Right
CDivideBlock 150.0, 210.0 Right, Right 0.0 Right
CGainBlock 180.0 Right 0.0 Right
CIntegratorBlock 180.0 Right 0.0 Right
CLinearFnBlock NA NA 0.0 Right
COutputBlock 180.0 Right NA NA
CSignalGeneratorBlock NA NA 0.0 Right
CSubsystemBlock 180.0 Right 0.0 Right
CSubsystemInBlock NA NA 0.0 Right
CSubsystemOutBlock 180.0 Right NA NA
CSumBlock 180.0, 270.0 Right, Up 0.0 Right
CTransferFnBlock 180.0 Right 0.0 Right
1. Call CBlock::DrawBlockPorts()
2. Add CBlock::DrawBlockPorts()
3. Add CPort::DrawPort()
Augment all the CBlock-derived blocks’ virtual DrawBlock() functions, with a call to the to-
be-added CBlock-based DrawBlockPorts() method, as shown in the following code excerpt.
Here, the CConstantBlock::DrawBlock() function performs all its drawing, before finally
calling the base class DrawBlockPorts() function, passing in the pointer-to-Class-Device-
Context argument. The ellipsis (“…”) denotes omitted but unchanged code from Chapter 3.
The call to DrawBlockPorts() shown earlier in bold needs to be made to all overriding virtual
DrawBlock() functions of all CBlock-derived classes, as listed in Table 4.1.
// Msg. box
sMsg.Format(“\n CPort::DrawPort(), m_ePortArrowDirec = %d\n,”
m_ePortArrowDirec);
//AfxMessageBox(sMsg, nType, nIDhelp);
case e_up_arrow:
pt_array[0].x = m_ptPortPosition.x − d;
pt_array[0].y = m_ptPortPosition.y + 0.5*d;
pt_array[1].x = m_ptPortPosition.x;
pt_array[1].y = m_ptPortPosition.y − 0.5*d;
pt_array[2].x = m_ptPortPosition.x + d;
pt_array[2].y = pt_array[0].y;
// Shift pts as desired
if(shift_up)
116 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
for(i=0;i<3;i++)
{
pt_array[i].y = pt_array[i].y + 0.5*d;
}
}
break;
case e_right_arrow:
pt_array[0].x = m_ptPortPosition.x − 0.5*d;
pt_array[0].y = m_ptPortPosition.y − d;
pt_array[1].x = m_ptPortPosition.x + 0.5*d;
pt_array[1].y = m_ptPortPosition.y;
pt_array[2].x = pt_array[0].x;
pt_array[2].y = m_ptPortPosition.y + d;
case e_down_arrow:
pt_array[0].x = m_ptPortPosition.x + d;
pt_array[0].y = m_ptPortPosition.y − 0.5*d;
pt_array[1].x = m_ptPortPosition.x;
pt_array[1].y = m_ptPortPosition.y + 0.5*d;
pt_array[2].x = m_ptPortPosition.x − d;
pt_array[2].y = pt_array[0].y;
default:
// Use e_right_arrow case
pt_array[0].x = m_ptPortPosition.x − 0.5*d;
pt_array[0].y = m_ptPortPosition.y − d;
pt_array[1].x = m_ptPortPosition.x + 0.5*d;
pt_array[1].y = m_ptPortPosition.y;
pt_array[2].x = m_ptPortPosition.x − 0.5*d;
pt_array[2].y = m_ptPortPosition.y + d;
{
pt_array[i].x = pt_array[i].x + 0.5*d;
}
}
break;
}// end switch
// -- FILL THE TRIANGULAR PORT POLYGON
// Create a brush
brush_color = RGB(200,200,200); // White = RGB(255,255,255),
Black = RGB(0,0,0)
CBrush NewBrush(brush_color); // create a new brush using the
brush_color
CBrush *pBrush; // declare a ptr-to-CBrush (for
resetting below)
// Select the new brush
pBrush = pDC->SelectObject(&NewBrush);
// Fill the polygon
pDC->Polygon(pt_array, 3); // 3 vertices of triangle stored in
pt_array enclose polygon region
// Reset the old brush
pDC->SelectObject(pBrush);
// -- DRAW THE BORDER OF THE TRIANGULAR PORT POLYGON
// Set a new pen color for the border only
pen_color = RGB(0,0,0); // White = RGB(255,255,255), Black =
RGB(0,0,0)
pen_width = 1;
// Create the pen
CPen lpen(PS_SOLID, pen_width, pen_color);
// Select the pen as the drawing obj.
// The value returned by SelectObject() is a ptr to the obj. being
replaced, i.e. an old-pen-ptr.
CPen *pOldPen = pDC->SelectObject(&lpen);
// Draw the arrow/triangle (clockwise)
pDC->MoveTo(pt_array[0]);
pDC->LineTo(pt_array[1]);
pDC->LineTo(pt_array[2]);
pDC->LineTo(pt_array[0]);
// Reset the prev. pen
pDC->SelectObject(pOldPen);
}
The developer will notice that the length, “d”, used in the code, represents half the length of the
hypotenuse of a right-angled triangle used to draw a port, where the 90° vertex (“pt_array[1]” given
earlier) points in the intended direction of the port (left, up, right, or down) as shown in Figure 4.5.
The center of the port lies in the middle of this right-angled triangle, such that the port intersects the
block face upon which it resides, clearly displaying the intended signal flow direction.
pt. 2
FIGURE 4.5 Block port graphic intersecting the block face and pointing to the right.
FIGURE 4.6 Derived blocks with default port settings as specified in their constructors.
they may be drawn via the virtual overriding DrawBlock() method, which invokes the base
CBlock class’ DrawBlockPorts() method, which in turn iterates through the aforemen-
tioned vectors calling DrawPort() upon a pointer-to-CPort. On building and then running the
DiagramEng application and adding all the blocks available on the Common Blocks toolbar on
the palette, the output should appear as shown in Figure 4.6: here, the default block ports are
shaded in gray where the direction of the port arrow indicates the direction of signal flow into
and/or out of the block.
4.8 SUMMARY
The construction of block-based ports first involved a mathematical discussion of the mechanism
to place ports, based on a port position angle, on the side face of an elliptical-, rectangular-, or
triangular-shaped block. CPort-based member variables and methods were added in preparation for
Constructing Block Ports 119
5.1 INTRODUCTION
Connection objects are used to connect and transfer signal data between model blocks. A connec-
tion involves a tail and a head point, including an arrowhead that indicates the direction of signal
flow. Appendix B presents a single document interface application that allows a user to simply draw
lines with arrowheads attached, on the screen using the mouse. The developer is encouraged to read
this material that is actually based on the work of Chapman [1], before commencing the develop-
ment presented herein to extend the DiagramEng project.
121
122 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
An aside: the developer will notice that, at present, a new CSignal-based object is created and its
address assigned to “m_pSignal”. This may change later depending on the CSignal-based class
structure that is finally used for signal propagation purposes.
The AddConnection() function creates the connection using the CConnection constructor tak-
ing two CPoint arguments and adds it to the list of connections, “m_lstConnection”.
type and then manually alter the function prototype declaration in “SystemModel.h” and definition in
“SystemModel.cpp”, to return the connection list by reference. Edit the function as shown.
list<CConnection*> &CSystemModel::GetConnectionList()
{
// Return the connection list by reference
return m_lstConnection;
}
The developer will notice that this function uses the member variables “m_ptPrevPos” and
“m_ptOrigin” to be used in the following OnMouseMove() and OnLButtonUp() functions.
Hence, add these two private CPoint member variables to the CDiagramEngView class.
The OnMouseMove() function declares two local objects of class CConnection, where the to-
be-added DrawConnection() function is called on those objects. The first CConnection
object, “con_obj1”, is used to effectively erase the previous line drawn on a prior call to the
OnMouseMove() function: otherwise, it would remain on the screen. This is performed by revers-
ing the color, using SetROP2(R2_NOT) and overdrawing the old line defined by the points:
“m_ptOrigin” and “m_ptPrevPos”. The second CConnection object, “con_obj2”, defining the pres-
ent connection line, with the starting and ending points, “m_ptOrigin” and “point”, respectively,
is drawn as normal, with DrawConnection(). Finally, the current mouse cursor point, “point”,
is assigned to “m_ptPrevPos” to make a record of what will become the previous point, to be sub-
sequently used when overdrawing the line on the next entry into the OnMouseMove() function.
The developer will note that GetSystemModel() is called upon the pointer-to-CDiagramEngDoc,
obtained via GetDocument(), and then AddConnection(), a member function of the
CSystemModel class introduced earlier, is called to add the connection to the list. To make the doc-
ument redraw itself, UpdateAllViews() is called on the pointer-to-CDiagramEngDoc. In fact,
UpdateAllViews() calls CView::OnUpdate() and allows the view to update its display to
reflect the modifications made to the view’s document [2].
DrawSystemModel() then iterates over the list of blocks, “m_lstBlock” and connections (with
arrowheads) “m_lstConnection”, calling DrawBlock() and DrawConnection(), respectively,
as shown in the following.
The developer will notice the call to DrawArrowHead() made earlier to draw the connection
line’s arrowhead. Hence, add a public member function to the CConnection class with the proto-
type, void CConnection::DrawArrowHead(CDC *pDC), and edit the function as shown in
the following. Note the width of the arrowhead is fixed for all block sizes. In addition, include the
“math.h” header file, using #include <math.h>, at the top of the source file, “Signal.cpp”, holding all
CConnection and CSignal-related code, since the mathematical functions, sin() and cos(), are used.
// Print msg.
sMsg.Format(“\n CConnection::DrawArrowHead(), d = %lf, h = %lf\n”, d, h);
//AfxMessageBox(sMsg, nType, nIDhelp);
The developer will have noticed that since GetDocumentGlobalFn() returns a pointer-
to-CDiagramEngDoc, upon which the GetDeltaLength() function is called, the
“DiagramEngDoc.h” header file needs to be included beneath the inclusion of the “Signal.h” header
file, near the top of “Signal.cpp”, i.e., #include “DiagramEngDoc.h”.
In addition, the developer will recall that the previous code simply implements the mathematical
derivation provided in Appendix B, concerning the geometrical method of determining the position
vectors of the arrowhead vertices to draw the connection-based arrowhead in the appropriate direc-
tion and with the correct angular orientation.
Now upon running the application and drawing on the palette, the document is labeled as having
been modified, and a message box appears upon closing or exiting the application, prompting the
user to save changes to the document.
Finally, the user may now draw both blocks and connections on the palette as shown in Figure 5.1.
However, at this stage, the connections do not connect/snap to the block ports but simply lie in a
disconnected state. In future, functionality will be added to the project to allow the user to attach
connection head and tail points, to block input and output ports, respectively.
FIGURE 5.1 Blocks, block ports, and connection objects, with arrowheads pointing in the correct direction,
drawn on the palette.
130 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 5.1
Context Menu IDs, Captions, and Prompts (Status Bar and Tooltips)
ID Caption Prompts
IDM_DELETE_ITEM &Delete Item Delete selected item\nDelete selected item
IDM_SET_ITEM_PROPERTIES &Set Properties Set item properties\nSet item properties
Constructing Connections 131
Initially, the menu is loaded by declaring a CMenu object and then calling LoadMenu() upon
the object specifying the ID of the menu to be loaded, i.e., IDR_CONTEXTMENU. Thereafter,
the first submenu is obtained and used to display the Context menu to the user via a call to
TrackPopupMenu(). Finally, the point at which the Context menu was invoked is recorded.
The diagram-based items to be deleted may be numerous, and each item type will have its own dele-
tion function: here, DeleteBlock() is called to perform block deletion where the integer variable,
“item_deleted”, denotes whether an entity was deleted. Hence, add a public member function to the
CDiagramEngDoc class with the prototype, int CDiagramEngDoc::DeleteBlock(void),
and edit the code as shown.
int CDiagramEngDoc::DeleteBlock()
{
int count = 0; // counter
int delete_blk = 0; // blk deletion flag
double dist; // Euclidean dist bw. block posn and point posn.
double width; // block width
CPoint blk_posn; // block posn.
CPoint point; // local point var.
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CBlock*>::iterator it_blk; // iterator
list<CBlock*>::iterator it_er = NULL; // element to erase
// Get the point at which the context menu was invoked
point = m_ptContextMenu; // init a local copy.
// Print msg.
//sMsg.Format(“\n CDiagramEngDoc::DeleteBlock(), point (x,y) =
(%d,%d)\n”, point.x, point.y);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Get a copy of the blk_list in the system model
list<CBlock*> &blk_list = GetSystemModel().GetBlockList(); // MUST BE
A REFERENCE!
// Iterate through the list to find which item to delete.
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
blk_posn = (*it_blk)->GetBlockPosition();
width = (*it_blk)->GetBlockShape().GetBlockWidth();
Constructing Connections 133
The DeleteBlock() function iterates through the list of blocks to determine whether the mouse
cursor point, “point”, or equivalently, the Context menu point, “m_ptContextMenu”, was over a
block: a block deletion flag, “delete_blk”, is used to denote whether a block is to be deleted. This
is then checked in the following conditional statement: “if(delete_blk == 1)”. The iterator “it_er”
should not be used in the if statement as follows, “if(it_er != NULL)”, since it could be nonnull, but
a block may not have been marked for deletion: in that case, a block would be deleted erroneously.
Hence, an integer or Boolean flag should be used rather than a pointer.
GetBlockList() returns a reference: hence, “blk_list” should be preceded with an ampersand
“&” upon its declaration, and in this case its simultaneous assignment. This block-list-reference then
refers to the “m_lstBlock” member variable in the CSystemModel class: so whatever happens to
“blk_list” locally in DeleteBlock() will then be reflected at the CSystemModel-class level in
“m_lstBlock”.
Upon deletion of a block in the list, “blk_list”, or effectively in the CSystemModel list,
“m_lstBlock”, the iterator “it_er” should be dereferenced (*it_er) and “delete” called to delete
the block pointed to by “it_er”. Thereafter, erase(it_er) is called to delete the element in
the list at offset “it_er” that held the block. This ensures proper deletion of what is in the list
and that the size of the list is reduced by one. This may be verified as shown by calling size()
upon the block list.
134 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CDiagramEngView::OnSetItemProperties()
{
// TODO: Add your command handler code here
// DiagramEng (start)
// DiagramEng (end)
}
CSystemModel::∼CSystemModel(void)
{
// Delete block list
DeleteBlockList();
// Delete connection list
DeleteConnectionList();
}
Now, add a public member function to the CSystemModel class with the prototype,
void CSystemModel::DeleteBlockList(void), and edit the function as shown.
void CSystemModel::DeleteBlockList()
{
list<CBlock*>::iterator it_blk;
// Delete block list
for(it_blk = m_lstBlock.begin(); it_blk != m_lstBlock.end(); it_blk++)
{
delete (*it_blk); // delete what it_blk is pointing to:
i.e. deref the it_blk ptr and delete the ptr-to-CBlock.
}
m_lstBlock.clear();
}
In addition, add a public member function to the CSystemModel class with the prototype, void
CSystemModel::DeleteConnectionList(void), and edit the function as shown in the
following.
void CSystemModel::DeleteConnectionList()
{
list<CConnection*>::iterator it_con;
Constructing Connections 135
Further functionality, concerning the deletion of drawing objects, e.g., blocks, and connections, will
be added in later sections. In addition, the setting of item properties, selected with the right mouse
button, will be added through the use of dialog windows.
5.4 SUMMARY
Initially, an exploration is made in Appendix B to construct and draw connection objects with
attached arrowheads that indicate the direction of intended signal flow. The mathematical deriva-
tion of the global position vectors of the arrowhead vertices shows that the arrowhead will be cor-
rectly oriented with a change in orientation of the connection object.
The CConnection class was augmented by the following: (1) providing a new constructor func-
tion taking two CPoint arguments defining the connection object, (2) introducing a function to add
a connection to the list, “m_lstConnection”, and (3) adding a method to retrieve the connection list
by reference. Event-handler functions were added to the CView-derived class to set the points of the
connection (OnLButtonDown()), to dynamically draw the connection (OnMouseMove()) and to
add the connection to the list (OnLButtonUp()). The actual drawing of connections is initiated
by the CView-based OnDraw() method, which calls the DrawSystemModel() function that iter-
ates through the list of blocks and connections calling DrawBlock() and DrawConnection()
upon the appropriate pointers, wherein the latter, DrawArrowHead(), is called to draw the
connection-based arrowhead.
A Context menu is introduced and the point at which it is invoked is stored in the CDiagramEngDoc
class, such that event-handler functions, associated with the menu entries, may use it to contex-
tually determine the appropriate action to take upon the selected diagram object. Item deletion
(OnDeleteItem()) and CSystemModel object destruction are also added to the project.
REFERENCES
1. Chapman, D., Teach Yourself Visual C++ 6 in 21 Days, Sams Publishing, Indianapolis, IN, 1998.
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
6 Moving Blocks
and Connections
6.1 INTRODUCTION
At present, both blocks and simple connections may be drawn on the palette but they cannot be
interactively moved by the user or connected together to form a system model. Here, an object of
the CRectTracker class is instantiated and used to move a diagram item [1]. The topics presented
include the moving of blocks and connection-based head, tail, and bend points, the snapping and
unsnapping of connection end points to and from block ports, inserting and deleting connection-
based bend points, and the deletion of whole connection objects.
137
138 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// DiagramEng (start)
// Capture the mouse so no other apps. can get it.
SetCapture();
// Save the pt that the cursor is at, upon left-btn-down,
i.e. init member var with incoming CPoint var.
m_ptPrevPos = point; // Init the prev. pt, to be used in
OnMouseMove(), to the current point
m_ptOrigin = point; // Init the origin of the ensuing line,
to be drawn in OnMouseMove(), to be the starting pt.
// DiagramEng (end)
CView::OnLButtonDown(nFlags, point);
}
New functionality needs to be added to this function to differentiate between two actions
triggered by a left-mouse-button click event: (1) the tracking or moving of a block or connec-
tion object (see TrackItem()) or (2) the construction of a connection object. Hence, modify
the CDiagramEngView::OnLButtonDown() function as shown in bold in the following,
such that upon a left-mouse-button-down event, either a CRectTracker object is used (within
TrackItem()), or a connection constructed, depending on the returned value of the tracker
flag, “tracker_flag”.
// DiagramEng (start)
int tracker_flag; // 0 => no tracker and hence a connector,
1 => tracker and hence no connector
//AfxMessageBox(“\n CDiagramEngView::OnLButtondown()\n”, MB_OK, 0);
// Assume an item is being tracked: if so tracker_flag = 1, if not:
tracker_flag = 0.
tracker_flag = GetDocument()->TrackItem(point, this); // this is a
ptr-to-CWnd, i.e. CWnd *pWnd.
// If nothing was tracked, i.e. tracker_flag = 0, then record points
to draw and construct a connector.
if(tracker_flag == 0)
{
// Capture the mouse so no other apps. can get it.
SetCapture();
// Save the pt that the cursor is at, upon left-btn-down,
i.e. init member var with incoming CPoint var.
m_ptPrevPos = point; // Init the prev. pt, to be used in
OnMouseMove(), to the current point
m_ptOrigin = point; // Init the origin of the ensuing line,
to be drawn in OnMouseMove(), to be the starting pt.
}
// DiagramEng (end)
CView::OnLButtonDown(nFlags, point);
}
Moving Blocks and Connections 139
The TrackItem() function is the main function that processes various item tracking calls, e.g., the
tracking of blocks and connection-based head, tail, and bend points (to be added later). The flag-like
variable, “tracker_flag”, indicates whether an item was tracked (1) or not (0).
In the OnLButtonDown() function shown earlier, TrackItem(point, this) is called
passing in the CPoint argument, “point”, which is the current mouse cursor position, and the “this”
pointer, pointing to the object upon which the function is called, i.e., a pointer-to-CWnd. These
variables are then used in the various item tracking actions.
Now, add a public member function to the CDiagramEngDoc class with prototype, int
CDiagramEngDoc::TrackBlock(CPoint point, CWnd *pWnd), passing in the CPoint
and pointer-to-CWnd arguments, and edit it as shown in the following.
The TrackBlock() function iterates through the list of blocks and determines whether a block
is selected by the user through a left-button-down event. If the point at which the click occurred
is sufficiently close to the block, then the CRectTracker object, “m_RectTracker” is set up, using
SetRect() and then tracked using Track(), whereupon release of the left mouse button, the
movement action is terminated and the block position updated using SetBlockPosition().
If the left-button-down event is not sufficiently close to a block, then the action is deemed to have
taken place over white space on the palette and the drawing of a connection object intended.
Initially, the CPoint member variable, “m_ptBlockPosition”, is updated with the incoming argu-
ment, and then the vectors of input and output ports are iterated over and the port positions recalcu-
lated due to the change in the position of the block on which they reside.
Now add a public member function to the CDiagramEngDoc class, with the prototype,
int CDiagramEngDoc::TrackConnectionEndPoint(CPoint point, CWnd *pWnd),
and edit the function as shown.
int CDiagramEngDoc::TrackConnectionEndPoint(CPoint point, CWnd *pWnd)
{
int delta = (int)(0.25*m_dDeltaLength); // integer increment
int tracker_flag = 0;
double disc_r = 0.1*m_dDeltaLength;
double dist_to_tail;
142 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
double dist_to_head;
CPoint bottom_right;
CPoint head_pt;
CPoint tail_pt;
CPoint top_left;
list<CConnection*>::iterator it_con;
list<CConnection*> con_list = GetSystemModel().GetConnectionList();
// Iterate through connection list
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
// Get tail and head points of connection
tail_pt = (*it_con)->GetConnectionPointTail();
head_pt = (*it_con)->GetConnectionPointHead();
// Determine Euclidean dist bw point and tail and head pts
dist_to_tail = sqrt(pow(tail_pt.x − point.x,2) +
pow(tail_pt.y − point.y,2) );
dist_to_head = sqrt(pow(head_pt.x − point.x,2) +
pow(head_pt.y − point.y,2) );
// If the tail pt is clicked
if(dist_to_tail <= disc_r)
{
top_left.x = tail_pt.x − delta;
top_left.y = tail_pt.y − delta;
bottom_right.x = tail_pt.x + delta;
bottom_right.y = tail_pt.y + delta;
// Set the rect tracker’s position
m_RectTracker.m_rect.SetRect(top_left.x, top_left.y,
bottom_right.x, bottom_right.y);
// Track the mouse’s position till left mouse button up
tracker_flag = m_RectTracker.Track(pWnd, point, TRUE);
// 0 => item not tracked, 1 => item was tracked
// Get the new tracker position and update the new connection
end pt position with the tracker position
tail_pt = m_RectTracker.m_rect.CenterPoint();
(*it_con)->SetConnectionPointTail(tail_pt);
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should redraw.
break;
}
// If the head pt is clicked
if(dist_to_head <= disc_r)
{
top_left.x = head_pt.x − delta;
top_left.y = head_pt.y − delta;
bottom_right.x = head_pt.x + delta;
bottom_right.y = head_pt.y + delta;
// Set the rect tracker’s position
m_RectTracker.m_rect.SetRect(top_left.x, top_left.y,
bottom_right.x, bottom_right.y);
Moving Blocks and Connections 143
Add a public constant member function to the CConnection class with prototype,
CPoint CConnection::GetConnectionPointTail(void) const, to return the con-
nection tail point, “m_ptTail”, and edit the code as shown.
Add a public member function to the CConnection class with the prototype, void CConnection::
SetConnectionPointHead(CPoint new_pt), and edit the code as shown to set the connection
head point.
144 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Add a public member function to the CConnection class with the prototype void CConnection::
SetConnectionPointTail(CPoint new_pt), and edit the code as shown to set the connection
tail point.
if(from_port != NULL)
{
(*it_con)->SetRefFromPort(from_port);
}
}
(*it_con)->SetConnectionPointTail(tail_pt);
…
break;
}
// If the head pt is clicked
if(dist_to_head <= disc_r)
{
…
head_pt = m_RectTracker.m_rect.CenterPoint();
// Check to snap connector to port
if( (*it_con)->GetRefToPort() == NULL)
{
to_port = SnapConnectionHeadPointToPort(head_pt);
// head_pt passed by ref. and may be updated.
if(to_port != NULL)
{
(*it_con)->SetRefToPort(to_port);
}
}
(*it_con)->SetConnectionPointHead(head_pt);
…
break;
}
}// end for
// Return flag indicating if an item was tracked: 0 => not tracked,
1 => tracked.
return tracker_flag;
}
The developer will have noticed the two calls to get the reference-from-port, “m_pRefFromPort” and
reference-to-port, “m_pRefToPort”, in the previous code. Hence, add a public member function to
the CConnection class with the prototype, CPort *CConnection::GetRefFromPort(void),
and edit it as shown.
CPort* CConnection::GetRefFromPort()
{
return m_pRefFromPort;
}
Add a public member function to the CConnection class with the prototype, CPort *CConnection::
GetRefToPort(void), and edit it as shown.
CPort* CConnection::GetRefToPort()
{
return m_pRefToPort;
}
146 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
In addition, add a public member function to the CConnection class to set the reference-from-port,
with the prototype, void CConnection::SetRefFromPort(CPort *from_port), and
edit it as shown.
void CConnection::SetRefFromPort(CPort *from_port)
{
m_pRefFromPort = from_port;
}
Add a public member function to the CConnection class to set the reference-to-port, with
the prototype, void CConnection::SetRefToPort(CPort *to_port), and edit it
as shown.
void CConnection::SetRefToPort(CPort *to_port)
{
m_pRefToPort = to_port;
}
Now, to snap the connection-based head and tail points to their respective input and output ports,
two more functions are required, as called in the TrackConnectionEndPoint() function
provided earlier. Hence, add a public member function to the CDiagramEngDoc class, with the
prototype, CPort *CDiagramEngDoc::SnapConnectionTailPointToPort(CPoint
&tail_pt), and edit it as shown.
CPort* CDiagramEngDoc::SnapConnectionTailPointToPort(CPoint &tail_pt)
{
// Passing in a point by reference, and returning a prt-to-CPort
“m_pRefFromPort”.
double disc_r = 0.1*m_dDeltaLength;
double dist_to_port;
CPoint port_posn;
list<CBlock*>::iterator it_blk;
list<CBlock*> blk_list = GetSystemModel().GetBlockList();
vector<CPort*> vec_output_ports;
vector<CPort*>::iterator it_port;
// Iterate through block list
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
vec_output_ports = (*it_blk)->GetVectorOfOutputPorts();
for(it_port = vec_output_ports.begin(); it_port !=
vec_output_ports.end(); it_port++)
{
port_posn = (*it_port)->GetPortPosition();
dist_to_port = sqrt(pow(tail_pt.x − port_posn.x,2) +
pow(tail_pt.y − port_posn.y,2) );
if(dist_to_port <= disc_r)
{
tail_pt = port_posn;
return (*it_port); // return CPort*, i.e. a from_port
}
}
}
// Return 0 if no “m_pRefFromPort” returned earlier
return 0;
}
Moving Blocks and Connections 147
This function operates in a similar manner to the previous one, but here, as a head point is to be
attached to an input port, the vector of input ports is iterated over and the position of the port com-
pared to that of the head point: if they are coincident, the address of the input port is returned.
The developer will notice that the update functions are called on the pointer-to-parent-system-model,
“m_pParentsystemModel”: a member variable of the CBlock class. If this pointer member variable
were not present, then the system model object, “m_SystemModel”, would have to be retrieved
explicitly, by first calling GetDocumentGlobalFn(), followed by GetSystemModel(), which
would require the inclusion of the “DiagramEngDoc.h” header file at the top of the “Block.cpp”
source file. The power of the pointer, “m_pParentSystemModel”, is particularly important here:
a block can “know” the model to which it belongs quite easily.
Finally, to update the head and tail points to the ports to which they are snap-associated, two
new functions need to be added to the CSystemModel class that iterate through the connection list,
“m_lstConnection”, retrieve the port-reference-variable, i.e., the port address, and compare this to
the pointer-to-CPort argument, “port”, passed into the function.
Add a public member function to the CSystemModel class, with the prototype, void
CSystemModel::UpdateConnectionPointHead(CPort *port), and edit it as shown.
void CSystemModel::UpdateConnectionPointHead(CPort *port)
{
list<CConnection*>::iterator it_con;
// Iterate through the list of connections to check for connection
end pt. updating due to port associations
Moving Blocks and Connections 149
Add a public member function to the CSystemModel class, with the prototype, void
CSystemModel::
UpdateConnectionPointTail(CPort *port), and edit it as shown.
6.4.1 Augment the Context Menu with an Insert Bend Point Operation
Navigate to the Menu Designer and select the menu with ID: IDR_CONTEXT_MENU. Drag the
blank entry at the bottom of the Context menu list, i.e., the entry below Set Properties, and place
this in between Delete Item and Set Properties. Add the menu entry listed in Table 6.1, with the ID,
IDM_INSERT_BEND_POINT, named Insert Bend Point.
To attach functionality to the event message for the new entry, invoke the ClassWizard: if the
dialog box shown in Figure 6.1 appears, just click cancel and ignore this message for now.
Select the class name to be CDiagramEngDoc and choose the object ID: IDM_INSERT_BEND_
POINT. Add a function to the Command message, naming it OnInsertBendPoint() and edit
the code as shown.
void CDiagramEngDoc::OnInsertBendPoint()
{
// TODO: Add your command handler code here
// DiagramEng (start)
double delta_x;
double delta_y;
double eps = 1.0*m_dDeltaWidth;
double gradient;
double y_val;
CPoint current_pt;
CPoint head_pt;
CPoint point = m_ptContextMenu;
CPoint prev_pt;
TABLE 6.1
Context Menu Entries, IDs, Captions, and Prompts (Status Bar and Tooltips)
ID Caption Prompt
IDM_DELETE_ITEM &Delete Item Delete selected item\nDelete selected item
IDM_INSERT_BEND_POINT &Insert Bend Point Insert bend point\nInsert bend point
IDM_SET_ITEM_PROPERTIES &Set Properties Set item properties\nSet item properties
152 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 6.1 Adding a Class dialog window as a result of an attempt to attach an event-handler function to
a Context menu entry.
CPoint tail_pt;
list<CConnection*> &con_list = GetSystemModel().GetConnectionList();
list<CConnection*>::iterator it_con;
list<CPoint> local_pts_list;
list<CPoint>::iterator it_loc;
list<CPoint>::iterator it_pt;
list<CPoint>::iterator temp_it;
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Iterate through all connections
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
// Get the bend points list
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
local_pts_list = bend_pts_list;
// Augment the bend points list with the Connection’s tail_pt and
head_pt.
head_pt = (*it_con)->GetConnectionPointHead();
tail_pt = (*it_con)->GetConnectionPointTail();
local_pts_list.push_front(tail_pt);
local_pts_list.push_back(head_pt);
//sMsg.Format(“\n CDiagramEngDoc::OnInsertBendPoint(),
local_pts_list.size() = %d\n”, local_pts_list.size() );
//AfxMessageBox(sMsg, nType, nIDhelp);
// Iterate through points making up this connection after the
tail pt.
prev_pt = tail_pt;
temp_it = local_pts_list.begin();
temp_it++;
for(it_pt = temp_it; it_pt != local_pts_list.end(); it_pt++)
{
current_pt = *it_pt;
// Consider a vertical connection line segment
if(current_pt.x == prev_pt.x)
{
// Check to see if mouse point is on the vertical
connection line segment in the x domain
Moving Blocks and Connections 153
The function works as follows: the list of connections is iterated over, the current connection’s
bend point list is retrieved, the tail and head points are augmented to the bend point list, if the
mouse cursor point is on the connection line connecting two consecutive points, a bend point is
inserted between them, and, finally, the bend points list is updated. Two line segments are consid-
ered: an approximately vertical connection-line segment and a nonvertical connection-line segment.
Elementary linear geometry is used to determine whether the point, at which the Context menu is
invoked, lies on the line.
The developer will have noticed the usage of a bend-point list in the previous code, to which the
head and tail points are added, prior to the iteration of all connection-based points. Hence, add a
private member variable to the CConnection class that is a list of CPoint objects, i.e., “list<CPoint>
m_lstBendPoints”. The ClassWizard will complain with the message: “Template declarations or
definitions cannot be added.” Hence, change the type to integer, and then manually change the dec-
laration in the “Signal.h” header file.
Note that in the function OnInsertBendPoint(), to update the member variable
“m_lstBendPoints” of class CConnection, the assignment “bend_pts_list = local_pts_list” would
be incorrect, since this would just pass the address of the “local_pts_list” to the “bend_pts_list”.
Hence, an accessor method is required to pass the “local_pts_list”, containing the bend points for
the current connection, and perform explicit assignment of the member variable “m_lstBendPoints”
within the accessor function.
Hence, first, add a public accessor function to the CConnection class to return the list,
“m_lstBendPoints” by reference, with the prototype, list<CPoint>& CConnection::
GetConnectionBendPointsList(void): again, use “int &” as the return type, and then
manually change the declaration in the “Signal.h” header file, and the definition in the “Signal.
cpp” source file. Edit the function as follows.
list<CPoint>& CConnection::GetConnectionBendPointsList()
{
// Return connection’s bend points list
return m_lstBendPoints;
}
Then, add a public accessor function to the CConnection class to set the bend points
list, “m_lstBendPoints”, by passing in a list<CPoint> argument, with the prototype,
void CConnection::SetConnectionBendPointsList(list<CPoint> bend_pts_
list): again, use “int &” as the incoming argument type, and then manually change the declara-
tion in the “Signal.h” header file, and the definition in the “Signal.cpp” source file. Edit the function
as follows.
Finally add, #include <list>, followed by using namespace std; within the inclusion
guard portion of the code, in the “Signal.h” header file, and do the same at the top of the “Signal.
cpp” source file. At this point, the code should compile; however, no bend point is visible as it has
not yet been drawn.
6.4.2.1 DrawConnection()
Edit the DrawConnection() function as indicated in the following, to perform the following actions:
(1) initialize all local variables, (2) move to the tail point, (3) draw lines connecting all points from the
tail point, through the bend points and finally to the head point, (4) draw an ellipse around each bend
point, and (5) draw an arrowhead in the direction from the tail point or final bend point, to the head point.
// Set the current bend pt. as the pt prior to the head_pt: used
for DrawArrowHead().
final_pt = *it_pt;
}
// Line to head pt.
pDC->LineTo(m_ptHead);
// Iterate through the bend pts. drawing ellipses at each pt.
for(it_pt = m_lstBendPoints.begin(); it_pt != m_lstBendPoints.end();
it_pt++)
{
// Draw an ellipse about the bend pt.
bend_pt = *it_pt;
top_left.x = (int)(bend_pt.x − length*0.5);
top_left.y = (int)(bend_pt.y − length*0.5);
bottom_right.x = (int)(bend_pt.x + length*0.5);
bottom_right.y = (int)(bend_pt.y + length*0.5);
pDC->Ellipse(top_left.x, top_left.y, bottom_right.x,
bottom_right.y);
}
// Reset the prev. pen
pDC->SelectObject(pOldPen);
// Draw arrowhead
DrawArrowHead(pDC, final_pt, m_ptHead);
}
// Set length
dDeltaLength = GetDocumentGlobalFn()->GetDeltaLength();
length = 0.15*dDeltaLength;
// Print msg.
sMsg.Format(“\n CConnection::DrawArrowHead(), d = %lf, h = %lf\n”, d, h);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Length of vecs u and v
v[0] = 1.0; // x cmpt. of unit vector [1,0]
v[1] = 0.0; // y cmpt. of unit vector [1,0]
u[0] = head.x − tail.x; // length in x direc of connection vector
u[1] = head.y − tail.y; // length in y direc of connection vector
length_u = sqrt(pow(u[0],2) + pow(u[1],2) );
length_v = sqrt(pow(v[0],2) + pow(v[1],2) );
// Angle between vecs u and v
theta = acos( (u[0]*v[0] + u[1]*v[1])/(length_u*length_v) );
if(u[1] < 0)
{
theta = −theta; // negate theta if y-cmpt of u < 0, since acos
result is an element from [0,pi] radians ONLY (non-neg).
}
// Draw arrowhead
pDC->MoveTo(A);
pDC->LineTo(B);
pDC->LineTo(C);
pDC->LineTo(A);
// Prepare for filling the polygon
CBrush NewBrush(RGB(0,0,0) ); // create a new brush
vertices[0] = A;
vertices[1] = B;
vertices[2] = C;
// Select the new brush
pBrush = pDC->SelectObject(&NewBrush);
// Fill the polygon
pDC->Polygon(vertices, 3);
// Reset the prev. pen and brush
pDC->SelectObject(pOldPen);
pDC->SelectObject(pBrush);
}
When the user runs the program and attempts to place a connection with length, l < lmin, the message
box shown in Figure 6.2 appears reporting the start and end points of the connection, the connection
length and that the connection was not constructed: the AfxMessageBox() calls may be com-
mented out later.
FIGURE 6.2 Diagnostic warning indicating an invalid length for the connection object.
160 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Hence, add code to the TrackItem() function as shown in bold in the following to call
TrackConnectionBendPoint() and check the returned value of the flag-like “tracker_flag”
variable.
Now, add a public member function to the CDiagramEngDoc class with the prototype, int
CDiagramEngDoc::TrackConnectionBendPoint(CPoint point, CWnd *pWnd),
and edit the function as shown in the following. The developer will notice that there are two for
loops: the first iterates through the list of connections in the current diagram, and the second
iterates through all the bend points on the current connection. The conditional statement checks
to see whether the bend point has actually been selected, i.e., if the cursor position at the left-
button-down event is within a disc of radius r of the center of the bend point.
{
// Get connection bend pts.
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having
been modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should
redraw.
break;
return tracker_flag;
}
Now, upon running the program, connection-based bend points may be inserted using the Context
menu and moved using the mouse. Figure 6.3 shows that mathematical models may now be inter-
actively drawn with all items aligned: (a) a linear function involving the Linear Function, Gain,
Constant, Sum, and Output blocks, and (b) a differential equation being integrated, involving the
Signal Generator, Sum, Integrator, and Output blocks, using a branch/bend point to allow the
drawing of a feedback loop.
162 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
(a) (b)
FIGURE 6.3 System models: (a) a linear equation representation and (b) a differential equation representation.
void CDiagramEngDoc::OnDeleteItem()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int item_deleted = 0;
// Delete a block
item_deleted = DeleteBlock();
if(item_deleted == 1)
{
return;
}
// DiagramEng (end)
}
Moving Blocks and Connections 163
Now add a public member function to the CDiagramEngDoc class to delete a connection-based
bend point, with the prototype, int CDiagramEngDoc::DeleteConnectionBendPoint
(void), and edit the code as shown in the following.
int CDiagramEngDoc::DeleteConnectionBendPoint()
{
int count = 0; // counter
int delete_bp = 0; // bend pt. deletion flag
double dist; // Euclidean dist bw. bend
pt. posn and mouse point posn.
double disc_r = 0.8*m_dDeltaLength*0.5; // disc or radius r
CPoint bend_pt_posn; // bend pt. posn.
CPoint point; // local point var.
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for
the msg.
list<CConnection*>::iterator it_con; // connection iterator
list<CPoint>::iterator it_pt; // bend pt iterator
list<CPoint>::iterator it_er = NULL; // erase iterator
(element to erase)
// Get the point at which the context menu was invoked
point = m_ptContextMenu; // init a local copy.
// Print msg.
//sMsg.Format(“\n CDiagramEngDoc::DeleteConnectionBendPoint(), point
(x,y) = (%d,%d)\n”, point.x, point.y);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Get a copy of the bend_pts_list in the system model
list<CConnection*> &con_list = GetSystemModel().GetConnectionList();
// MUST BE A REFERENCE!
// Iterate through the connection list
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
// Get the bend points list
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
// Iterate through the bend pts. list
delete_bp = 0; // init delete_bp = 0 since, at present no
bend_pt is to be deleted
for(it_pt = bend_pts_list.begin(); it_pt != bend_pts_list.end();
it_pt++)
{
bend_pt_posn = *it_pt;
// Check whether mouse cursor within disc_r of bend pt.
dist = sqrt(pow( (bend_pt_posn.x − point.x),2) +
pow( (bend_pt_posn.y − point.y),2) );
if(dist <= disc_r)
{
//sMsg.Format(“\n CDiagramEngDoc::
DeleteConnectionBendPoint(), blk(x,y) = (%d,%d),
point(x,y) = (%d,%d)\n”, blk_posn.x, blk_posn.y,
point.x, point.y);
//AfxMessageBox(sMsg, nType, nIDhelp);
164 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Note that “break” is required in the first distance-checking conditional (if) statement if the mouse
cursor position was close enough to a bend point. This is so, since after the bend point is found,
the for loop concerning bend points for the particular connection should be terminated. Note also
that delete should not be called to delete the CPoint object, i.e., the statement, “delete (*it_pt)” is
not required in the second conditional (if) statement in the DeleteConnectionBendPoint()
function: “erase(it_er)” will suffice.
Hence, add a new section to the OnDeleteItem() function, as shown in bold in the following,
that calls DeleteConnection().
void CDiagramEngDoc::OnDeleteItem()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int item_deleted = 0;
// Delete a block
item_deleted = DeleteBlock();
if(item_deleted == 1)
{
return;
}
// DiagramEng (end)
}
Now add a public member function to the CDiagramEngDoc class with the prototype, int
CDiagramEngDoc::DeleteConnection(void), and edit the code as shown.
int CDiagramEngDoc::DeleteConnection()
{
int delete_con = 0;
double disc_r = 0.5*m_dDeltaLength;
double dist;
CPoint current_pt;
CPoint head_pt;
CPoint midpoint;
CPoint point = m_ptContextMenu;
CPoint prev_pt;
CPoint tail_pt;
list<CConnection*> &con_list = GetSystemModel().
GetConnectionList();
list<CConnection*>::iterator it_con;
list<CConnection*>::iterator it_er;
list<CPoint> local_pts_list;
list<CPoint>::iterator it_pt;
list<CPoint>::iterator temp_it;
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
166 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
//count = m_SystemModel.GetBlockList().size();
//sMsg.Format(“\n CDiagramEngDoc::DeleteConnection(), size =
%d\n”, count);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
// Set as modfied and redraw the doc.
SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should redraw.
// Return a flag indicating whether an item was deleted
return delete_con;
}
6.6 SUMMARY
The movement of items, including blocks, connection objects and their head, tail, and bend points,
is performed using a CRectTracker object and its related functions, SetRect(), Track(), and
CenterPoint(). Connection-based end points, i.e., head and tail points, can then be moved
and snapped to block input and output ports, respectively. Functionality to unsnap the end points
is then added to allow the user to disassociate or break the connection from the ports to which
the connection may be attached. Bend points were then inserted on a connection line, using an
168 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
entry on the Context menu, and moved or tracked with the CRectTracker object. An interac-
tion problem concerning the left-button-down action, resulting in invisible connections, was also
resolved. Finally, the deletion of connection-based bend points and whole connection objects was
implemented.
REFERENCES
1. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
2. Microsoft Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
7 Automatic Block Placement
7.1 INTRODUCTION
Currently, upon clicking the Common Blocks toolbar, the bottom toolbar shown in Figure 7.1, the
OnBlockGroupName() command-event message handling function of the CDiagramEngDoc
class is called, where Group represents either of the Continuous, Math Operations, Sink, Source,
and Subsystem group classifications, and Name represents the particular name of the block:
e.g., OnBlockSourceConst() is the command-event message handling function for the click-
ing of the Constant block toolbar button, representing the Constant block of the Source group. This
function then calls ConstructBlock() that filters an enumerated type to call the particular block
construction function, e.g., ConstructConstantBlock(). However, prior to calling the block’s
actual constructor, an automatic position assignment function, AssignBlockPosition() is
called to automatically determine the position of the block to be placed on the palette. This was
initially used in the developmental process to avoid unnecessary interactive complexity.
The software developer may choose between three different courses of action to take here: (1) retain
the existing automatic block placement functionality provided by AssignBlockPosition(),
(2) enhance the AssignBlockPosition() function with more flexible block positioning, or
(3) introduce an interactive mechanism such that if the CDiagramEngDoc::ConstructBlock()
function is invoked via a particular toolbar button command-event message handling function, then
the position of the block will be that of the mouse pointer upon a subsequent left-button-down event
to locate the block on the palette.
The following first set of instructions concern option three given earlier and indicate how the
new interactive point-based block-position-assignment mechanism may be implemented if desired.
However, this does require significant alteration, and it was decided that option two given earlier,
concerning the implementation of an enhanced AssignBlockPosition() function, would be
preferable (see Section 7.3).
169
170 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 7.1 The Main-frame-based toolbar (top), Common Ops. toolbar (middle), and the Common Blocks
toolbar (bottom) of the DiagramEng application used to add blocks to the model.
Step 3: Edit the CDiagramEngDoc toolbar button command-event handler functions, e.g.,
OnBlockSourceConst(), as follows:
1. Set the “m_eBlockType” variable with the type of the block that needs to be constructed,
e.g., “eConstBlock”.
2. Set the “m_BtnClick” variable with the value “1” indicating that a button was clicked.
3. Comment out the call, “pDoc->ConstructBlock(e_block_type)”, as an extended
version of this would be performed in the CDiagramEngView::OnLButtonDown()
function.
Step 4: Edit the OnLButtonDown() function as shown in the following hypothetical code excerpt.
First, the value of the “m_BtnClick” variable is retrieved using the statement: GetDocument()->
GetButtonClick(). Then, the value of the “m_eBlockType” variable is obtained:
GetDocument()->GetBlockType(). Finally, the ConstructBlock(m_eBlockType,
point) is called, passing in both the existing EBlockType variable and the new mouse position,
CPoint, “point” variable. The passing of the variable “m_eBlockType” from OnLButtonDown()
is somewhat excessive, since the variable “m_eBlockType” and the function ConstructBlock()
both belong to the CDiagramEngDoc class: but this is done here to conform to the existing struc-
ture, since a Block Library Dialog window may be used (see the following).
void CDiagramEngView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
// DiagramEng (start)
int tracker_flag; // 0 => no tracker and hence a connector, 1 =>
tracker and hence no connector
// Check to see whether toolbar button has been clicked.
if(GetDocument()->GetButtonClick() == 1)
{
ConstructBlock(GetDocument()->GetBlockType(), point);
GetDocument()->SetButtonClick(0);
}
else
{
// Assume an item is being tracked: if so tracker_flag = 1, if
not: tracker_flag = 0.
Automatic Block Placement 171
CView::OnLButtonDown(nFlags, point);
}
Step 5: Amend the ConstructBlock() function as follows: (1) change the for-
mal parameter list to take two arguments, the EBlockType and CPoint variables, i.e.,
ConstructBlock(EBlockType e_block_type, CPoint point), and (2) pass the
CPoint “point” variable into the ConstructParticularBlock() functions, where Particular
denotes the particular block construction function: e.g., for the Constant block construction func-
tion, ConstructConstantBlock(point) should be called. This will require changing the
parameter lists of these Particular functions to take the CPoint, “point”, argument.
Step 6: Comment out the AssignBlockPosition() call in the ConstructParticularBlock()
functions, since the block position is now that of the “point” variable, i.e., the mouse cursor
location that is passed into the function ConstructParticularBlock(). Assign to the local
variable, “block_posn”, the incoming CPoint argument, i.e., “block_posn = point”. The block will
then be constructed correctly.
However, there is a slight additional complication: if the user decides to add blocks to the
palette using the Block Library Dialog window as shown in Figure 7.2, then the Add All Blocks
button will be used, and the CBlockLibDlg::OnBlocklibdlgBtnAddallblocks() com-
mand event message handling function is invoked. Within this function, ConstructBlock()
is called passing in the enumerated type EBlockType argument. However, if the interactive
changes mentioned earlier were to have been made, then the call to ConstructBlock() would
have to take both the enumerated type argument and a CPoint block-position argument. In this
case, to have both the interactive mouse-based positioning as a result of toolbar button usage,
and the automatic block position assignment functionality of AssignBlockPosition(), a
local CPoint block-position, “block_posn”, variable would need to be declared within the Add
All Blocks function, CBlockLibDlg::OnBlocklibdlgBtnAddallblocks(), and the
call pDoc->GetBlockPosition(block_posn) would need to be made to retrieve the
block position. Then the call pDoc->ConstructBlock(e_block_type, block_posn)
would pass the necessary variables to the ConstructParticularBlock() functions for
correct ensuing block construction.
These alterations can be made, but it appears to be a lot of work for the additional interactive
flexibility of being able to click a toolbar button and then click the palette at the desired location for
172 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 7.2 Block Library Dialog window used to add blocks to the project.
initial block placement: in fact, on implementation, there may be other unforeseen difficulties. The
disadvantage of clicking a button and then clicking the palette is that an additional mouse-button
click is required per toolbar button click, for block placement. The alternative is to simply click the
toolbar button and for the block to be positioned automatically without overlapping any existing
blocks on the palette: it is the automatic nonoverlapping behavior that should be augmented to the
AssignBlockPosition() function as discussed later, since it requires no structural changes to
the project.
(a) (b)
(c) (d)
FIGURE 7.3 Automatic block placement: (a) blocks added to system model, (b) two blocks deleted,
(c) a constant block added (earliest available position), and (d) an output block added (second available position).
// Check to see whether there are no blocks: if so, set the first
block and return.
if(n_blocks == 0)
{
block_posn.x = left_spacing + (n_blocks%n_blks_in_row)*(width +
blk_spacing) + 0.5*width; // CofM of block at width*0.5
block_posn.y = top_spacing + (n_blocks/n_blks_in_row)*(height +
blk_spacing) + 0.5*height; // CofM of block at height*0.5
return;
}
if(too_close == 0)
{
posn_found = 1;
block_posn = trial_blk_posn;
break;
}
}// end for i
}// end while
}
7.4 SUMMARY
Two different methods are presented for the placement of blocks on the palette: (1) a hypothetical
interactive block positioning method and (2) an improved automatic block positioning method. The
former method would place a selected block at the (user-defined) point of a mouse-click left-button-
down event on the palette. The latter process places blocks in an array, across the rows and down the
columns, in the earliest available position on the palette such that the blocks are sufficiently spaced
apart. The second method was implemented since it required the fewest changes to the existing
program structure.
8 Connection-Based Bend Points
8.1 INTRODUCTION
The movement of blocks with connection-based head and tail points attached to input and output
ports was introduced in Chapter 6. Connection tail points can not only be connected to a block
output port but also to another connection’s bend point to allow the formation of a feedback loop in
a model diagram. The topics covered here are (1) the attachment of a secondary connection’s tail
point to a primary connection’s bend point, (2) the deletion of a bend point to which a secondary
connection is attached, (3) the deletion of a whole connection with bend points to which other con-
nections are attached, (4) the insertion of multiple bend points, (5) automatic attachment of connec-
tion end points to ports and bend points upon initial construction, and (6) the deletion of blocks with
attached connections.
177
178 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 8.1 Primary connection with four bend points to which two secondary connection tail end points
are attached.
// Get the new tracker position and update the new connection end
pt position with the tracker position
tail_pt = m_RectTracker.m_rect.CenterPoint();
// Check to snap connector to port
if( (*it_con)->GetRefFromPort() == NULL) // If not already a
ref_from port
{
from_port = SnapConnectionTailPointToPort(tail_pt);
// tail_pt passed by ref. and may be updated.
if(from_port != NULL)
{
(*it_con)->SetRefFromPort(from_port);
}
}
else // If already a ref-from-port, then the intention on moving
the end pt. is to break the assoc. with the port.
{
(*it_con)->SetRefFromPort(NULL);
}
(*it_con)->SetConnectionPointTail(tail_pt);
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should redraw.
break;
}
…
}
First, a check is made to determine whether the connection object has a reference-from-port, if
not, then an attempt is made to snap the tail point to an output port, and if this is successful, the
“from_port” is set to be the output port from which the tail point then emanates, and the tail point
position is adjusted to be that of the output port position.
Later, if the block to which the connection object is attached is moved, the connection object tail
point associated with the block’s output port moves when the block and hence its port moves. The
TrackBlock() function allows the user to move a block to a different position, and inside this function a
call to SetBlockPosition() is made, wherein the vector of input and output ports are iterated over and
the ports repositioned according to the block movement. However, after the ports have been positioned,
a call is made to UpdateConnectionPointHead() and UpdateConnectionPointTail().
In these functions if the reference-to-port variable, “m_pRefToPort” and reference-from-port vari-
able, “m_pRefFromPort”, match the input pointer-to-CPort argument, then the head and tail points are
assigned the input and output port positions, respectively.
Connection-Based Bend Points 179
The development to attach a connection tail point to a bend point and allow the tail point to
subsequently move when the bend point to which it is attached is moved is very similar to the
attachment of a connection tail point to a block output port described earlier. The general steps to
perform this attachment are provided in the following list, while specific details are provided in the
ensuing sections:
{
from_port = SnapConnectionTailPointToPort(tail_pt);
// tail_pt passed by ref. and may be updated.
if(from_port != NULL)
{
(*it_con)->SetRefFromPort(from_port);
}
}
else // If already a ref-from-port, then the intention on moving the
end pt. is to break the assoc. with the port.
{
(*it_con)->SetRefFromPort(NULL);
}
(*it_con)->SetConnectionPointTail(tail_pt);
// Check to snap connector to bend point
if( (*it_con)->GetRefFromPoint() == NULL) // If not already a
ref_from point
{
from_point = SnapConnectionTailPointToBendPoint(tail_pt);
// tail_pt passed by ref. and may be updated.
if(from_point != NULL)
{
(*it_con)->SetRefFromPoint(from_point);
}
}
else // If already a ref-from-point, then the intention on moving
the end pt. is to break the assoc. with the point.
{
(*it_con)->SetRefFromPoint(NULL);
}
(*it_con)->SetConnectionPointTail(tail_pt);
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been modified to
prompt user to save
UpdateAllViews(NULL); // indicate that sys. should redraw.
break;
}
…
}
The outer loop iterates through all connection objects, and the first if statement checks to see whether
the current connection object has a valid reference-from-port variable, “m_pRefFromPoint”. If so
(else), then any tracking movement applied to the connection tail point is an intention to break
the connection from an existing bend point, and hence the “m_pRefFromPoint” is set to NULL.
However, if there is no bend point reference (if), then an attempt is made to snap the tail point to a
connection bend point through the call to SnapConnectionTailPointToBendPoint(). The
return value from this function is the address of the bend point, i.e., “m_pRefFromPoint”, to which
the tail point is to be attached, and the new tail point position is updated since it is passed by refer-
ence as an argument.
accessor methods to get and set the value of this variable. Hence, add a private member variable to
the CConnection class of type pointer-to-CPoint (CPoint *), with name “m_pRefFromPoint”.
Add a public accessor method to the CConnection class with the prototype CPoint
*GetRefFromPoint(void) to return the “m_pRefFromPoint” member variable, and edit the
code as shown.
CPoint* CConnection::GetRefFromPoint()
{
return m_pRefFromPoint;
}
Add a public accessor method to the CConnection class with the prototype void
SetRefFromPoint(CPoint *from_pt) to set the “m_pRefFromPoint” member variable,
and edit the code as shown.
(a) (b)
FIGURE 8.2 Snapping of a connection tail point to another connection’s bend point: (a) moving connection
tail point toward the bend point and (b) snapping the tail point to the bend point.
The code may be compiled and run, and a connection tail point may be snapped to a connection
bend point as shown in Figure 8.2.
In the function TrackConnectionBendPoint(), only the CPoint position of the bend point is
updated, but because the bend point and the connection tail end point that is snapped to that bend
point share the same address, any motion applied to the bend point should also apply to the tail
end point.
The problem lies in drawing the connection object. If the “m_pRefFromPoint” of the connection
object is NULL, i.e., if the connection end point is not associated with a bend point, then a connec-
tion line should be drawn from the tail point. If, however, the “m_pRefFromPoint” is not NULL,
then the connection tail end point must be associated with a connection bend point from which it
emanates, and hence the connection line should be drawn from the “m_pRefFromPoint”. Edit the
initial part of the function DrawConnection() as shown. (Later in the development the changes
to the DrawConnection() function will be supplemented, but the process is worth pursuing.)
Now, when the code is compiled and run, the tail point of one connection may be snapped to a bend
point of another, and when the bend point is moved, the tail point moves with the bend point as
shown in Figure 8.3.
In addition, in the DrawConnection() function, the call to DrawArrowHead(pDC,
final_pt, m_ptHead) uses the “final_pt” and the “m_ptHead” to determine the angular ori-
entation of the arrowhead. The developer will recall that “final_pt” is either the tail point or the final
bend point on the connection object prior to the head point. However, when a tail point is connected
to another connection object’s bend point, the tail point position must be updated if the bend point
to which it is attached is moved. Hence, code should be added that explicitly sets the value of the
CConnection member variable, “m_ptTail”.
Hence, add a public member function to the CSystemModel class with the prototype
void CSystemModel::UpdateConnectionPointTailToBendPoint(CPoint *bend_pt),
and edit it as shown in the following:
(a) (b)
FIGURE 8.3 Movement of a tail point with a bend point: (a) tail point connected to a bend point and (b) tail
point moves with the moving bend point.
Connection-Based Bend Points 185
Now, make a call from within the TrackConnectionBendPoint() function to update the con-
nection tail end point given its association to another connection’s bend point, i.e., add the code after
getting the new tracker position, and before the setting of flags, as shown in bold in the following:
int CDiagramEngDoc::TrackConnectionBendPoint(CPoint point, CWnd *pWnd)
{
…
// If the bend pt is clicked
if(dist <= disc_r)
{
// Assign rectangle setting values
top_left.x = bend_pt.x − delta;
top_left.y = bend_pt.y − delta;
bottom_right.x = bend_pt.x + delta;
bottom_right.y = bend_pt.y + delta;
// Set the rect tracker’s position
m_RectTracker.m_rect.SetRect(top_left.x, top_left.y,
bottom_right.x, bottom_right.y);
// Track the mouse’s position till left mouse button up
tracker_flag = m_RectTracker.Track(pWnd, point, TRUE);
// 0 => item not tracked, 1 => item was tracked
// Get the new tracker position and update the connection bend pt
position with the tracker position
*it_pt = m_RectTracker.m_rect.CenterPoint();
// Update any connection’s tail point if it was connected to this
bend point
m_SystemModel.UpdateConnectionPointTailToBendPoint(&(*it_pt) );
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should redraw.
break;
}// end if dist
…
}
Hence, the code that was altered (shown in bold in the following) in the DrawConnection()
function to take into consideration any connection tail points that may have been associated with a
connection bend point, i.e.,
void CConnection::DrawConnection(CDC *pDC)
{
int pen_color = RGB(0,0,0); // White = RGB(255,255,255),
Black = RGB(0,0,0).
…
// The value returned by SelectObject() is a ptr to the obj. being
replaced, i.e. an old-pen-ptr.
CPen *pOldPen = pDC->SelectObject(&lpen);
if(m_pRefFromPoint != NULL)
{
// Move to the bend_point to which the tail point is connected
(if it’s connected)
186 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
pDC->MoveTo(*m_pRefFromPoint);
}
else
{
// Move to the tail pt.
pDC->MoveTo(m_ptTail);
}
…
}
can now be replaced with the following single line (shown in bold), since now the CConnection
member variable “m_ptTail” has been updated explicitly using the call to the function
UpdateConnectionPointTailToBendPoint(). The redundant code is commented out.
These changes do not reflect a perfect design. This may be a suboptimal workaround, but allows
the code to continue to evolve in a consistent manner, i.e., consistent with previous design decisions.
1. Add a public member function to the CDiagramEngDoc class with the prototype:
void CDiagramEngDoc::DisconnectTailPointFromBendPoint(CPoint
*bend_pt).
2. Add a call to this function from within the DeleteConnectionBendPoint() func-
tion as shown in the if statement concerning bend point deletion in bold:
int CDiagramEngDoc::DeleteConnectionBendPoint()
{
…
// Delete the item in the list
188 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
if(delete_bp == 1)
{
// Unsnap any tail points connected to this bend point prior to
delete bend point
DisconnectTailPointFromBendPoint(&(*it_er) ); // pass address of
the bend_pt
// Delete element at offset it_er in list (that held the bend pt)
bend_pts_list.erase(it_er);
count = (*it_con)->GetConnectionBendPointsList().size();
//sMsg.Format(“\n CDiagramEngDoc::DeleteConnectionBendPoint(),
size = %d\n”, count);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
…
}
The code was compiled and run, but when a bend point was deleted the program crashed.
A break point was set using the debugger to be at the first “for(it_con)” loop of the
DeleteConnectionBendPoint() function as shown in the following:
int CDiagramEngDoc::DeleteConnectionBendPoint()
{
…
// Iterate through the connection list
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
…
}
…
}
It was noticed that the delete-bend-point flag, “delete_bp”, was not set to zero prior to the “for(it_pt)”
loop, and hence if a bend point was deleted in one iteration of this loop, causing the “delete_bp” flag
Connection-Based Bend Points 189
(a) (b)
FIGURE 8.4 Deletion of a bend point to which a tail end point is attached: (a) a secondary connection’s tail
end point attached to a primary connection’s bend point and (b) a disconnected tail end point after bend point
deletion.
to be set to 1, then in subsequent iterations, more delete points would be marked for deletion even if
they did not exist in the bend points list, “m_lstBendPoints”. Hence, “delete_bp = 0” was inserted
prior to the “for(it_pt)” loop as shown in bold in the following:
int CDiagramEngDoc::DeleteConnectionBendPoint()
{
…
// Iterate through the connection list
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
// Get the bend points list
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
// Iterate through the bend pts. list
delete_bp = 0; // init delete_bp = 0 since, at present no
bend_pt is to be deleted
for(it_pt = bend_pts_list.begin(); it_pt != bend_pts_list.end();
it_pt++)
{
…
}
}
…
}
Now if the code is compiled and run, a bend point may be deleted from the connection line object
and the tail point that was connected to this bend point, simply disconnects, as shown in Figure 8.4.
connection tail points attached to the bend points, then these need to be disconnected prior to
deleting the primary connection.
Add a public member function to the CDiagramEngDoc class with prototype void CDiagram
EngDoc::DisconnectTailPointsFromBendPoints(CConnection *con), and edit it
as shown in the following. Notice that the incoming argument is of type pointer-to-CConnection:
the result of dereferencing the “it_er” iterator, or equivalently, the “it_con” iterator (see the follow-
ing DeleteConnection() function).
void CDiagramEngDoc::DisconnectTailPointsFromBendPoints(CConnection *con)
{
list<CPoint>::iterator it_pt;
list<CPoint> &bend_pts_list = con->GetConnectionBendPointsList();
for(it_pt = bend_pts_list.begin(); it_pt != bend_pts_list.end();
it_pt++)
{
// Unsnap any tail points connected to this bend point prior to
delete bend point
DisconnectTailPointFromBendPoint(&(*it_pt) ); // pass address of
the bend_pt
}
}
In the if statement concerning deletion of the connection from the connection list in
the DeleteConnection() function below, make a call to the newly added function
DisconnectTailPointsFromBendPoints() as shown in bold. Here the iterator “it_er”
itself is dereferenced (“*it_er”), passing the pointer-to-CConnection object to the disconnecting
function: this is the case since “it_er” was assigned “it_con” in the prior loop concerning the con-
nection list, and dereferencing the “it_con” iterator yields the item stored in the connection list,
i.e., the pointer-to-CConnection object.
int CDiagramEngDoc::DeleteConnection()
{
…
// Delete the connection in the list
if(delete_con == 1)
{
// Disconnect all tail points from all the bend points on this
connection.
DisconnectTailPointsFromBendPoints(*it_er);
delete *it_er; // delete actual connection pointed to by
it_er
con_list.erase(it_er); // delete element at offset it_er in list
(that held the connection)
//count = m_SystemModel.GetBlockList().size();
//sMsg.Format(“\n CDiagramEngDoc::DeleteBlock(), size = %d\n”,
count);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
…
}
Now upon compiling and running the code, a primary connection object, with a bend point to which
a tail point of a secondary connection is attached, may be deleted, disconnecting the tail point, as
shown in Figure 8.5.
Connection-Based Bend Points 191
(a) (b)
FIGURE 8.5 Deletion of a primary connection to which a tail point is attached: (a) a tail point connected
to a primary connection’s bend point and (b) the disconnected tail point after the whole primary connection
object, to which it was attached, is deleted.
(a) (b)
(c) (d)
(e) (f )
(g) (h)
FIGURE 8.6 A problem of inserting a bend point: (a) bend points on a connection, (b) connections attached
to bend points, (c) bend points moved, (d) bend point inserted prior to the head, (e) bend points moved, (f) bend
point inserted just after the tail, (g) bend point moved inducing an unintentional jump of connectivity, and
(h) an unintentional disconnection of a connection object from its bend point.
Connection-Based Bend Points 193
there are any bend points on the current connection, if not then bend point insertion would be
between the connection’s tail point and head point, (3) if there are bend points, then bend point
insertion would be between either the tail point and the first bend point, or between subsequent
bend points, and (4) finally, (if there are bend points) a check is made to determine whether
a bend point should be placed between the connection’s final existing bend point and its head
point. Break statements are required to avoid unnecessary checks and potentially incorrect bend
point placement.
void CDiagramEngDoc::OnInsertBendPoint()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int inserted;
int point_on_line;
CPoint current_pt;
CPoint head_pt;
CPoint point = m_ptContextMenu;
CPoint prev_pt;
CPoint tail_pt;
list<CConnection*> &con_list = GetSystemModel().
GetConnectionList();
list<CConnection*>::iterator it_con;
list<CPoint>::iterator it_pt;
// -- ITERATE THROUGH ALL CONNECTIONS
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
inserted = 0;
// Get the bend points list
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
// Get the Connection’s tail_pt and head_pt.
head_pt = (*it_con)->GetConnectionPointHead();
tail_pt = (*it_con)->GetConnectionPointTail();
prev_pt = tail_pt;
// -- IF THERE ARE NO BEND POINTS ON THIS CONNECTION
if(bend_pts_list.size() == 0)
{
current_pt = head_pt;
point_on_line = 0;
point_on_line = CheckPointOnLineSegment(prev_pt, current_pt,
point);
if(point_on_line == 1)
{
// Create a bend pt. at the front of the list.
bend_pts_list.push_front(point);
inserted = 1;
break; // THIS BREAK EXITS THE it_con LOOP
}
}
else // -- IF THERE ARE BEND POINTS ON THIS CONNECTION
{
prev_pt = tail_pt;
194 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
This function separates the bend-point-insertion logic into two main parts: a connection with no
bend points and a connection with bend points. A check is made to determine whether the mouse
cursor, denoting the bend point position, lies on the line segment, and this test is performed through
a new function call to CheckPointOnLineSegment().
Connection-Based Bend Points 195
Hence, add a new public member function to the CDiagramEngDoc class, with the prototype
int CDiagramEngDoc::CheckPointOnLineSegment(CPoint prev_pt, CPoint
current_pt, CPoint point), and edit it as shown in the following:
The developer may compare the original OnInsertBendPoint() function introduced in Section 6.4
with the aforementioned function, noting that the function CheckPointOnLineSegment()
extracts the point-on-line-segment test for clarity and to avoid repetition. Finally, now that there is
no need for the SetConnectionBendPointsList() function, this may be deleted from the
“Signal.cpp” source and “Signal.h” header files. Now when a bend point is inserted or deleted, no
spurious side effects result.
In addition, the user will notice that the integer, abs(), and double, fabs(), forms of the abso-
lute value function are used in the aforementioned code, in the statements, “if(abs(current_pt.x −
prev_pt.x) <= eps)” and “if(fabs(point.y − y_val) <= eps)”, respectively: these should not be confused.
if(valid)
{
// Attempt snapping of connection end points to ports or
bend points
GetDocument()->SnapConnectionEndPointsAfterInitial
Construction();
}
// Make the document redraw itself using either Invalidate(TRUE)
or UpdateAllViews(NULL)
//Invalidate(TRUE);
GetDocument()->UpdateAllViews(NULL);
// Release the capture so other apps. can have access to the
mouse.
ReleaseCapture();
}
// DiagramEng (end)
CView::OnLButtonUp(nFlags, point);
}
The valid flag is “0” if the drawn connection is not of a suitable length and “1” otherwise,
whereupon a snapping of the connection’s end points is attempted for the newly constructed
connection object.
Second, the developer will notice the conditional action (“if(valid)”) taken upon
the returned value (“valid”) from the AddConnection() function. Hence, alter the
CSystemModel::AddConnection() function to return a value indicating whether the drawn
connection is valid, i.e., whether it is long enough to be deemed an intended connection object, as
shown in bold in the following. The new prototype is then int CSystemModel::AddConnection
(CPoint ptFrom, CPoint ptTo) and this is to be updated in the “SystemModel.h” header file.
void CDiagramEngDoc::SnapConnectionEndPointsAfterInitialConstruction()
{
int modified = 0; // flag indicating whether drawing is
modified
CPoint head_pt; // connection head point
CPoint tail_pt; // connection tail point
CPoint *from_point = NULL; // connection from point
CPort *from_port = NULL; // connection from port
CPort *to_port = NULL; // connection to port
list<CConnection*>::iterator it_con;
list<CConnection*> con_list = GetSystemModel().GetConnectionList();
return;
}
conditional clauses test whether the end point in question has a “from port”, “from point”, or “to port”,
and if not, a snapping to the desired port or point and a setting of the connection’s state is performed.
Now the user can draw connections linking ports of different blocks directly in one action and
also have the tail point of a connection directly snap to an existing bend point; the only requirement
is that the end points are suitably close to the port or bend point to which they are to be snapped.
However, if connection head or tail points, i.e., end points, are connected to the block’s input or out-
put ports, then these connection objects’ reference-to-port and reference-from-port port variables,
need to be reassigned to NULL prior to block deletion. The previous code is then modified as shown
in the following (in bold) to call the DisconnectEndPointFromPorts() function, passing in
a pointer-to-CBlock (“it_er” is assigned “it_blk” if the block is to be deleted), to disconnect the con-
nection object’s head and tail (end) points from the block’s ports to which they may be associated.
int CDiagramEngDoc::DeleteBlock()
{
…
// Delete the item in the list
if(delete_blk == 1)
{
// Dereference block’s ports (allowing previously connected
connections to be reassigned to new ports)
DisconnectEndPointsFromPorts(*it_er);
Connection-Based Bend Points 201
// Delete block
delete *it_er; // delete actual block pointed to by it_er
blk_list.erase(it_er); // delete element at offset it_er in list
(that held the block)
count = m_SystemModel.GetBlockList().size();
//sMsg.Format(“\n CDiagramEngDoc::DeleteBlock(), size = %d\n”,
count);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
…
}
Hence, add a public member function to the CDiagramEngDoc class taking a pointer-to-CBlock
argument with the prototype void DisconnectEndPointsFromPorts(CBlock *block),
and edit it as shown in the following (note the plural “Ports” in the function name):
In this function the vector of input and output ports are iterated over, and the function
DisconnectEndPointFromPort() is called to disconnect the tail or head point associated
with the pointer-to-CPort argument that is passed (note the singular “Port” used in the function
name). Hence, add a public member function to the CDiagramEngDoc class taking the aforemen-
tioned pointer-to-CPort argument, i.e., void DisconnectEndPointFromPort(CPort *port),
and edit the function as shown.
All the system model’s connections are iterated over, and if the reference-from-port or reference-
to-port (port reference) matches that passed in (“port”), i.e., the one from which the connection end
point should be disconnected, then the reference-from-port or reference-to-port is set to NULL.
This disconnects the connection head or tail points, i.e., end points, from the port of the block that
is to be deleted.
8.8 SUMMARY
Branch points are bend points on a primary connection object that may have other secondary
connection tail points attached to them. To attach a connection tail point to a bend point,
various modifications and additions were made to the project, including the introduc-
tion of the functions: GetRefFromPoint(), SetRefFromPoint(), of the CConnection
class and CDiagramEngDoc::SnapConnectionTailPointToBendPoint(). The
UpdateConnectionPointTailToBendPoint() function was required to keep the
connection emanating from a bend point, attached to the moving bend point. The dele-
tion of a bend point to which a secondary connection is attached required a discon-
necting of the tail point first, using the CDiagramEngDoc::DisconnectTail
PointFromBendPoint() function. The deletion of a whole primary connection line contain-
ing bend points to which another secondary connection’s tail point is attached required a simi-
lar CDiagramEngDoc function named DisconnectTailPointsFromBendPoints()
to perform a disconnection prior to deletion. A problem concerning the insertion of
bend points was resolved using the CDiagramEngDocOnInsertBendPoint() and
CheckPointOnLineSegment() functions. The automatic connection of connection end
points to ports and bend points, allowing the direct connection of block ports, was performed by
the CDiagramEngDoc::SnapConnectionEndPointsAfterInitialConstruction()
function. Finally, the deletion of a block requires any connection that may be attached to its
ports to first be disconnected. This was performed using the CDiagramEngDoc functions
DisconnectEndPointsFromPorts(), to iterate through the vector of input and output ports,
and DisconnectEndPointFromPort(), to perform the disconnection from the actual port.
9 Block Dialog Windows
9.1 INTRODUCTION
Blocks may be placed on the palette, and connections may be drawn to connect the blocks together.
However, before a simulation can be initiated, block parameters need to be set using a dialog window,
and the values of these variables need to be updated to the underlying code. Double-left-clicking on
any of the blocks should invoke the block dialog window for that particular block and clicking the
OK button should update the entered data to the program variables. The following step-by-step
instructions indicate how functionality can be added to the code to allow block parameter values to
be assigned to a block via a dialog window invoked upon double-left-clicking a block.
203
204 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 9.1
The Derived Block Classes and Their
Corresponding Derived Dialog Classes
Derived Block Class Dialog Class
No. (Base Class: CBlock) (Base Class: CDialog)
1 CConstantBlock CConstantBlockDialog
2 CDerivativeBlock CDerivativeBlockDialog
3 CDivideBlock CDivideBlockDialog
4 CGainBlock CGainBlockDialog
5 CIntegratorBlock CIntegratorBlockDialog
6 CLinearFnBlock CLinearFnBlockDialog
7 COutputBlock COutputBlockDialog
8 CSignalGeneratorBlock CSignalGeneratorBlockDialog
9 CSubsystemBlock CSubsystemBlockDialog
10 CSubsystemInBlock CSubsystemInBlockDialog
11 CSubsystemOutBlock CSubsystemOutBlockDialog
12 CSumBlock CSumBlockDialog
13 CTransferFnBlock CTransferFnBlockDialog
All the block classes derived from the base CBlock class and their corresponding dialog classes
derived from the base CDialog class are shown in Table 9.1: the aforementioned six-step pro-
cedure needs to be performed for all CBlock-derived classes as detailed in the instructions that
follow.
TABLE 9.2
Constant Block Dialog Window Controls
Object Property Setting
Static Text ID ID_CONSTANT_BLK_DLG_TXT
Caption Enter constant &value/vector/matrix:
Edit Box ID ID_CONSTANT_BLK_DLG_VALUE
Multiline Checked
Horizontal scroll Checked
Vertical scroll Checked
Want return Checked
Button ID ID_CONSTANT_BLK_DLG_BTN_OK
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.1 ConstantBlockDialog window showing the controls as specified in Table 9.2.
FIGURE 9.2 Adding a Class dialog window for the IDD_CONSTANT_BLK_DLG resource.
the ClassWizard. A message box appears (Figure 9.2) with the content: “IDD_CONSTANT_BLK_DLG
is a new resource. Since it is a dialog resource you probably want to create a new class for it. You can also
select an existing class.” Create a new class with the name CConstantBlockDialog and base class CDialog.
9.3.1.3 Attach Variables to the Dialog Window Controls
Open the ClassWizard and select the Member Variables tab, select the class name to be
CConstantBlockDialog, since variables to be added relate to dialog window controls. Select the
Block Dialog Windows 207
TABLE 9.3
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_CONSTANT_BLK_DLG Resource
Control Variable Name Category Type
ID_CONSTANT_BLK_DLG_VALUE m_strConstValue Value CString
ID of the control to which a variable should be added, click Add Variable, and specify the
details as shown in Table 9.3.
Add a private member variable to the CConstantBlock class of the same CString type and name,
“m_strConstValue”, since this variable will be updated with the value of that of the dialog class. The
other variables “scalar_const”, “vector_const”, and “matrix_const” of the CConstantBlock class
may be consolidated into one (double) variable “m_dConstMatrix” since all numerical data will be
treated as double-type matrices in future for consistency reasons.
void CConstantBlockDialog::OnConstantBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CConstantBlockDialog::OnConstantBlkDlgBtnOk()\n”,
MB_OK, 0);
// Update variable values with the Dlg Wnd control values
UpdateData(TRUE);
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
TABLE 9.4
Objects, IDs, Class, and Event-Handler Functions for the Constant
Block Dialog Buttons
COMMAND
Object ID Class Event Handler
OK button ID_CONSTANT_BLK_DLG_BTN_OK CConstantBlockDialog OnConstantBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
208 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Then, if the dialog window variable values are changed, these are updated to the derived CBlock
class’s variables in the BlockDlgWndParameterInput() function. If the dialog window is
closed and later reopened, the correct CBlock-derived class’ member values are reflected in the
dialog window.
The member variable of the CConstantBlock and CConstantBlockDialog classes is the
CString “m_strConstValue” (the name is shared for convenience). Initialize this variable in the
CConstantBlock constructor as shown in bold in the following code.
derived block classes providing their own version of the function. Hence, make a comment in the
function that the function is to be overridden; otherwise leave the function empty as shown in the
following code.
void CBlock::BlockDlgWndParameterInput()
{
// VIRTUAL FN to be OVERRIDDEN by each DERIVED Block class.
// This is done, so the correct BlockDlgWndParameterInput() fn is
called,
// based on the run time type of the ptr-to-CBlock, within the block
list.
}
void CConstantBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class vars using the block class vars
oDlg.m_strConstValue = m_strConstValue;
The developer will notice that first the CConstantBlock variable “m_strConstValue” is writ-
ten to the CConstantBlockDialog variable which is then subsequently displayed. Then, in the
conditional “if(oDlg.DoModal() == IDOK)” section, the dialog window input, recorded in the
CConstantBlockDialog “oDlg.m_strConstValue” dialog window variable is assigned to the associ-
ated CConstantBlock-based “m_strConstValue” class variable.
210 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Note also that upon compilation of the code, the following error is reported: “error C2065:
‘IDD_CONSTANT_BLK_DLG’: undeclared identifier”! To resolve this, add #include “Resource.h”
at the top of the “ConstantBlockDialog.h” header file as shown in bold in the following.
#if !defined(AFX_CONSTANTBLOCKDIALOG_H__EAFC2435_59E6_4653_
B7C0_5C7B656D7064__INCLUDED_)
#define AFX_CONSTANTBLOCKDIALOG_H__EAFC2435_59E6_4653_B7C0_5C7B656D7064__
INCLUDED_
#include “Resource.h” // rqd. since resources are used to build the dlg.
wnd.
/////////////////////////////////////////////////////////////////////////
// CConstantBlockDialog dialog
//{ {AFX_INSERT_LOCATION} }
// Microsoft Visual C++ will insert additional declarations immediately
before the previous line.
#endif // !defined(AFX_CONSTANTBLOCKDIALOG_H__EAFC2435_59E6_4653_
B7C0_5C7B656D7064__INCLUDED_)
(a)
(b)
FIGURE 9.3 (a) A connector is drawn from the block position to the location of the OK button on the
ConstantBlockDialog window. (b) Connector erroneously drawn to where the OK button was on the dialog
window.
FIGURE 9.4 Properties dialog for the OK button on the IDD_CONSTANT_BLK_DLG resource shows the
“Default button” option unintentionally checked: this should be unchecked.
212 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 9.5
Derivative Block Dialog Window Controls
Object Property Setting
Group Box ID ID_DERIVATIVE_BLK_DLG_GPBOX
Caption Type of Numerical Derivative df/dt
Radio Button ID ID_DERIVATIVE_BLK_DLG_RBTN3PT
Group Checked
Caption 3 point derivative
Radio Button ID ID_DERIVATIVE_BLK_DLG_RBTN5PT
Group Unchecked
Caption 5 point derivative
Button ID ID_DERIVATIVE_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.5 DerivativeBlockDialog window showing the controls as specified in Table 9.5.
Block Dialog Windows 213
TABLE 9.6
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_DERIVATIVE_BLK_DLG Resource
Control Variable Name Category Type
ID_DERIVATIVE_BLK_DLG_RBTN3PT m_iDerivativeMethod Value int
void CDerivativeBlockDialog::OnDerivativeBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CDerivativeBlockDialog::OnDerivativeBlkDlgBtn
Ok()\n”, MB_OK, 0);
// Update variable values with the Dlg Wnd control values
UpdateData(TRUE);
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
TABLE 9.7
Objects, IDs, Class, and Event-Handler Functions for the Derivative Block Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_DERIVATIVE_BLK_DLG_BTN_OK CDerivativeBlockDialog OnDerivativeBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
214 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
CDerivativeBlock::CDerivativeBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Set the derivative method
m_iDerivativeMethod = 1; // (0) Euler, (1) Runge-Kutta
…
}
// Set the dialog class vars using the block class vars
oDlg.m_iDerivativeMethod = m_iDerivativeMethod;
TABLE 9.8
Divide Block Dialog Window Controls
Object Property Setting
Static Text ID ID_DIVIDE_BLK_DLG_TXT_NMULTS
Caption Number of multiplication inputs:
Static Text ID ID_DIVIDE_BLK_DLG_TXT_NDIVIDES
Caption Number of divide inputs:
Edit Box ID ID_DIVIDE_BLK_DLG_EB_NMULTS
Edit Box ID ID_DIVIDE_BLK_DLG_EB_NDIVIDES
Group Box ID ID_DIVIDE_BLK_DLG_GPBOX
Caption Type of Multiplication/Division
Radio Button ID ID_DIVIDE_BLK_DLG_RBTN_ELEMENT
Group Checked
Caption Elemental
Radio Button ID ID_DIVIDE_BLK_DLG_RBTN_MATRIX
Group Unchecked
Caption Matrix
Button ID ID_DIVIDE_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.6 DivideBlockDialog window showing the controls as specified in Table 9.8.
216 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 9.9
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_DIVIDE_BLK_DLG Resource
Control Variable Name Category Type Min. Value Max. Value
ID_DIVIDE_BLK_DLG_EB_NMULTS m_iNMultiplyInputs Value int 0 1000
ID_DIVIDE_BLK_DLG_EB_NDIVIDES m_iNDivideInputs Value int 0 1000
ID_DIVIDE_BLK_DLG_RBTN_ELEMENT m_iMultType Value int
invoke the ClassWizard. The familiar Adding a Class message box appears: create a new class with
the name CDivideBlockDialog and base class CDialog.
void CDivideBlockDialog::OnDivideBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CDivideBlockDialog::OnDivideBlkDlgBtnOk()\n”,
MB_OK, 0);
// Update variable values with the Dlg Wnd control values
UpdateData(TRUE);
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
TABLE 9.10
Objects, IDs, Class, and Event-Handler Functions for the Divide Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_DIVIDE_BLK_DLG_BTN_OK CDivideBlockDialog OnDivideBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
Block Dialog Windows 217
CDivideBlock::CDivideBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Set the Divide block member vars
m_iNMultiplyInputs = 1;
m_iNDivideInputs = 1;
m_iMultType = 1;
…
}
TABLE 9.11
Gain Block Dialog Window Controls
Object Property Setting
Static Text ID ID_GAIN_BLK_DLG_TXT_GAIN
Caption Enter gain constant/vector/matrix:
Edit Box ID ID_GAIN_BLK_DLG_EB_GAIN
Multiline Checked
Horizontal scroll Checked
Vertical scroll Checked
Want return Checked
Group Box ID ID_GAIN_BLK_DLG_GPBOX
Caption Type of Gain
Radio Button ID ID_GAIN_BLK_DLG_RB_ELEMENT
Group Checked
Caption Elemental
Radio Button ID ID_GAIN_BLK_DLG_RB_GAIN_INPUT
Group Unchecked
Caption Gain*Input
Radio Button ID ID_GAIN_BLK_DLG_RB_INPUT_GAIN
Group Unchecked
Caption Input*Gain
Button ID ID_GAIN_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.7 GainBlockDialog window showing the controls as specified in Table 9.11.
220 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 9.12
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_GAIN_BLK_DLG Resource
Control Variable Name Category Type
ID_GAIN_BLK_DLG_EB_GAIN m_strGainValue Value CString
ID_GAIN_BLK_DLG_RBTN_ELEMENT m_iGainType Value int
TABLE 9.13
Objects, IDs, Class, and Event-Handler Functions for the Gain Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_GAIN_BLK_DLG_BTN_OK CGainBlockDialog OnGainBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
function for the Cancel button, since this already calls the CDialog::OnCancel() function, so
no new code is required. Edit the OnGainBlkDlgBtnOk() function as shown in the following.
void CGainBlockDialog::OnGainBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CGainBlockDialog::OnGainBlkDlgBtnOk()n”, MB_OK, 0);
// Update variable values with the Dlg Wnd control values
UpdateData(TRUE);
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
void CGainBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class CGainBlockDialog : public CDialog
CGainBlockDialog oDlg;
// Set the dialog class vars using the block class vars
oDlg.m_iGainType = m_iGainType;
oDlg.m_strGainValue = m_strGainValue;
// Return val of DoModal() fn of ancestor class CDialog is checked to
determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign CGainBlockDialog variable values to CGainBlock variable
values.
m_strGainValue = oDlg.m_strGainValue;
m_iGainType = oDlg.m_iGainType;
// Print msg with variable value.
sMsg.Format(“\n CGainBlock::BlockDlgWndParameterInput(),
m_strGainValue = %s\n”, m_strGainValue);
AfxMessageBox(sMsg, nType, nIDhelp);
}
}
TABLE 9.14
Integrator Block Dialog Window Controls
Object Property Setting
Static Text ID ID_INTEGRATOR_BLK_DLG_TXT_NOTE
Caption IntegratorBlockDialog controls initial condition
vector input!\nNumericalSolverDialog
controls numerical method variable input!
Sunken Checked
Static Text ID ID_INTEGRATOR_BLK_DLG_TXT_IC
Caption Initial Condition Vector:
Edit Box ID ID_INTEGRATOR_BLK_DLG_EB_IC
Multiline Checked
Horizontal scroll Checked
Vertical scroll Checked
Want return Checked
Button ID ID_INTEGRATOR_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.8 IntegratorBlockDialog window showing the controls as specified in Table 9.14.
TABLE 9.15
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_INTEGRATOR_BLK_DLG Resource
Control Variable Name Category Type
ID_INTEGRATOR_BLK_DLG_EB_IC m_strICVector Value CString
TABLE 9.16
Objects, IDs, Class, and Event-Handler Functions for the Integrator Block Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_INTEGRATOR_BLK_DLG_BTN_OK CIntegratorBlockDialog OnIntegratorBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
class may be left there for now as it may be used in future as a double-typed vector variable.
However, add a new private CString variable to the CIntegratorBlock class, “m_strICVector”;
this holds the initial condition vector for the integration process as a CString object. The vari-
able names of the CIntegratorBlockDialog and the CIntegratorBlock classes are the same
for convenience.
void CIntegratorBlockDialog::OnIntegratorBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CIntegratorBlockDialog::OnIntegratorBlkDlgBtn
Ok()\n”, MB_OK, 0);
// Update variable values with the Dlg Wnd control values
UpdateData(TRUE);
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
CIntegratorBlock::CIntegratorBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Set the Integrator block member var.
m_strICVector = “[0 0 0]”;
…
}
224 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CIntegratorBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class CIntegratorBlockDialog : public
CDialog
CIntegratorBlockDialog oDlg;
// Set the dialog class vars using the block class vars
oDlg.m_strICVector = m_strICVector;
// Return val of DoModal() fn of ancestor class CDialog is checked to
determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign CIntegratorBlockDialog variable values to
CIntegratorBlock variable values.
m_strICVector = oDlg.m_strICVector;
// Print msg with variable value.
sMsg.Format(“\n CIntegratorBlock::BlockDlgWndParameterInput(),
m_strICVector = %s\n”, m_strICVector);
AfxMessageBox(sMsg, nType, nIDhelp);
}
}
TABLE 9.17
Linear Function Block Dialog Window Controls
Object Property Setting
Group Box ID ID_LINEARFN_BLK_DLG_GPBOX
Caption Linear Function Parameters
Static Text ID ID_LINEARFN_BLK_DLG_TXT_TINIT
Caption Initial Time:
Edit Box ID ID_LINEARFN_BLK_DLG_EB_TINIT
Static Text ID ID_LINEARFN_BLK_DLG_TXT_TFINAL
Caption Final Time:
Edit Box ID ID_LINEARFN_BLK_DLG_EB_TFINAL
Static Text ID ID_LINEARFN_BLK_DLG_TXT_VINIT
Caption Initial Value:
Edit Box ID ID_LINEARFN_BLK_DLG_EB_VINIT
Static Text ID ID_LINEARFN_BLK_DLG_TXT_DERIV
Caption Derivative:
Edit Box ID ID_LINEARFN_BLK_DLG_EB_DERIV
Button ID ID_LINEARFN_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.9 LinearFnBlockDialog window showing the controls as specified in Table 9.17.
TABLE 9.18
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_LINEARFN_BLK_DLG Resource
Control Variable Name Category Type
ID_LINEARFN_BLK_DLG_EB_TINIT m_dTimeInit Value double
ID_LINEARFN_BLK_DLG_EB_TFINAL m_dTimeFinal Value double
ID_LINEARFN_BLK_DLG_EB_VINIT m_dValueInit Value double
ID_LINEARFN_BLK_DLG_EB_DERIV m_dDerivative Value double
“m_dValueInit”, and “m_dTimeInit”, respectively. In addition, a new private double variable should
be added to the CLinearFnBlock class, i.e., “m_dTimeFinal”: this holds the final time value at which
the signal is considered to act. The variable names of the CLinearFnBlockDialog class are the same
as those of the CLinearFnBlock class for convenience.
void CLinearFnBlockDialog::OnLinearFnBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CLinearFnBlockDialog::OnLinearFnBlkDlgBtnOk
()\n”, MB_OK, 0);
// DiagramEng (end)
}
TABLE 9.19
Objects, IDs, Class, and Event-Handler Functions for the Linear Function Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_LINEARFN_BLK_DLG_BTN_OK CLinearFnBlockDialog OnLinearFnBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
Block Dialog Windows 227
CLinearFnBlock::CLinearFnBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Set the LinearFnBlock member vars.
m_dTimeInit = 0.0;
m_dTimeFinal = 10.0;
m_dValueInit = 0.0;
m_dDerivative = 1.0;
…
}
void CLinearFnBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class vars using the block class vars
oDlg.m_dDerivative = m_dDerivative;
oDlg.m_dTimeFinal = m_dTimeFinal;
oDlg.m_dTimeInit = m_dTimeInit;
oDlg.m_dValueInit = m_dValueInit;
TABLE 9.20
Output Block Dialog Window Controls
Object Property Setting
Group Box ID ID_OUTPUT_BLK_DLG_GPBOXNOT
Caption Textual Output Settings
Radio Button ID ID_OUTPUT_BLK_DLG_RB_STDNOT
Group Checked
Caption Standard notation
Radio Button ID ID_OUTPUT_BLK_DLG_RB_SCINOT
Group Unchecked
Caption Scientific notation
Group Box ID ID_OUTPUT_BLK_DLG_GPBOXTPTS
Caption Graphical Output Settings
Radio Button ID ID_OUTPUT_BLK_DLG_RB_TPTS
Group Checked
Caption Show time points
Radio Button ID ID_OUTPUT_BLK_DLG_RB_NOTPTS
Group Unchecked
Caption Hide time points
Button ID ID_OUTPUT_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
Block Dialog Windows 229
FIGURE 9.10 OutputBlockDialog window showing the controls as specified in Table 9.20.
TABLE 9.21
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_OUTPUT_BLK_DLG Resource
Control Variable Name Category Type
ID_OUTPUT_BLK_DLG_RB_STDNOT m_iNotation Value int
ID_OUTPUT_BLK_DLG_RB_TPTS m_iTimePtDisplay Value int
void COutputBlockDialog::OnOutputBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
230 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 9.22
Objects, IDs, Class, and Event-Handler Functions for the Output Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_OUTPUT_BLK_DLG_BTN_OK COutputBlockDialog OnOutputBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
//AfxMessageBox(“\n COutputBlockDialog::OnOutputBlkDlgBtnOk()\n”,
MB_OK, 0);
// DiagramEng (end)
}
void COutputBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class COutputBlockDialog : public
CDialog
COutputBlockDialog oDlg;
Block Dialog Windows 231
// Set the dialog class vars using the block class vars
oDlg.m_iNotation = m_iNotation;
oDlg.m_iTimePtDisplay = m_iTimePtDisplay;
// Return val of DoModal() fn of ancestor class CDialog is checked to
determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign COutputBlockDialog variable values to COutputBlock
variable values.
m_iNotation = oDlg.m_iNotation;
m_iTimePtDisplay = oDlg.m_iTimePtDisplay;
// Print msg with variable value.
sMsg.Format(“\n COutputBlock::BlockDlgWndParameterInput(),
m_iNotation = %d\n’’, m_iNotation);
AfxMessageBox(sMsg, nType, nIDhelp);
}
}
TABLE 9.23
Signal Generator Block Dialog Window Controls
Object Property Setting
Group Box ID ID_SIGNALGEN_BLK_DLG_GB_PARAS
Caption Signal Generator Function Parameters
Static Text ID ID_SIGNALGEN_BLK_DLG_TXT_FN
Caption Function Type:
Static Text ID ID_SIGNALGEN_BLK_DLG_TXT_AMP
Caption Amplitude:
Static Text ID ID_SIGNALGEN_BLK_DLG_TXT_FREQ
Caption Frequency:
Static Text ID ID_SIGNALGEN_BLK_DLG_TXT_PHASE
Caption Phase:
Static Text ID ID_SIGNALGEN_BLK_DLG_TXT_UNITS
Caption Domain Units:
Combo Box ID ID_SIGNALGEN_BLK_DLG_CB_FN
Data Random
Sine
Square
Combo Box ID ID_SIGNALGEN_BLK_DLG_CB_UNITS
Data Hz
rad/s
Edit Box ID ID_SIGNALGEN_BLK_DLG_EB_AMP
Edit Box ID ID_SIGNALGEN_BLK_DLG_EB_FREQ
Edit Box ID ID_SIGNALGEN_BLK_DLG_EB_PHASE
Button ID ID_SIGNALGEN_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.11 SignalGeneratorBlockDialog window showing the controls as specified in Table 9.23.
Block Dialog Windows 233
TABLE 9.24
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_SIGNALGEN_BLK_DLG Resource
Control Variable Name Category Type
ID_SIGNALGEN_BLK_DLG_CB_FN m_strFnType Value CString
ID_SIGNALGEN_BLK_DLG_EB_AMP m_dAmplitude Value double
ID_SIGNALGEN_BLK_DLG_EB_FREQ m_dFrequency Value double
ID_SIGNALGEN_BLK_DLG_EB_PHASE m_dPhase Value double
ID_SIGNALGEN_BLK_DLG_CB_UNITS m_strUnits Value CString
TABLE 9.25
Objects, IDs, Class, and Event-Handler Functions for the Signal Generator Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_SIGNALGEN_BLK_ CSignalGeneratorBlockDialog OnSignalGeneratorBlkDlgBtnOk()
DLG_BTN_OK
Cancel button IDCANCEL (default) CDialog OnCancel()
{
…
// Init. member vars.
m_strFnType = “Sine”;
m_dAmplitude = 1.0;
m_dFrequency = 1.0;
m_dPhase = 0.0;
m_strUnits = “rad/s”;
…
}
void CSignalGeneratorBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class vars using the block class vars
oDlg.m_strFnType = m_strFnType;
oDlg.m_dAmplitude = m_dAmplitude;
oDlg.m_dFrequency = m_dFrequency;
oDlg.m_dPhase = m_dPhase;
oDlg.m_strUnits = m_strUnits;
TABLE 9.26
Subsystem Block Dialog Window Controls
Object Property Setting
Group Box ID ID_SUBSYS_BLK_DLG_IN
Caption SubsystemIn Parameters
Group Box ID ID_SUBSYS_BLK_DLG_OUT
Caption SubsystemOut Parameters
Static Text ID ID_SUBSYS_BLK_DLG_TXT_INPUT
Caption Name of Input Port:
Static Text ID ID_SUBSYS_BLK_DLG_TXT_OUTPUT
Caption Name of Output Port:
Edit Box ID ID_SUBSYS_BLK_DLG_EB_INPUT
Edit Box ID ID_SUBSYS_BLK_DLG_EB_OUTPUT
Button ID ID_SUBSYS_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.12 SubsystemBlockDialog window showing the controls as specified in Table 9.26.
236 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 9.27
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_SUBSYS_BLK_DLG Resource
Control Variable Name Category Type
ID_SUBSYS_BLK_DLG_EB_INPUT m_strInputPortName Value CString
ID_SUBSYS_BLK_DLG_EB_OUTPUT m_strOutputPortName Value CString
invoke the ClassWizard. The Adding a Class message box appears: create a new class with the name
CSubsystemBlockDialog and base class CDialog.
TABLE 9.28
Objects, IDs, Class, and Event-Handler Functions for the Subsystem Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_SUBSYS_BLK_DLG_BTN_OK CSubsystemBlockDialog OnSubsystemBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
Block Dialog Windows 237
TABLE 9.29
Subsystem In Block Dialog Window Controls
Object Property Setting
Static Text ID ID_SUBSYSIN_BLK_DLG_TXT_INPUT
Caption Name of Input Port:
Edit Box ID ID_SUBSYSIN_BLK_DLG_EB_INPUT
Button ID ID_SUBSYSIN_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.13 SubsytemInBlockDialog window showing the controls as specified in Table 9.29.
TABLE 9.30
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_SUBSYSIN_BLK_DLG Resource
Control Variable Name Category Type
ID_SUBSYSIN_BLK_DLG_EB_INPUT m_strInputPortName Value CString
Block Dialog Windows 239
TABLE 9.31
Objects, IDs, Class, and Event-Handler Functions for the Subsystem In Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_SUBSYSIN_BLK_DLG_BTN_OK CSubsystemInBlockDialog OnSubsystemInBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
Modify the CSubsystemInBlock class member variables: remove the integer vari-
able, “port_number_label” and change the existing character array placeholder variable,
“port_signal_label”, to a CString variable, “m_strInputPortName”. The “m_strInputPortName”
variable in the CSubsystemInBlock and CSubsystemInBlockDialog classes shares the same name
for convenience.
void CSubsystemInBlockDialog::OnSubsysInBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CSubsystemInBlockDialog::OnSubsystemInBlkDlgBtn
Ok()\n”, MB_OK, 0);
// Update variable values with the Dlg Wnd control values
UpdateData(TRUE);
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
CSubsystemInBlock::CSubsystemInBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Init. the member CString var.
m_strInputPortName = “input port name”;
…
}
240 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CSubsystemInBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class var using the block class var
oDlg.m_strInputPortName = m_strInputPortName;
TABLE 9.32
Subsystem Out Block Dialog Window Controls
Object Property Setting
Static Text ID ID_SUBSYSOUT_BLK_DLG_TXT_OUTPUT
Caption Name of Output Port:
Edit Box ID ID_SUBSYSOUT_BLK_DLG_EB_OUTPUT
Button ID ID_SUBSYSOUT_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.14 SubsytemOutBlockDialog window showing the controls as specified in Table 9.32.
TABLE 9.33
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_SUBSYSOUT_BLK_DLG Resource
Control Variable Name Category Type
ID_SUBSYSOUT_BLK_DLG_EB_OUTPUT m_strOutputPortName Value CString
TABLE 9.34
Objects, IDs, Class, and Event-Handler Functions for the Subsystem Out Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_SUBSYSOUT_BLK_DLG_BTN_OK CSubsystemOutBlockDialog OnSubsystemOutBlkDlgBtnOk()
Cancel IDCANCEL (default) CDialog OnCancel()
button
void CSubsystemOutBlockDialog::OnSubsysOutBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CSubsystemOutBlockDialog::OnSubsystemOutBlkDlgBtn
Ok()\n”, MB_OK, 0);
// DiagramEng (end)
}
CSubsystemOutBlock::CSubsystemOutBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Init. the member CString var.
m_strOutputPortName = “output port name”;
…
}
void CSubsystemOutBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class var using the block class var
oDlg.m_strOutputPortName = m_strOutputPortName;
TABLE 9.35
Sum Block Dialog Window Controls
Object Property Setting
Static Text ID ID_SUM_BLK_DLG_TXT_NPLUS
Caption Number of addition inputs:
Edit Box ID ID_SUM_BLK_DLG_EB_NPLUS
Static Text ID ID_SUM_BLK_DLG_TXT_NMINUS
Caption Number of subtraction inputs:
Edit Box ID ID_SUM_BLK_DLG_EB_NMINUS
Button ID ID_SUM_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.15 SumBlockDialog window showing the controls as specified in Table 9.35.
TABLE 9.36
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_SUM_BLK_DLG Resource
Control Variable Name Category Type Min Value Max Value
ID_SUM_BLK_DLG_EB_NPLUS m_iNAddInputs Value int 0 1000
ID_SUM_BLK_DLG_EB_NMINUS m_iNSubtractInputs Value int 0 1000
Block Dialog Windows 245
TABLE 9.37
Objects, IDs, Class, and Event-Handler Functions for the Sum
Block Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_SUM_BLK_DLG_BTN_OK CSumBlockDialog OnSumBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
function for the Cancel button, since this already calls the CDialog::OnCancel() func-
tion, so no new code is required. Edit the OnSumBlkDlgBtnOk() function as shown in the
following.
void CSumBlockDialog::OnSumBlkDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
// DiagramEng (end)
}
void CSumBlock::BlockDlgWndParameterInput()
{
int n_inputs = 0; // number of input ports
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class var using the block class var
oDlg.m_iNAddInputs = m_iNAddInputs;
oDlg.m_iNSubtractInputs = m_iNSubtractInputs;
if(n_inputs < 2)
{
sMsgTemp.Format(“\n CSumBlock::BlockDlgWndParameter
Input()\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“ No. of input ports = %d\n”, n_inputs);
sMsg += sMsgTemp;
sMsgTemp.Format(“ Two or more input ports are required!\n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
sMsg.Format(“”); // reset the msg since within a while loop
}
}
}
TABLE 9.38
Transfer Function Block Dialog Window Controls
Object Property Setting
Static Text ID ID_TRANSFERFN_BLK_DLG_TXT_NUMER
Caption Numerator coefficients (in decreasing powers of s):
Edit Box ID ID_TRANSFERFN_BLK_DLG_EB_NUMER
Static Text ID ID_TRANSFERFN_BLK_DLG_TXT_DENOM
Caption Denominator coefficients (in decreasing powers of s):
Edit Box ID ID_TRANSFERFN_BLK_DLG_EB_DENOM
Button ID ID_TRANSFERFN_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 9.16 TransferFnBlockDialog window showing the controls as specified in Table 9.38.
248 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 9.39
Dialog Window Controls, Variable Name, Category, and Type
for the IDD_TRANSFERFN_BLK_DLG Resource
Control Variable Name Category Type
ID_TRANSFERFN_BLK_DLG_EB_NUMER m_strNumerCoeffs Value CString
ID_TRANSFERFN_BLK_DLG_EB_DENOM m_strDenomCoeffs Value CString
TABLE 9.40
Objects, IDs, Class, and Event-Handler Functions for the Transfer Function Block
Dialog Buttons
Object ID Class COMMAND Event Handler
OK button ID_TRANSFERFN_BLK_DLG_BTN_OK CTransferFnBlockDialog OnTransferFnBlkDlgBtnOk()
Cancel button IDCANCEL (default) CDialog OnCancel()
Block Dialog Windows 249
CTransferFnBlock::CTransferFnBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Init. the CString member vars.
m_strNumerCoeffs = “0 2 1”;
m_strDenomCoeffs = “0 3 2 1”;
…
}
void CTransferFnBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class vars using the block class vars
oDlg.m_strNumerCoeffs = m_strNumerCoeffs;
oDlg.m_strDenomCoeffs = m_strDenomCoeffs;
9.4 SUMMARY
Blocks placed on the palette can be double-clicked in order to generate a dialog window to
allow the user to enter block-based parameters. A function titled CDiagramEngView::
OnLButtonDblClk() is added to process double-left-click events and this calls a
CDiagramEngDoc::DoubleLeftClickBlock() method which in turn calls a CBlock-
derived BlockDlgWndParameterInput() function for the clicked block to accept
block parameter input. To add dialog window based functionality, six key steps are required: (1) insert
a new dialog window and add controls, (2) attach a class to the dialog window, (3) attach variables
to the dialog window controls, (4) add functionality to the dialog window buttons, (5) add function-
ality to initialize the variables, and (6) add the overriding BlockDlgWndParameterInput()
function to the derived block class.
REFERENCES
1. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
2. Simulink® 6, Using Simulink, MATLAB® SIMULINK®, The MathWorks Inc., Natick, MA, 2007.
3. Ogata, K., Modern Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.
4. MATLAB®, The MathWorks Inc., Natick, MA, 2007.
10 Conversion of String
Input to Double Data
10.1 INTRODUCTION
The Constant, Gain, Integrator, and Transfer Function blocks all require user input of numerical val-
ues, for the constant (scalar, vector, or matrix), gain multiplier (scalar, vector, or matrix), initial condi-
tion vector, and numerator and denominator coefficient vectors, respectively. The user input is entered
via the block dialog windows and is processed by the class’ BlockDlgWndParameterInput()
function described in the previous chapter. At present, the numerical input is read in as a CString
value from the dialog window and stored in the associated block class’ CString data member. For
example, the initial condition vector is recorded in the CString variable, “m_strICVector”, of the
CIntegratorBlockDialog and CIntegratorBlock classes. This CString value now needs to be con-
verted to the equivalent numerical (double) representation and stored in the block class.
251
252 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 10.1
Function-Call Tree of the StringToDouble Application Showing Functions Used
in Converting a CString to Double Values
Level 0 Level 1 Level 2 Level 3
main()
PrepareStringToDouble()
ConvertStringToDouble()
StripInputString()
DetermineDataDimsByStrpbrk()
DetermineDataDimsByStrtok()
ConvertStringMatrixToDoubleMatrix()
TABLE 10.2
Common String Handling/Conversion Functions
String Function Description
double atof(const char *s) ASCII to floating number conversion: the string “s” is converted to a
double value
char *strcpy(char *s1, const char *s2) The string “s2” is copied into string “s1” including the “\0”
terminator, given sufficient space in “s1”
size_t strlen(const char *s) The length of string “s”, not including the “\0” terminator, is returned
char *strncpy(char *s1, const char *s2, size_t n) The first “n” characters from “s2” are copied into “s1”. If strlen(s2)
≥ n, then no “\0” terminator will be written in “s1”
char *strpbrk(const char *s1, const char *s2) The address of the first character in “s1” that matches the
character(s) in “s2” is returned; otherwise, NULL is returned
char *strtok(char *s1, const char *s2) The address of a character group or token in “s1” separated by
separators in “s2” is returned. The first separator is overwritten
with a NULL character. Further calls with “s1” = NULL return the
address of the next separated character group or token
Source: Kelley, A. and Pohl, I., A Book On C: Programming in C, 2nd edn., Benjamin Cummings, Redwood City,
CA, 1990.
Edit the ConvertStringToDouble() function as shown in the following to perform the key
actions described previously: the various functions called herein are introduced in the sections
that follow.
{
valid = DetermineDataDimsByStrtok(stripped_str_copy, nrows,
ncols, max_row_length);
}
// -- BUILD THE STRING MATRIX
// -- MEMORY ALLOC
str_matrix = new char *[nrows]; // input string matrix
for(i=0; i<nrows; i++)
{
str_matrix[i] = new char[max_row_length + 1]; // allocate an
array of chars, return & to str_Matrix[i]
}
// Get the first TOKEN/SET-OF-CHARS/ROW-OF-DATA of stripped_str,
up until the first delimiter/SEPARATOR “;”
// The address of the first character of the token is returned.
// The character immediately following the token is overwritten
with the NULL character.
ptr_char = strtok(stripped_str, “;”);
// Record the rows
while(ptr_char != NULL) // while not at the end of the original
string (denoted by NULL)
{
strcpy(str_matrix[cnt], ptr_char);
// -- GET THE NEXT TOKEN/SET-OF-CHARS/ROW-OF-DATA
// NOTE! - “Subsequent calls with the first string = NULL,
return the base address of a string supplied
// by the system that contains the NEXT token.” [Kelley & Pohl]
// Hence, find the next set of chars from the prev end pt (NULL)
as a result of using strtok(),
// to the next delimiter “;”, i.e. get the next row of
characters, where the base address of the first char,
// is returned to ptr_char.
ptr_char = strtok(NULL, “;”);
cnt++; // increment the row cnt.
}
// EXTRACT THE STRING DATA OUT OF THE STRING MATRIX AND CONVERT IT TO
A DOUBLE MATRIX
if(valid == 0) // inconsistent no. of cols per row
{
nrows = 0;
ncols = 0;
d_matrix = NULL;
}
else if(valid == 1) // consistent no. of cols per row
{
d_matrix = ConvertStringMatrixToDoubleMatrix(str_matrix, nrows,
ncols);
}
// MEMORY DELETE
// Note: memory for stripped_str is only allocated when return value
of StripInputString() is not NULL.
delete [] input_str; // original input str
256 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// -- MEMORY DELETE
// Note: deletion of stripped_str should take place in the calling
environ.
return stripped_str;
}
The StripInputString() function extracts only the meaningful numerical data from the
string entered by the user: it strips away unnecessary characters leading and trailing the string,
e.g., “[”, “]”, “;”, “.”, etc. Initially the first number, minus sign, or decimal point is found using
strpbrk(), and the pointer returned denotes the start of the string. Then the string is traversed
(ptr_str++) updating the end point recorded in “ptr_end”, with the position one beyond the last
number found. The length of valid data is determined by taking the difference in the string lengths
of “ptr_begin” and “ptr_end”, and a stripped string is formed using strncpy(). However, since
the string, “ptr_begin”, is longer than “stripped_str”, which is constructed to be of size “length + 1”,
no NULL (“0”) character is placed at the end of “stripped_str” automatically: this must then be
manually performed as shown. The stripped string is returned, and the responsibility for deleting
the memory is that of the calling environment, i.e., ConvertStringToDouble().
Place their declarations in the “DiagramEngDoc.h” header file under the “Global Functions” sec-
tion and edit these functions as shown in the following. The user may select which of these two
functions is desired in the ConvertStringToDouble() function by setting the data-dimension-
determining flag-like variable, “dim_flag”, accordingly.
{
ncols = 1;
// -- ITERATE OVER ALL COLS
ptr_str = ptr_row;
// While the address of the next no. or decimal pt. is not NULL
while( (pch1 = strpbrk(ptr_str, “0123456789.”) ) != NULL)
{
// Advance the ptr_str by one.
ptr_str = pch1 + 1;
// Get the address of the next number or decimal pt.
pch2 = strpbrk(ptr_str, “0123456789.”);
// If the address of the next no. is greater than one space
away, then white space intervened.
if( (pch2 != NULL) && (pch2 > (pch1 + 1) ) )
{
ncols++;
}
}
// Increment nrows
nrows++;
// Check the consistency of ncols.
if(nrows == 1)
{
ncols_ref = ncols;
valid = 1;
}
else if(ncols != ncols_ref)
{
AfxMessageBox(“\n DetermineDataDimsByStrpbrk(): Inconsistent
number of columns\n”, MB_OK, 0);
valid = 0;
}
// Record the max row length for memory alloc purposes
if(strlen(ptr_row) >= max_row_length)
{
max_row_length = strlen(ptr_row);
}
// Get the new row
ptr_row = strtok(NULL, “;”);
}
return valid;
}
int nrows, int ncols). Place the declaration in the “DiagramEngDoc.h” header file under
the “Global Functions” section and edit the code as shown in the following.
matrix with “nrows” number of rows will be the destructor of the class whose member variable of
the form “double **matrix” was assigned the returned matrix or the function in which the returned
matrix was assigned.
The character data are extracted from each row of data, using strtok(), which returns the
address (a pointer-to-char) of the (next) token, character, or group of characters, separated by white
space (“ ”). This array of ASCII character(s), or string, is then converted to a double type using the
“ASCII to floating number” conversion function atof().
Add a function to print a vector to the project with the prototype, int PrintVector(double *v,
int ncols), and place the declaration in the “DiagramEngDoc.h” header file. Place the function
definition in the “DiagramEngDoc.cpp” source file and edit it as shown.
sMsgTemp.Format(“\n PrintVector()\n”);
sMsg += sMsgTemp;
// Print Vector
sMsgTemp.Format(“\n”);
sMsg += sMsgTemp;
return 0;
}
void CConstantBlock::BlockDlgWndParameterInput()
{
int i;
int input = 0; // input: (0) incomplete, (1) complete
CString sMsg; // string msg. to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class vars using the block class vars
oDlg.m_strConstValue = m_strConstValue;
sMsg += sMsgTemp;
sMsgTemp.Format(“\n Please enter consistent numerical
data. \n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
sMsg.Format(“”); // reset the msg since within a while loop
}
else
{
input = 1;
}
}
}
Augment the CConstantBlock destructor to delete the memory allocated for the two-dimensional
double array, “m_dConstMatrix”, (on block deletion) as shown in the following.
CConstantBlock::∼CConstantBlock(void)
{
int i;
// MEMORY DEALLOCATION
// Delete the input data matrix whose memory was allocated in
ConvertStringMatrixToDoubleMatrix()
if(m_dConstMatrix != NULL)
{
for(i=0; i<m_iNrows; i++)
{
delete [] m_dConstMatrix[i];
}
delete [] m_dConstMatrix;
m_dConstMatrix = NULL;
}
// Reset member vars.
m_iNrows = 0;
m_iNcols = 0;
}
Finally, augment the CConstantBlock constructor to set the member variable, “m_dConstMatrix”, given
the CString value defined earlier, “m_strConstValue”. This is done so that the block has a default starting
value. This also prevents erroneous memory deletion in the destructor in the event that the pointer was
not assigned to NULL. The ellipsis denotes omitted but unchanged code from before (as usual).
void CGainBlock::BlockDlgWndParameterInput()
{
int i;
int input = 0; // input: (0) incomplete, (1) complete
CString sMsg; // string msg. to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class CGainBlockDialog : public CDialog
CGainBlockDialog oDlg;
// Set the dialog class vars using the block class vars
oDlg.m_iGainType = m_iGainType;
oDlg.m_strGainValue = m_strGainValue;
// While input not correct
while(input == 0)
{
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign CGainBlockDialog variable values to CGainBlock
variable values.
m_strGainValue = oDlg.m_strGainValue;
m_iGainType = oDlg.m_iGainType;
// Print msg with variable value.
//sMsg.Format(“\n CGainBlock::BlockDlgWndParameterInput(),
m_strGainValue = %s\n”, m_strGainValue);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
// DELETE POSSIBLY EXISTING MATRIX
// Delete the input data matrix whose memory was allocated in
ConvertStringMatrixToDoubleMatrix()
if(m_dGainMatrix != NULL)
{
for(i=0; i<m_iNrows; i++)
{
delete [] m_dGainMatrix[i];
}
delete [] m_dGainMatrix;
}
// Convert the input string into a double matrix.
m_dGainMatrix = ConvertStringToDouble(m_strGainValue, m_iNrows,
m_iNcols);
//PrintMatrix(m_dGainMatrix, m_iNrows, m_iNcols);
268 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Augment the CGainBlock destructor to delete the memory allocated for the two-dimensional dou-
ble array, “m_dGainMatrix” (upon block deletion), as shown in the following.
CGainBlock::∼CGainBlock(void)
{
int i;
// MEMORY DEALLOCATION
// Delete the input data matrix whose memory was allocated in
ConvertStringMatrixToDoubleMatrix()
if(m_dGainMatrix != NULL)
{
for(i=0; i<m_iNrows; i++)
{
delete [] m_dGainMatrix[i];
}
delete [] m_dGainMatrix;
m_dGainMatrix = NULL;
}
// Reset member vars.
m_iNrows = 0;
m_iNcols = 0;
}
Augment the CGainBlock constructor to set the member variable, “m_dGainMatrix”, given the
CString value, “m_strGainValue”, defined earlier. This is done so that the block has a default start-
ing value.
void CIntegratorBlock::BlockDlgWndParameterInput()
{
int input = 0; // input: (0) incomplete, (1) complete
CString sMsg; // string msg. to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class vars using the block class vars
oDlg.m_strICVector = m_strICVector;
// While input not correct
while(input == 0)
{
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign CIntegratorBlockDialog variable value to
CIntegratorBlock variable value.
m_strICVector = oDlg.m_strICVector;
CIntegratorBlock::∼CIntegratorBlock(void)
{
// Delete the initial condition vector whose memory was allocated in
ConvertStringToDoubleVector()
if(m_dICVector != NULL)
{
delete [] m_dICVector;
m_dICVector = NULL;
}
}
Augment the CIntegratorBlock constructor to set the member variable, “m_dICVector”, given
the CString value, “m_strICVector”, defined earlier. This is done so that the block has a default
starting value.
CIntegratorBlock::CIntegratorBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Initialize m_dICVector to have the default numerical value of the
CString arg. (a non-NULL value).
// If an Integrator block were to be deleted, then memory would be
deleted correctly.
m_dICVector = ConvertStringToDoubleVector(m_strICVector, m_iNrows,
m_iNcols);
}
Conversion of String Input to Double Data 273
void CTransferFnBlock::BlockDlgWndParameterInput()
{
int input = 0; // input: (0) incomplete, (1) complete
CString sMsg; // string msg. to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class CTransferFnBlockDialog : public
CDialog
CTransferFnBlockDialog oDlg;
// Set the dialog class vars using the block class vars
oDlg.m_strNumerCoeffs = m_strNumerCoeffs;
oDlg.m_strDenomCoeffs = m_strDenomCoeffs;
// While input not correct
while(input == 0)
{
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign CTransferFnBlockDialog variable values to
CTransferFnBlock variable values.
m_strNumerCoeffs = oDlg.m_strNumerCoeffs;
m_strDenomCoeffs = oDlg.m_strDenomCoeffs;
// Print msg with variable value.
//sMsg.Format(“\n CTransferFnBlock::BlockDlgWndParameter
Input(), m_strNumerCoeffs = %s, m_strDenomCoeffs = %s\n”,
m_strNumerCoeffs, m_strDenomCoeffs);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
// DELETE POSSIBLY EXISTING VECTORS
// Delete the coeff vectors whose memory was allocated in
ConvertStringToDoubleVector()
if(m_dNumerVector != NULL)
{
delete [] m_dNumerVector;
}
if(m_dDenomVector != NULL)
{
delete [] m_dDenomVector;
}
274 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
else
{
sMsgTemp.Format(“\n CTransferFnBlock::ConvertStringToDouble
Vector()\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“\n Dimensions of coefficient vector are that of
a matrix!\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“\n nrows = %d, ncols = %d\n”, nrows, ncols);
sMsg += sMsgTemp;
sMsgTemp.Format(“\n Re-enter coefficient vector.\n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
// Assign NULL indicating that something is wrong
coeff_vector = NULL;
}
// MEMORY DELETE
// Delete the input data matrix whose memory was allocated in
ConvertStringMatrixToDoubleMatrix()
// Note: if matrix = NULL, then fn already returns NULL and hence
memory not deleted as expected.
for(i=0; i<nrows; i++)
{
delete [] matrix[i];
}
delete [] matrix;
return coeff_vector;
}
CTransferFnBlock::∼CTransferFnBlock(void)
{
// Delete the coefficient vector whose memory was allocated in
ConvertStringToDoubleVector()
if(m_dNumerVector != NULL)
{
delete [] m_dNumerVector;
m_dNumerVector = NULL;
}
if(m_dDenomVector != NULL)
{
delete [] m_dDenomVector;
m_dDenomVector = NULL;
}
}
Conversion of String Input to Double Data 277
CTransferFnBlock::CTransferFnBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Initialize m_dNumerVector and m_dDenomVector to have the default
numerical values of the CString args. (non-NULL values).
// If a TransferFn block were to be deleted, then memory would be
deleted correctly.
m_dNumerVector = ConvertStringToDoubleVector(m_strNumerCoeffs,
m_iLengthNumer);
m_dDenomVector = ConvertStringToDoubleVector(m_strDenomCoeffs,
m_iLengthDenom);
}
Now upon running the code, data may be entered for each of the four block objects of type,
CConstantBlock, CGainBlock, CIntegratorBlock, and CTransferFnBlock, where the values are con-
verted into a matrix or vector of double values as appropriate. This input then forms the initial data
of the block that is used in signal propagation (to be introduced later).
10.4 SUMMARY
The conversion of CString input strings to double member data is performed by using various global
and class-specific functions. The ConvertStringToDouble() function converts a CString
value into double values. A StripInputString() function strips the input string of unwanted
leading and trailing characters. The data dimensions, i.e., the number of rows and columns of
matrix values, are determined using either a DetermineDataDimsByStrpbrk() function or a
DetermineDataDimsByStrtok() function, which makes predominant use of the strpbrk()
and strtok() functions, respectively. The ConvertStringMatrixToDoubleMatrix() func-
tion then converts the string data to double matrix data. A ConvertStringToDoubleVector()
function is also used to convert string data to a double vector if a vector is required. The classes that
required modification to cater for string input are CConstantBlock, CGainBlock, CIntegratorBlock,
and CTransferFnBlock.
REFERENCE
1. Kelley, A. and Pohl, I., A Book On C: Programming in C, 2nd edn., Benjamin Cummings, Redwood City,
CA, 1990.
11 Moving Multiple Items
11.1 INTRODUCTION
Many different types of items may be moved together by first circumscribing them with what is
called a “rubber band” and then moving the whole rubber-band-enclosed group together. This is
performed using a CRectTracker object and determining whether the enclosed region of the rubber
band intersects or contains items on the palette, e.g., blocks, connection bend points, and connection
end points, and then updating the positions of the items to be translated by the same amount as the
center point of the whole rectangular rubber band region.
The process of tracking or moving of multiple items using a CRectTracker object first involves
initiating the procedure using either a keyboard key or a toolbar button, followed by multiple invoca-
tion of a to-be-introduced TrackMultipleItems() function, where, on the first entry, a rubber
band is created and, on a subsequent entry, the circumscribed items may be moved. The key topics
presented here are as follows:
279
280 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The developer will notice that the flag-like variable, “tracker_flag”, is returned from the
TrackMultipleItems() function denoting whether an item was actually moved, as is the case
for all diagram entity movement functions shown.
{
m_iRubberBandCreated = 0; // end the rubber band state
SetKeyFlagTrack(0); // reset the key-based track flag since
tracking aborted
tracker_flag = 0; // tracking did not occur
return tracker_flag;
}
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews (NULL); // indicate that sys. should
redraw.
}
}// end for it_blk
{
if(item_tracked == 0)
{
// Determine the tracker’s change in position
item_tracked = DetermineTrackerDeltaPosition(pWnd,
point, tracker_init, delta_posn);
}
// Check if the TAIL POINT is NOT CONNECTED to a PORT or
a BEND POINT: if so move it.
if( ( (*it_con)->GetRefFromPort() == NULL) &&
( (*it_con)->GetRefFromPoint() == NULL) )
{
// Update tail pt. posn.
(*it_con)->SetConnectionPointTail(tail_pt + delta_posn);
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should
redraw.
}
}
// HEAD POINT
head_pt = (*it_con)->GetConnectionPointHead();
// Determine if item lies within rubber band
intersected = DetermineCurrentAndIntersectRects(temp_tracker,
head_pt, delta);
if(intersected)
{
if(item_tracked == 0)
{
// Determine the tracker’s change in position
item_tracked = DetermineTrackerDeltaPosition(pWnd,
point, tracker_init, delta_posn);
}
// Check if the HEAD POINT is NOT CONNECTED to a PORT:
if so move it.
if( (*it_con)->GetRefToPort() == NULL)
{
// Update head pt. posn.
(*it_con)->SetConnectionPointHead(head_pt + delta_posn);
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should
redraw.
}
}
// -- ITERATE THROUGH ALL BEND POINTS FOR THIS CONNECTION
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
for(it_pt = bend_pts_list.begin(); it_pt != bend_pts_list.
end(); it_pt++)
284 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
// BEND POINT
bend_pt = *it_pt;
// Determine if item lies within rubber band
intersected = DetermineCurrentAndIntersectRects(temp_tracker,
bend_pt, delta);
if(intersected)
{
if(item_tracked == 0)
{
// Determine the tracker’s change in position
item_tracked = DetermineTrackerDeltaPosition(pWnd,
point, tracker_init, delta_posn);
}
// Update the connection bend pt posn.
*it_pt = *it_pt + delta_posn;
// Update any connection’s tail point if it was
connected to this bend point
m_SystemModel.UpdateConnectionPointTailToBendPoint
(&(*it_pt) );
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should
redraw.
}
}// end for it_pt
}// end for it_con
// Set flags
m_iRubberBandCreated = 0; // end the rubber band state
SetKeyFlagTrack(0); // reset the key-based track flag since
tracking aborted
if(item_tracked == 0) // if no item was tracked
{
tracker_flag = 0;
return tracker_flag;
}
else // if an item was tracked
{
tracker_flag = 1;
return tracker_flag;
}
}
// Return the tracker_flag
return tracker_flag;
}
variable. Hence, add the private integer member variable, “m_iRubberBandCreated”, to the
CDiagramEngDoc class and initialize it to zero in the CDiagramEngDoc class’ constructor, as
shown in the following.
CDiagramEngDoc::CDiagramEngDoc()
{
// TODO: add one-time construction code here
// DiagramEng (start)
m_iRubberBandCreated = 0;
m_dDeltaLength = 50.0;
// DiagramEng (end)
}
The developer will also notice two often-used function calls within the
TrackMultipleItems() function, i.e., DetermineCurrentAndIntersectRects() and
DetermineTrackerDeltaPosition(). DetermineCurrentAndIntersectRects()
determines the rectangle coordinates of the current graphic item on the palette, e.g., a block, con-
nection end point, or connection bend point, and the rectangle coordinates of its intersection with
the rubber band: this is used to determine whether the current item lies within the rubber band
region. DetermineTrackerDeltaPosition() is used to determine the change in position
(translation) of the rubber band region through the initial and final positions of the center point of
the “m_RectTracker” object.
int CDiagramEngDoc::DetermineCurrentAndIntersectRects(CRectTracker
temp_tracker, CPoint item_posn, double delta)
{
int intersected = 0;
CPoint bottom_right;
CPoint top_left;
CRect current_rect;
CRect intersect_rect;
// Determine the coordinates of the item
top_left.x = (int)(item_posn.x - delta);
top_left.y = (int)(item_posn.y - delta);
bottom_right.x = (int)(item_posn.x + delta);
bottom_right.y = (int)(item_posn.y + delta);
// Determine the current and intersect rectangles
current_rect.SetRect(top_left.x, top_left.y, bottom_right.x,
bottom_right.y);
intersect_rect.IntersectRect(temp_tracker.m_rect, current_rect);
// Check whether the item lies within the rubber band region,
i.e. it intersects.
if(intersect_rect == current_rect)
{
intersected = 1;
}
else
286 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
intersected = 0;
}
// Return the intersected flag.
return intersected;
}
The IntersectRect() function used earlier makes a CRect object equal to the intersection of
two existing rectangles, where the intersection is the largest rectangle contained in both the existing
rectangles [1]. Here, this is used to determine whether a diagram item described by “current_rect”
lies within and equivalently intersects the bounding “temp_tracker” rubber-band-enclosing rectan-
gular region.
function for the WM_KEYDOWN event message for the CDiagramEngView class with the pro-
totype void CDiagramEngView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT
nFlags), and edit it as shown.
CDiagramEngDoc::CDiagramEngDoc()
{
// TODO: add one-time construction code here
// DiagramEng (start)
m_iKeyFlagTrack = 0;
m_iRubberBandCreated = 0;
m_dDeltaLength = 50.0;
// DiagramEng (end)
}
Now that the “m_iKeyFlagTrack” variable has been added to the CDiagramEngDoc class, an acces-
sor function is required to set its value. Hence, add a public accessor function to the CDiagramEngDoc
class, with the prototype void CDiagramEngDoc::SetKeyFlagTrack(int key_flag),
and edit it as shown.
(a) (b)
FIGURE 11.1 Moving a group of items: (a) block diagram items enclosed by a dotted rectangular rubber
band and (b) diagram translated to the right using the mouse left-click–drag–release sequence.
Now, when the user runs the application, a rectangular rubber band region may be drawn from the
initial point of the left-click event to the current point of the mouse cursor: when the user releases
the left mouse button, a dotted rectangle appears, indicating the initial rubber band rectangular
selection. Upon clicking within the rubber band region and moving the mouse cursor, the dotted
rectangle then follows the mouse cursor. Diagram entities may then be selected and moved conve-
niently as a group. Figure 11.1a indicates two blocks, two connections, and a connection bend point,
enclosed by a dotted rubber band region on the left side of the palette. The user may then click
within the dotted-bordered region and move the enclosed items to a different location on the palette
as shown in Figure 11.1b.
The developer will notice a conditional statement involving the flag-like variable, “manual_rect”,
in the function TrackMultipleItems() as shown in the following code excerpt (the ellipsis,
“…”, denotes omitted and unchanged code).
This simply allows the dotted rectangle to be drawn manually, or automatically, using the provided
Draw() function. The manual method requires the developer to set up the four corner points of the
rectangle object but does allow the developer to change the color of the rectangle* the default color
is black. The statement dc.SetROP2(R2_NOTXORPEN) allows the enclosing rectangular region
to be drawn transparently; otherwise, the circumscribed items would not be visible.
* The color of the rectangle is red, as may be observed when running the DiagramEng application.
290 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
should be connected. As a result, only head and tail points that are not connected to other diagram
entities, i.e., bend points or ports, may be moved independently. Finally, in the section concerning
only bend point movement, after a connection bend point is moved, any tail point connected to that
bend point must be updated to the new bend point position, as implemented by the function call:
“m_SystemModel.UpdateConnectionPointTailToBendPoint(&(*it_pt) )”.
void CDiagramEngDoc::OnInitTrackMultipleItems()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int key_flag = 1; // 0 => can’t call TrackMultipleItems(),
1 => can call TrackMultipleItems.
TABLE 11.1
Common Operations Toolbar-Based Track Multiple Items Button
Object, Properties, Settings, and Icon
Object Property Setting Icon
Track Multiple ID ID_INIT_TRACK_MULTIPLE_ITEMS
Items Prompts: status bar Initiate tracking of multiple items/nTrack
and tooltips Multiple Items
TABLE 11.2
Common Operations Toolbar Button Settings: Object, ID, Class, and the Corresponding
Command Event-Handler Function
Object ID Class COMMAND Event-Handler
Track multiple items ID_INIT_TRACK_MULTIPLE_ITEMS CDiagramEngDoc OnInitTrackMultipleItems()
Moving Multiple Items 291
Now, upon running the program, the Track Multiple Items button at the end of the Common
Operations toolbar may be used for the initiation of the tracking of multiple items, rather than
pressing the “T” key (this is somewhat easier). The tracking action ends automatically, either upon
movement of the circumscribed items or upon clicking outside of the dotted rectangular region.
11.8 SUMMARY
The simultaneous movement of multiple items is achieved through the addition of
a TrackMultipleItems() function to the CDiagramEngDoc class that makes use of a
TrackRubberBand() function call upon a CRectTracker, “m_RectTracker”, object. The
TrackMultipleItems() function has three main conditional sections and is entered twice
to perform the entire tracking action. The user can enclose the items to be moved with a rect-
angular rubber band, and then, upon left-clicking within the region, the enclosed items may be
translated across the palette. In order to allow this function to work, additional methods and
variables are introduced, including the DetermineCurrentAndIntersectRects() and
DetermineTrackerDeltaPosition() functions, which determine the rectangular coordi-
nates of an item on the palette and the change in position of the rubber band region, respectively.
Finally, a keystroke (“T”) or the Track Multiple Items toolbar button may be used to initiate the
multiple-item-tracking operation.
REFERENCE
1. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
12 Addition of a Tree
View Control
12.1 INTRODUCTION
The current state of the DiagramEng application allows the user to draw a block diagram on the
palette, including blocks, connections, and bend points. The user can insert the blocks by using the
Block Library Dialog window to select multiple blocks or by clicking on the buttons of the Common
Blocks toolbar (IDR_TB_COMMON_BLOCKS). Here, a Tree View control will be added to the
application, initially as a dialog window with base class CDialog, and then this will be converted to
a CDialogBar instance and docked to the application Main frame.
Adaptations are made here to the original instructions provided in an article made available by
FunctionX, titled “TreeView on a dialog” [1], to clarify the addition of a Tree View control on a
dialog window for an MDI application. In addition, provided here are instructions to dock the dialog
window, upon which the Tree View control resides, to the left side of the application Main frame.
The docking instructions follow closely the work of an article provided by Microsoft Support, titled
“How to initialize child controls in a derived CDialogBar” [2], as explained in Section 12.3.
293
294 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 12.1
Dialog Window (IDD_TREE_DLG) Objects,
Properties, and Settings
Object Property Setting
Tree Control ID ID_TREE_DLG_TREE
Has buttons Checked
Has lines Checked
Lines at root Checked
Button ID IDOK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 12.1 Tree Dialog window showing the controls as specified in Table 12.1.
corresponding dialog window in the editor area and right-click on the dialog box to invoke the
ClassWizard. The Adding a Class message box appears; create a new class with name CTreeDialog
and base class CDialog.
FIGURE 12.2 Common Operations toolbar (second from the top) showing the block library button repre-
sented by a closed folder icon (second from the left).
TABLE 12.2
Common Operations Toolbar Button Settings: Object, ID, Class, and the Corresponding
Command Event-Handler Function
Object (Toolbar Button) ID Class COMMAND Event-Handler
Add Multiple Blocks ID_EDIT_ADD_MULTI_BLOCKS CDiagramEngDoc OnEditAddMultipleBlocks()
The developer will recall that the event-handler function to generate the Block Library Dialog
window is CDiagramEngDoc::OnEditAddMultipleBlocks(). This function declares a
CBlockLibDlg object, “oDlg”, which allows the original check-box-based block-selection dialog
window to be presented to the user as shown in Figure 12.3.
The OnEditAddMultipleBlocks() function is now modified, with the addition of an
extra conditional statement “if(display_item == 2)”, which declares a CTreeDialog object, “oDlg”,
which allows the Tree Dialog (IDD_TREE_DLG) added above, to display the Tree View
control (ID_TREE_DLG_TREE).
Hence, edit the OnEditAddMultipleBlocks() function with the code shown in bold in the
following. To compile and run the code, the header file “TreeDialog.h” needs to be included at the
top of the “DiagramEngDoc.cpp” file since a CTreeDialog object is being implicitly constructed.
void CDiagramEngDoc::OnEditAddMultipleBlocks()
{
// TODO: Add your command handler code here
// DiagramEng (start)
// Display a msg.
if(display_item == 0)
{
AfxMessageBox(“\n CDiagramEngDoc::OnEditAddMultipleBlocks()n”,
MB_OK, 0);
}
else if(display_item == 1)
{
CBlockLibDlg oDlg; // create a dlg obj. of class CBlockLibDlg :
public CDialog
Now, upon running the application and clicking the block library button on the Common Operations
toolbar (ID_TB_COMMON_OPS), the Tree Dialog window shown in Figure 12.1 will appear.
Clicking OK then prints a simple message from the OnEditAddMultipleBlocks() function.
TABLE 12.3
Dialog Window Control, Variable Name, Category,
and Type for the IDD_TREE_DLG (Dialog Window)
Resource
Control Variable Name Category Type
ID_TREE_DLG_TREE M_TreeCtrl Control CTreeCtrl
Notice how, upon using the ClassWizard to add the member variable, the DoDataExchange()
function of the CTreeDialog class (as shown) is automatically updated with the DDX_Control()
function call. The DDX_Control(), Dialog Data Exchange function, manages the data transfer
between the dialog window control, i.e., the Tree Control (ID_TREE_DLG_TREE) and the variable
attached to the control, i.e., the CTreeCtrl, “m_TreeCtrl”, member variable of the CTreeDialog class.
This variable is then used to present data to the user, e.g., through the use of the InsertItem()
function to populate the branch names of the directory-structure-like Tree View.
1. Invoke the ClassWizard, select the Message Maps tab, choose CTreeDialog as the class,
and select CTreeDialog under the Object IDs section and WM_INITDIALOG as the
message.
2. Add an initialization function named CTreeDialog::OnInitDialog(): the initializa-
tion cannot be done within the constructor, since the constructor can only handle variable
initialization, not dialog function-call-based initialization.
3. Edit the CTreeDialog::OnInitDialog() function, as shown in the following, to call
a specific InitTreeControl() function (in bold) wherein all the Tree View control
initialization occurs.
4. Finally, add a public member function to the CTreeDialog class with the prototype
void CTreeDialog::InitTreeControl(void), and edit it as shown.
BOOL CTreeDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
// DiagramEng (start)
// Specifically initialize the Tree control
InitTreeControl();
298 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// DiagramEng (end)
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CTreeDialog::InitTreeControl()
{
// DiagramEng (start)
//AfxMessageBox(“\n CTreeDialog::InitTreeControl()n”, MB_OK, 0);
// Tree label vars - Blocks level
HTREEITEM hBlocks;
HTREEITEM hContinuousBlocks;
HTREEITEM hMathOperationsBlocks;
HTREEITEM hSinkBlocks;
HTREEITEM hSourceBlocks;
HTREEITEM hSubsystemBlocks;
// Tree label vars - ContinuousBlocks level
HTREEITEM hDerivativeBlock;
HTREEITEM hIntegratorBlock;
HTREEITEM hTransferFnBlock;
// Tree label vars - MathOperationsBlocks level
HTREEITEM hDivideBlock;
HTREEITEM hGainBlock;
HTREEITEM hSumBlock;
// Tree label vars - SinkBlocks level
HTREEITEM hOutputBlock;
//HTREEITEM hSubsystemOutBlock; // defined under SubsystemBlocks
// Tree label vars - SourceBlocks level
HTREEITEM hConstantBlock;
HTREEITEM hLinearFnBlock;
HTREEITEM hSignalGeneratorBlock;
//HTREEITEM hSubsystemInBlock; // defined under SubsystemBlocks
// Tree label vars - SubsystemBlocks level
HTREEITEM hSubsystemBlock;
HTREEITEM hSubsystemInBlock;
HTREEITEM hSubsystemOutBlock;
// Specification of vars - Blocks level
hBlocks = m_TreeCtrl.InsertItem(“DiagramEng Blocks”, TVI_ROOT);
hContinuousBlocks = m_TreeCtrl.InsertItem(“ContinuousBlocks”, hBlocks);
hMathOperationsBlocks = m_TreeCtrl.InsertItem(“MathOperationsBlocks”,
hBlocks);
hSinkBlocks = m_TreeCtrl.InsertItem(“SinkBlocks”, hBlocks);
hSourceBlocks = m_TreeCtrl.InsertItem(“SourceBlocks”, hBlocks);
hSubsystemBlocks = m_TreeCtrl.InsertItem(“SubsystemBlocks”, hBlocks);
// Specification of vars - ContinuousBlocks level
//hContinuousBlocks = m_TreeCtrl.InsertItem(“ContinuousBlocks”,
TVI_ROOT);
hDerivativeBlock = m_TreeCtrl.InsertItem(“DerivativeBlock”,
hContinuousBlocks);
hIntegratorBlock = m_TreeCtrl.InsertItem(“IntegratorBlock”,
hContinuousBlocks);
Addition of a Tree View Control 299
hTransferFnBlock = m_TreeCtrl.InsertItem(“TransferFnBlock”,
hContinuousBlocks);
// Specification of vars - MathOperationsBlocks level
//hMathOperationsBlocks = m_TreeCtrl.InsertItem(“MathOperationsBlocks”,
TVI_ROOT);
hDivideBlock = m_TreeCtrl.InsertItem(“DivideBlock”,
hMathOperationsBlocks);
hGainBlock = m_TreeCtrl.InsertItem(“GainBlock”,
hMathOperationsBlocks);
hSumBlock = m_TreeCtrl.InsertItem(“SumBlock”, hMathOperationsBlocks);
// Specification of vars - SinkBlocks level
//hSinkBlocks = m_TreeCtrl.InsertItem(“SinkBlocks”, TVI_ROOT);
hOutputBlock = m_TreeCtrl.InsertItem(“OutputBlock”, hSinkBlocks);
hSubsystemOutBlock = m_TreeCtrl.InsertItem(“SubsystemOutBlock”,
hSinkBlocks);
// Specification of vars - SourceBlocks level
//hSourceBlocks = m_TreeCtrl.InsertItem(“SourceBlocks”, TVI_ROOT);
hConstantBlock = m_TreeCtrl.InsertItem(“ConstantBlock”,
hSourceBlocks);
hLinearFnBlock = m_TreeCtrl.InsertItem(“LinearFnBlock”,
hSourceBlocks);
hSignalGeneratorBlock = m_TreeCtrl.InsertItem(“SignalGeneratorBlock”,
hSourceBlocks);
hSubsystemInBlock = m_TreeCtrl.InsertItem(“SubsystemInBlock”,
hSourceBlocks);
// Specification of vars - SubsystemBlocks level
//hSubsystemBlocks = m_TreeCtrl.InsertItem(“SubsystemBlocks”,
TVI_ROOT);
hSubsystemBlock = m_TreeCtrl.InsertItem(“SubsystemBlock”,
hSubsystemBlocks);
hSubsystemInBlock = m_TreeCtrl.InsertItem(“SubsystemInBlock”,
hSubsystemBlocks);
hSubsystemOutBlock = m_TreeCtrl.InsertItem(“SubsystemOutBlock”,
hSubsystemBlocks);
// DiagramEng (end)
}
Now, upon running the application and clicking the Add Multiple Blocks button on the Common
Operations toolbar, the Tree Dialog window with the Tree View control showing the inserted direc-
tory structure, done using InsertItem() as specified in the InitTreeControl() function, is
visible (Figure 12.4).
1. Select Resource from the Insert menu and choose Bitmap to add a bitmap resource.
2. Double click in the editor area to set the Bitmap properties as shown in Table 12.4: four
icons, all with dimensions of 16 × 16 pixels, are provided for by using a total width of
64 pixels and a height of 16 pixels.
300 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 12.4 Tree Dialog window showing the Tree View control with its initial directory label values:
(a) root DiagramEng Blocks directory, (b) block groups, and (c) specific available blocks.
TABLE 12.4
Bitmap Properties and Settings for the Four (Combined) Bitmap Images
for the Tree View Control
3. Draw four icons side by side in the editor area: (1) a closed folder, (2) a shaded folder,
(3) an unchecked check box, and (4) a checked check box, as shown in Figure 12.5. Use
the 16 × 16 pixel icons provided in the IDR_MAINFRAME toolbar resource as a drawing
guide, being careful to stay within the 16 × 16 pixel limit for each icon.
4. Add a private member variable to the CTreeDialog class of type CImageList with name
“m_TreeImageList”.
FIGURE 12.5 Sample icons for the node leaves of the Tree View control shown in the editor area: each icon
is of size 16 × 16 pixels; hence, the total width of the bitmap property is 64 pixels (as shown in Table 12.4).
Addition of a Tree View Control 301
5. Complete the creation of the CImageList object by calling Create() on the object in the
InitTreeControl() function, i.e., “m_TreeImageList.Create(IDB_BITMAP_TREE,
width, n_images, RGB(255, 255, 255) )”, as shown in the following code.
6. Set the image list using the “normal” flag, TVSIL_NORMAL, which contains both the
selected and unselected image icons for the Tree View control, i.e., “m_TreeDialog.
SetImageList(&m_TreeImageList, TVSIL_NORMAL)”.
7. Finally, insert the tree node identifiers including their associated images using the
InsertItem() function as shown in the following code.
void CTreeDialog::InitTreeControl()
{
// DiagramEng (start)
int icon_flag = 1; // flag indicating whether tree has image icons
associated with it (0) no, (1) yes.
int i_selected; // index of the selected image in the image list
int i_unselected; // index of the unselected image in the image list
int n_images = 4; // no. of images contained side-by-side in the
bitmap resource.
int width = 16; // width (and height) of the square sub-image in
the bitmap resource
HTREEITEM hInsertAfter = TVI_LAST; // handle of item after which new
item should be inserted.
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Print a msg.
sMsg.Format(“\n CTreeDialog::InitTreeControl()nn”);
AfxMessageBox(sMsg, nType, nIDhelp);
hSubsystemBlock = m_TreeCtrl.InsertItem(“SubsystemBlock”,
hSubsystemBlocks);
hSubsystemInBlock = m_TreeCtrl.InsertItem(“SubsystemInBlock”,
hSubsystemBlocks);
hSubsystemOutBlock = m_TreeCtrl.InsertItem(“SubsystemOutBlock”,
hSubsystemBlocks);
}
else
{
// -- Create the Image List
m_TreeImageList.Create(IDB_BITMAP_TREE, width, n_images,
RGB(255,255,255) );
// -- Set the Image List
// TVSIL_NORMAL = sets the normal image list for unselected
(image 1) and selected (image 2) node items.
// TVSIL_STATE = sets the stage image list for Tree View items in
a partic. user-defined state
m_TreeCtrl.SetImageList(&m_TreeImageList, TVSIL_NORMAL);
//m_TreeCtrl.SetImageList(&m_TreeImageList, TVSIL_STATE);
// -- Specification of vars - Blocks level
// Prototype: HTREEITEM InsertItem(UINT nMask, LPCTSTR
lpszItem, int nImage, int nSelectedImage, UINT nState, UINT
nStateMask, LPARAM lParam, HTREEITEM hParent, HTREEITEM
hInsertAfter );
// Choose which image to display for the parent node, based on
whether the parent’s child nodes are expanded
i_unselected = 0;
i_selected = 1;
hBlocks = m_TreeCtrl.InsertItem(“DiagramEng Blocks”, i_unselected,
i_selected, TVI_ROOT);
//hBlocks = m_TreeCtrl.InsertItem(TVIF_STATE | TVIF_TEXT |
TVIF_IMAGE, “DiagramEng Blocks”, i_unselected, i_selected,
TVIS_EXPANDED, TVIS_STATEIMAGEMASK, 0, TVI_ROOT,
hInsertAfter);
//hBlocks = m_TreeCtrl.InsertItem(TVIF_TEXT, “DiagramEng Blocks”,
i_unselected, i_selected, TVIS_EXPANDED, TVIS_STATEIMAGEMASK,
0, TVI_ROOT, hInsertAfter);
hContinuousBlocks = m_TreeCtrl.InsertItem(“ContinuousBlocks”,
i_unselected, i_selected, hBlocks, hInsertAfter);
hMathOperationsBlocks = m_TreeCtrl.InsertItem(“MathOperationsBlocks”,
i_unselected, i_selected, hBlocks, hInsertAfter);
hSinkBlocks = m_TreeCtrl.InsertItem(“SinkBlocks”, i_unselected,
i_selected, hBlocks, hInsertAfter);
hSourceBlocks = m_TreeCtrl.InsertItem(“SourceBlocks”,
i_unselected, i_selected, hBlocks, hInsertAfter);
hSubsystemBlocks = m_TreeCtrl.InsertItem(“SubsystemBlocks”,
i_unselected, i_selected, hBlocks, hInsertAfter);
// Specification of vars - ContinuousBlocks level
i_unselected = 2;
i_selected = 3;
//hContinuousBlocks = m_TreeCtrl.InsertItem(“ContinuousBlocks”,
TVI_ROOT);
hDerivativeBlock = m_TreeCtrl.InsertItem(“DerivativeBlock”,
i_unselected, i_selected, hContinuousBlocks, hInsertAfter);
304 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
hIntegratorBlock = m_TreeCtrl.InsertItem(“IntegratorBlock”,
i_unselected, i_selected, hContinuousBlocks, hInsertAfter);
hTransferFnBlock = m_TreeCtrl.InsertItem(“TransferFnBlock”,
i_unselected, i_selected, hContinuousBlocks, hInsertAfter);
// DiagramEng (end)
}
Now, upon running the code, the Tree View is visible, as shown in Figure 12.6, with the asso-
ciated node images for the unselected and selected states as specified in the CImageList object
“m_TreeImageList”.
FIGURE 12.6 Tree Dialog window showing the Tree View control with images next to the directory labels:
(a) root DiagramEng Blocks directory, (b) block groups, and (c) specific available blocks.
“item_text” value, and the block should appear on the palette as it does when using a toolbar but-
ton, or the Block Library Dialog window:
1. Select the ResourceView of the Workspace pane, choose the ID of the Tree Dialog,
i.e., IDD_TREE_DLG, and right-click on the Tree View control to invoke the ClassWizard.
2. Select CTreeDialog as the class name, ID_TREE_DLG_TREE as the Object ID, and
NM_DBLCLK as the message.
3. Add a function for the NM_DBLCLK message, named OnDblclkTreeDlgTree(), to
the CTreeDialog class and edit it as shown in the following.
{
e_block_type = eConstBlock;
}
else if(item_text == “LinearFnBlock”)
{
e_block_type = eLinearFnBlock;
}
else if(item_text == “SignalGeneratorBlock”)
{
e_block_type = eSignalGenBlock;
}
// -- SUBSYSTEM BLOCKS
else if(item_text == “SubsystemBlock”)
{
e_block_type = eSubsysBlock;
}
else if(item_text == “SubsystemInBlock”)
{
e_block_type = eSubsysInBlock;
}
else if(item_text == “SubsystemOutBlock”)
{
e_block_type = eSubsysOutBlock;
}// end if block type
// -- CONSTRUCT BLOCK based on the enumerated type value.
pDoc->ConstructBlock(e_block_type);
// Print the final concatenated msg. using a msg. box.
//AfxMessageBox(sMsg, nType, nIDhelp);
}// end if leaf node
// DiagramEng (end)
*pResult = 0;
}
The instructions herein follow very closely the work of an article that was published by Microsoft
Support, titled “How to initialize child controls in a derived CDialogBar” [2]. These instructions
consist of nine steps to perform the conversion of a CDialog-based dialog control to a CControlBar-
based control, intended for complex controls to be placed in a control bar rather than on a dialog
window. Here, all nine steps are implemented for the current DiagramEng project. A summary of
these steps is as follows:
// CTreeDialog dialog
//class CTreeDialog : public CDialog
class CTreeDialog : public CDialogBar // -- STEP 1: CONVERSION of CDialog
to CDialogBar
{
// Construction
public:
…
2. In the message map section of the dialog-based source file, change “BEGIN_MESSAGE_
MAP(CTreeDialog, CDialog)” to “BEGIN_MESSAGE_MAP(CTreeDialog,
CDialogBar)”, as shown in bold in the following.
//BEGIN_MESSAGE_MAP(CTreeDialog, CDialog)
BEGIN_MESSAGE_MAP(CTreeDialog, CDialogBar) // -- STEP 1: CONVERSION of
CDialog to CDialogBar
//{ {AFX_MSG_MAP(CTreeDialog)
ON_NOTIFY(NM_DBLCLK, ID_TREE_DLG_TREE, OnDblclkTreeDlgTree)
//} }AFX_MSG_MAP
END_MESSAGE_MAP()
// CTreeDialog dialog
//class CTreeDialog : public CDialog
Addition of a Tree View Control 309
2. In the dialog source file, make changes to the constructor definition to correspond to the
changes made to the declaration in the header file (as shown earlier) to yield the following:
1. Hence, comment out the existing “virtual BOOL OnInitDialog()” function decla-
ration in the dialog class and replace it with “afx_msg LONG OnInitDialog(UINT
wParam, LONG lParam)”, resulting in the following code shown in bold.
public:
void InitTreeControl(void);
//CTreeDialog(CWnd* pParent = NULL); // standard constructor
CTreeDialog(); // -- STEP 2: CONVERSION of CDialog to CDialogBar
// Dialog Data
//{ {AFX_DATA(CTreeDialog)
enum { IDD = IDD_TREE_DLG };
CTreeCtrl m_TreeCtrl;
//} }AFX_DATA
// Overrides
// ClassWizard generated virtual function overrides
//{ {AFX_VIRTUAL(CTreeDialog)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//} }AFX_VIRTUAL
// Implementation
protected:
// Generated message map functions
//{ {AFX_MSG(CTreeDialog)
//virtual BOOL OnInitDialog();
afx_msg LONG OnInitDialog(UINT wParam, LONG lParam); // -- STEP 3:
CONVERSION of CDialog to CDialogBar
afx_msg void OnDblclkTreeDlgTree(NMHDR* pNMHDR, LRESULT* pResult);
//} }AFX_MSG
DECLARE_MESSAGE_MAP()
private:
CImageList m_TreeImageList;
};
2. Now, in the dialog-based source file, comment out the original OnInitDialog() func-
tion and replace it with an empty one with the new function prototype, as shown in bold in
the following:
12.3.4 Step 4: Alter the Message Map to Invoke the OnInitDialog() Function
In the dialog-based source file, locate the BEGIN_MESSAGE_MAP section of the code, and alter
the existing code, shown here,
//BEGIN_MESSAGE_MAP(CTreeDialog, CDialog)
BEGIN_MESSAGE_MAP(CTreeDialog, CDialogBar) // -- STEP 1: CONVERSION of
CDialog to CDialogBar
//{ {AFX_MSG_MAP(CTreeDialog)
ON_NOTIFY(NM_DBLCLK, ID_TREE_DLG_TREE, OnDblclkTreeDlgTree)
//} }AFX_MSG_MAP
END_MESSAGE_MAP()
//BEGIN_MESSAGE_MAP(CTreeDialog, CDialog)
BEGIN_MESSAGE_MAP(CTreeDialog, CDialogBar) // -- STEP 1: CONVERSION of
CDialog to CDialogBar
//{ {AFX_MSG_MAP(CTreeDialog)
ON_NOTIFY(NM_DBLCLK, ID_TREE_DLG_TREE, OnDblclkTreeDlgTree)
//} }AFX_MSG_MAP
ON_MESSAGE(WM_INITDIALOG, OnInitDialog) // -- STEP 4: CONVERSION of
CDialog to CDialogBar
END_MESSAGE_MAP()
Microsoft Support [2] indicates that “The CDialogBar class does not have a virtual
OnInitDialog() function, and therefore calling one does not work. UpdateData is called to
subclass or initialize any child controls.” This is why CDialog::OnInitDialog() is com-
mented out and UpdateData() is introduced.
Notice how the InitTreeControl() function is being called (after HandleInitDialog()
and UpdateData()) from within the OnInitDialog() function: this function is specific to the
actual Tree View control on the dialog window, wherein all Tree View control initialization takes
place. This call was also made from within the original OnInitDialog() function: the initializa-
tion logic is unchanged here.
CToolBar m_wndToolBar;
CTreeDialog m_TreeDlgBar; // -- STEP 7: CONVERSION of CDialog to
CDialogBar
// Generated message map functions
protected:
//{ {AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
// NOTE - the ClassWizard will add and remove member functions
here.
// DO NOT EDIT what you see in these blocks of generated code!
//} }AFX_MSG
DECLARE_MESSAGE_MAP()
};
12.3.8 Step 8: Invoke the Create() Method for the New CDialogBar Instance
The original dialog window has now been converted to a dialog bar and an instance is
declared in the CMainFrame class. To complete the creation of this CTreeDialog instance
as a dialog bar, the Create() function needs to be called on the member object from within
CMainFrame::OnCreate(), i.e., “m_TreeDlgBar.Create(this, IDD_TREE_DLG,
CBRS_LEFT, IDD_TREE_DLG)”, as shown in the following. The first parameter is a pointer
to the parent window upon which the dialog bar should be docked, the second parameter is the
resource ID, the third parameter denotes the alignment style of the dialog bar, and the last parameter
is the control ID of the dialog bar: here, the resource and control share the same ID.
Two examples of the OnCreate() function are provided in the following: (1) a simplified
version, for clarity reasons, involving the provided toolbar (IDR_MAINFRAME) and status
bar, and the new CDialogBar-based “m_TreeDlgBar” Tree View dialog bar (IDD_TREE_DLG)
of a bare implementation, and (2) the more complicated current DiagramEng application ver-
sion, including the provided toolbars, the Common Blocks (IDR_TB_COMMON_BLOCKS) and
Common Operations (IDR_TB_COMMON_OPS) toolbars, and the new Tree View dialog bar
(IDD_TREE_DLG). The code to be inserted at steps 8 and 9 is shown in boldface.
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |
CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME) )
{
TRACE0(“Failed to create toolbarn”);
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT) ) )
{
TRACE0(“Failed to create status barn”);
return -1; // fail to create
}
// -- STEP 8: CONVERSION of CDialog to CDialogBar
if(!m_TreeDlgBar.Create(this, IDD_TREE_DLG, CBRS_LEFT, IDD_TREE_DLG) )
314 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
TRACE0(“Failed to create dialog barn”);
return -1; // failed to create
}
// TODO: Delete these three lines if you don’t want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
// -- STEP 9: CONVERSION of CDialog to CDialogBar
//m_TreeDlgBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS |
CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
//m_TreeDlgBar.EnableDocking(CBRS_ALIGN_ANY);
//DockControlBar(&m_TreeDlgBar);
return 0;
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |
CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME) )
{
TRACE0(“Failed to create toolbarn”);
return -1; // fail to create
}
// DiagramEng (start)
// -- CREATE THE CommonOps TOOLBAR
// NOTE: ‘this’ is the ptr to the parent frame wnd to which the
toolbar will be attached.
// CreateEx creates the toolbar
// LoadToolBar loads the toolbar specified by the ID
if(!m_wndTBCommonOps.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |
WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS |
CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndTBCommonOps.
LoadToolBar(IDR_TB_COMMON_OPS) )
{
// Failed to create msg.
TRACE0(“Failed to create toolbarn”);
return -1;
}
// -- END CREATION OF CommonOps TOOLBAR
// -- CREATE THE CommonBlocks TOOLBAR
// NOTE: ‘this’ is the ptr to the parent frame wnd to which the
toolbar will be attached.
// CreateEx creates the toolbar
// LoadToolBar loads the toolbar specified by the ID
if(!m_wndTBCommonBlocks.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |
WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS |
CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndTBCommonBlocks.
LoadToolBar(IDR_TB_COMMON_BLOCKS) )
Addition of a Tree View Control 315
{
// Failed to create msg.
TRACE0(“Failed to create toolbarn”);
return -1;
}
// -- END CREATION OF CommonBlocks TOOLBAR
// DiagramEng (end)
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT) ) )
{
TRACE0(“Failed to create status barn”);
return -1; // fail to create
}
// -- STEP 8: CONVERSION of CDialog to CDialogBar
if(!m_TreeDlgBar.Create(this, IDD_TREE_DLG, CBRS_LEFT, IDD_TREE_DLG) )
{
TRACE0(“Failed to create dialog barn”);
return -1; // failed to create
}
// TODO: Delete these three lines if you don’t want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
// DiagramEng (start)
// -- ENABLE DOCKING OF TOOLBARS
// Enable docking for the Common Ops toolbar (IDR_TB_COMMON_OPS)
// NOTE: enables the toolbar for docking with the frame wnd.
m_wndTBCommonOps.EnableDocking(CBRS_ALIGN_ANY);
// Enable docking for the Common Blocks toolbar (IDR_TB_COMMON_BLOCKS)
// NOTE: enables the toolbar for docking with the frame wnd.
m_wndTBCommonBlocks.EnableDocking(CBRS_ALIGN_ANY);
// DiagramEng (end)
EnableDocking(CBRS_ALIGN_ANY); // called for the frame wnd.
DockControlBar(&m_wndToolBar); // frame wnd fn passed & of toolbar var.
physically docks the toolbar to the frame wnd.
// DiagramEng (start)
// -- DOCK TOOLBARS
// Dock the Common Ops toolbar.
DockControlBar(&m_wndTBCommonOps);
// Dock the Common Blocks toolbar.
DockControlBar(&m_wndTBCommonBlocks);
// DiagramEng (end)
// -- STEP 9: CONVERSION of CDialog to CDialogBar
//m_TreeDlgBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS |
CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
//m_TreeDlgBar.EnableDocking(CBRS_ALIGN_ANY);
//DockControlBar(&m_TreeDlgBar);
return 0;
}
316 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The bar style and dynamic docking of the CDialogBar may be set using SetBarStyle() and
EnableDocking(), respectively, as shown in the following code. However, if permanent docking
is desired, then these lines may be commented out. Note that this addition should be inserted at the
end of the CMainFrame::OnCreate() function as shown earlier.
void CDiagramEngDoc::OnEditAddMultipleBlocks()
{
// TODO: Add your command handler code here
// DiagramEng (start)
// Local var declaration
int display_item = 2; // used to display msg box or dlg wnd.
// Display a msg.
if(display_item == 0)
{
AfxMessageBox(“\n CDiagramEngDoc::OnEditAddMultipleBlocks()n”,
MB_OK, 0);
}
else if(display_item == 1)
{
CBlockLibDlg oDlg; // create a dlg obj. of class CBlockLibDlg :
public CDialog
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
//AfxMessageBox(“\n CDiagramEngDoc::OnEditAddMultipleBlocks()
n”, MB_OK, 0);
}
}
/*else if(display_item == 2)
Addition of a Tree View Control 317
FIGURE 12.7 DiagramEng application showing the block library directory as a CDialogBar instance bound
to the left side of the Main frame.
{
CTreeDialog oDlg; // create a dlg obj. of class CTreeDialog :
public CDialog
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
AfxMessageBox(“\n CDiagramEngDoc::OnEditAddMultipleBlocks()
n”, MB_OK, 0);
}
}*/
// DiagramEng (end)
}
However, since the CTreeDialog class has had its base class changed from CDialog to CDialogBar, and
DoModal() is based upon CDialog and not CDialogBar, then calling DoModal() on the CDialogBar-
based object results in an error. Hence, the contents of the conditional “else if(display_item == 2)”
section should be commented out, as shown. The developer will recall that a CTreeDialog variable,
“m_TreeDlgBar”, was added to the CMainFrame class, and the CMainFrame::OnCreate() func-
tion completes the creation of what is now a CDialogBar-based resource.
In addition, the integer “display_item” should be set to 1, since the user may still want to use the
BlockLibraryDialog (IDD_BLOCKLIBDLG) dialog-based block insertion method: the Tree View
form of the block library is now provided by default, attached to the Main frame, as a CDialogBar
instance.
Now, upon running the program, the block library appears as a CDialogBar object bound to the
left side (given commenting out of the code included at step 9 mentioned earlier) of the Main frame.
To tidy the appearance of this CDialogBar instance, the original OK and Cancel buttons were
removed and the window resized to convenient proportions, as shown in Figure 12.7.
TABLE 12.5
View Menu Entry Object, ID, Caption, Prompts
(Status Bar and Tooltips), and Settings
Object Property Setting
Block Directory ID ID_VIEW_BLOCK_DIR
Caption &BlockDirectory
Prompts View block directory\nBlock Directory
void CMainFrame::OnViewBlockDirectory()
{
// TODO: Add your command handler code here
// DiagramEng (start)
BOOL bShow;
// Check state of block library toolbar
bShow = ( (m_TreeDlgBar.GetStyle() & WS_VISIBLE) != 0);
// Switch state
// NOTE: & of toolbar, bool show/hide toolbar, delay showing toolbar
(FALSE => no delay)
ShowControlBar(&m_TreeDlgBar, !bShow, FALSE);
// Recalculate layout
RecalcLayout();
// DiagramEng (end)
}
Now, the user interface needs to be updated with a tick-like check mark next to the menu entry when
the toolbar is visible. Hence, invoke the ClassWizard, choose CMainFrame as the class, and select
ID_VIEW_BLOCK_DIR as the object ID. Select UPDATE_COMMAND_UI as the message and
add an event-handler function named OnUpdateViewBlockDirectory(). Edit it as shown in
the following.
(a) (b)
FIGURE 12.8 Block Directory entry under the View menu allows controlling the visibility of the block
directory Tree View control: (a) viewing of the block directory and (b) hiding of the block directory.
Now, upon running the program, the user has the option of showing or hiding the block directory
Tree View (CDialogBar) object as shown in Figure 12.8.
12.6 SUMMARY
A Tree Dialog resource is added to the project with class CTreeDialog and base class CDialog.
A variable is attached to the dialog window control to be able to display Tree View proper-
ties. An OnInitDialog() function is added to the CTreeDialog class and this calls the
InitTreeControl() function to initialize the Tree View control with the correct properties.
Icons are added to the Tree View control and are displayed next to the leaves of the tree. An event-
handler function OnDblclkTreeDlgTree() is added for the double-click event on a node of
the Tree View control, which results in the correct block being constructed and positioned on the
palette. The docking of the Tree Dialog window is performed manually, since the CTreeDialog
class originally inherited from the CDialog base class, rather than the CDialogBar class. Nine
steps, originally published by Microsoft Support [2], are followed to implement the docking of the
Tree Dialog window to the Main frame. Finally, the visibility of the Tree View–based CDialogBar
object is controlled by adding an entry to the View menu and event-handler functions to the
CMainFrame class.
REFERENCES
1. TreeView on a dialog, http://www.functionx.com/visualc/treeview/tvdlg1.htm, FunctionX Inc., 1999.
2. How to initialize child controls in a derived CDialogBar, Microsoft Support, http://support.microsoft.com/
kb/185672, Microsoft Corporation, 2006.
13 Review of Menu and
Toolbar-Based Functionality
Part I
13.1 INTRODUCTION
The software development process thus far has resulted in building the main graphical user interface
(GUI)-based functionality, allowing the user to construct a basic model diagram of the system to
be simulated. However, various menu items and toolbar buttons have no working functionality but
simply call an event-handler function that presents a message in a message box. A brief review
of the developmental status is required to find out what functionality has and has not been imple-
mented. The developer can then concentrate on the essential functionality required for the purpose
of application demonstration, postponing nonessential items until later.
13.2 MENUS
The menus to be reviewed here are the Main frame–based menus (File, View, and Help) and the Child
frame–based menus (File, Edit, View, Model, Simulation, Format, Tools, Window, and Help). All the
menu items, their intended actions and developmental status, are listed in Tables 13.1 through 13.12.
321
322 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 13.1
Main Frame–Based File Menu Items, Intended Action,
and Developmental Status
File Action Developmental Status
New Creates a new document Works
Open Opens an existing document Incomplete: to chain to Child frame–based action
Print Setup Changes the printer and printing options Works
Recent File Views recent files Incomplete: to chain to Child frame–based action
Exit Quits the application Works: no child document is present, so no
prompting to save is necessary
TABLE 13.2
Main Frame–Based View Menu Items, Intended Action,
and Developmental Status
View Action Developmental Status
Toolbar Shows or hides the toolbar Works
Status Bar Shows or hides the status bar Works
TABLE 13.3
Main Frame–Based Help Menu Item, Intended Action,
and Developmental Status
Help Action Developmental Status
About DiagramEng Displays program information, version number, Works: as a dialog window
and copyright
TABLE 13.4
Child Frame–Based File Menu Items, Intended Action, and Developmental Status
File Action Developmental Status
New Creates a new document Works
Open Opens an existing document Incomplete: opens a blank document
Close Closes the active document Incomplete: cannot save content
Save Saves the active document Incomplete: saves file but not content
Save As Saves the active document with a new name Incomplete: saves new file but not content
Print Prints the active document Incomplete: prints too small
Print Preview Displays full pages Incomplete: consistent with print
Print Setup Changes the printer and printing options Works
Recent File Views recent files and prompts to open the document Incomplete: no prior content saved on
to-be-opened document
Exit Quits the application and prompts to save documents Incomplete: no content saved on document
Review of Menu and Toolbar-Based Functionality 323
TABLE 13.5
Child Frame–Based Edit Menu Items, Intended Action, and Developmental Status
Edit Action Developmental Status
Undo Undoes the last action Incomplete: not implemented
Cut Cuts the selection and puts it on the Clipboard Incomplete: not implemented
Copy Copies the selection and puts it on the Clipboard Incomplete: not implemented
Paste Inserts Clipboard contents Incomplete: not implemented
Delete Deletes the selection Incomplete: OnEditDelete()
Select All Selects all content Incomplete: OnEditSelectAll()
Add Multiple Blocks Adds multiple blocks to the model Works
TABLE 13.6
Child Frame–Based View Menu Items, Intended Action, and Developmental Status
View Action Developmental Status
Toolbar Shows or hides the toolbar Works
Status Bar Shows or hides the status bar Works
Common Ops. Toolbar Shows or hides the Common Operations toolbar Works
Common Blocks Toolbar Shows or hides the Common Blocks toolbar Works
Block Directory Shows or hides the block directory Works
Auto Fit Diagram Automatically fits diagram to view Incomplete: OnViewAutoFitDiagram()
Zoom In Zooms in to detail Incomplete: OnViewZoomIn()
Zoom Out Zooms out of detail Incomplete: OnViewZoomOut()
TABLE 13.7
Child Frame–Based Model Menu Items, Intended Action,
and Developmental Status
Model Action Developmental Status
Build Model Builds the active model Incomplete: OnModelBuild()
Build Subsystem Builds the selected model subsystem Incomplete: OnModelBuildSubsys()
TABLE 13.8
Child Frame–Based Simulation Menu Items, Intended Action,
and Developmental Status
Simulation Action Developmental Status
Start Starts the simulation Incomplete: OnSimStart()
Stop Stops the simulation Incomplete: OnSimStop()
Numerical Solver Sets the numerical solver parameters Incomplete: OnSimNumericalSolver()
324 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 13.9
Child Frame–Based Format Menu Item, Intended Action,
and Developmental Status
Format Action Developmental Status
Show Annotations Shows diagram annotations Incomplete: OnFormatShowAnnotations()
TABLE 13.10
Child Frame–Based Tools Menu Item, Intended Action,
and Developmental Status
Tools Action Developmental Status
Diagnostic Info. Presents diagnostic information Incomplete: OnToolsDiagnosticInfo()
TABLE 13.11
Child Frame–Based Window Menu Items, Intended Action,
and Developmental Status
Window Action Developmental Status
New Window Opens another window for the active document Works (provided)
Cascade Arranges windows so they overlap Works (provided)
Tile Arranges windows as nonoverlapping tiles Works (provided)
Arrange Icons Arranges icons at the bottom of the window Works (provided)
Close All Documents Closes all documents Incomplete: OnWndCloseAllDocs()
Name of child windows Shows names of windows and activates the Works (provided)
selected window
TABLE 13.12
Child Frame–Based Help Menu Items, Intended Action, and Developmental Status
Help Action Developmental Status
About DiagramEng Displays program information, version number Works (dialog window)
and copyright
Using DiagramEng Displays information about using the Incomplete: OnHelpUsingDiagramEng()
DiagramEng application
FIGURE 13.1 Main frame window showing the menu items and limited toolbar functionality in the absence
of a child (document) window.
Review of Menu and Toolbar-Based Functionality 325
FIGURE 13.2 Child frame window showing the applicable menu items, toolbars, and an empty palette.
TABLE 13.13
Context Menu Items, Intended Action, and Developmental Status
Context Action Developmental Status
Delete Item Deletes the selection Works: OnDeleteItem()
Insert Bend Point Inserts a bend point upon connection object Works: OnInsertBendPoint()
Set Properties Invokes a dialog window to set properties No functionality at present
for the selection
13.3 TOOLBARS
The existing toolbars are (1) the Main Frame toolbar (IDR_MAINFRAME), which presents general
actions that may be found under the File, Edit, and Help menus; (2) the Common Operations toolbar
(IDR_TB_COMMON_OPS), which provides common operations that may be found under the exist-
ing menus (see Figure 13.3); and (3) the Common Blocks toolbar (IDR_TB_COMMON_BLOCKS),
which allows the user to easily add diagram blocks to the model. The Tables 13.14 through 13.16 list
the functionality that should be associated with the existing toolbar buttons; this behavior is also
available from the equivalent menu entries where appropriate.
FIGURE 13.3 Three current toolbars of the DiagramEng application found directly beneath the Format
menu: (1) Main Frame, (2) Common Operations, and (3) Common Blocks (top down).
326 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 13.14
Main Frame Toolbar (IDR_MAINFRAME) Buttons, Corresponding Menu
Entries, Action, and Developmental Status
Main Frame Corresponding
Toolbar Button Menu Item Action Developmental Status
New File/New Creates a new document Works
Open File/Open Opens an existing document Incomplete: opens a
blank document
Save File/Save Saves the active document Incomplete: saves file
but not content
Cut Edit/Cut Cuts the selection and puts it on Not implemented
the Clipboard
Copy Edit/Copy Copies the selection and puts it Not implemented
on the Clipboard
Paste Edit/Paste Inserts Clipboard contents Not implemented
Print File/Print Prints the active document Prints too small
About Help/About Displays program information, Works (dialog window)
version number, and copyright
TABLE 13.15
Common Operations Toolbar (IDR_TB_COMMON_OPS) Buttons, Corresponding Menu
Entries, Action, and Developmental Status
Common Operations
Toolbar Button Corresponding Menu Item Action Developmental Status
Select All Edit/Select All Selects the entire model Incomplete: OnEditSelectAll()
Add Multiple Blocks Edit/Add Multiple Blocks Adds multiple blocks Works:
OnEditAddMultipleBlocks()
Auto Fit Diagram View/Auto Fit Diagram Automatically fits diagram Incomplete:
to view OnViewAutoFitDiagram()
Build Model Model/Build Builds the model Incomplete: OnModelBuild()
Start Simulation Simulation/Start Starts the simulation Incomplete: OnSimStart()
Stop Simulation Simulation/Stop Stops the simulation Incomplete: OnSimStop()
Numerical Solver Simulation/Numerical Solver Sets the numerical solver Incomplete:
parameters OnSimNumericalSolver()
Show Annotations Format/Show Annotations Shows diagram annotations Incomplete:
OnFormatShowAnnotations()
Track Multiple Items No corresponding menu item Selects and moves multiple Works: TrackMultipleItems()
items
TABLE 13.16
Common Blocks Toolbar (IDR_TB_COMMON_BLOCKS) Buttons in the Order
of Their Appearance on the Toolbar (from Left to Right), Block Type, Action,
and Developmental Status
Common Blocks
Toolbar Button Block Type Action Developmental Status
Derivative Block Continuous Inserts Derivative Block Works
Integrator Block Continuous Inserts Integrator Block Works
TransferFn Block Continuous Inserts TransferFn Block Works
Divide Block Math Operations Inserts Divide Block Works
Gain Block Math Operations Inserts Gain Block Works
Sum Block Math Operations Inserts Sum Block Works
Output Block Sink Inserts Output Block Works
Constant Block Source Inserts Constant Block Works
LinearFn Block Source Inserts LinearFn Block Works
Signal Generator Block Source Inserts SignalGenerator Block Works
Subsystem Block Subsystem Inserts Subsystem Block Works
SubsystemIn Block Subsystem/Source Inserts SubsystemIn Block Works
SubsystemOut Block Subsystem/Sink Inserts SubsystemOut Block Works
(a) (b)
FIGURE 13.4 Block listing mechanisms: (a) Tree View control and (b) dialog window resource.
TABLE 13.17
Essential Development: Child Frame Menu Items, Actions, and Desired
Developmental Results
Menu Item Action Developmental Result
File/Open Opens a document Opens a previously saved document with the content
present
File/Close Closes a document Closes a document prompting to save if necessary
File/Save Saves the active document Saves the content of the model to a model file
File/Save As Saves the active document Saves the content of the model to a model file with a
with a new name new name
File/Exit Exits the application Exits the application prompting to save if necessary
Edit/Delete Deletes the selection Links to the existing CDiagramEngDoc::OnDeleteItem()
function
Model/Build Model Builds the active model Builds the model
Simulation/Start Starts the simulation Starts execution of the simulation
Simulation/Stop Stops the simulation Stops execution of the simulation
Simulation/Numerical Solver Sets the numerical solver Generates a dialog window with which all system
parameters numerical solver parameters may be assigned
Window/Close All Documents Closes all documents Closes all documents sequentially prompting to save if
necessary
Review of Menu and Toolbar-Based Functionality 329
TABLE 13.18
Essential Development: Context Menu Item, Its Intended Action,
and Desired Developmental Result
Menu Item Action Developmental Result
Set Properties Invokes a dialog window to set Invokes a dialog window based on the location of the
properties for the selection mouse pointer and sets properties for the selection
TABLE 13.19
Essential Development: Toolbars, Toolbar Buttons, Corresponding Menu Items, Actions,
and Desired Developmental Results
Corresponding
Toolbar Toolbar Button Menu Item Action Developmental Result
IDR_MAINFRAME Open File/Open Opens a document Opens a previously saved
document
IDR_MAINFRAME Save File/Save Saves the active Saves the document
document content to a file
IDR_TB_COMMON_OPS Build Model Model/Build Builds the model Builds the model
IDR_TB_COMMON_OPS Start Simulation Simulation/Start Starts the simulation Starts execution of the
simulation
IDR_TB_COMMON_OPS Stop Simulation Simulation/Stop Stops the simulation Stops execution of the
simulation
IDR_TB_COMMON_OPS Numerical Solver Simulation/ Configures the Generates a dialog
Numerical simulation window to set numerical
Solver parameters solver parameters
project, when all class data structures are established. The Delete entry under the Edit menu will
be changed to Delete Grouped Items to delete multiple items requiring a special grouping selection
rather than just one item at a time. Both a Delete Item and a Delete Grouped Items menu entry will
eventually be placed on the Context menu, allowing the user multiple mechanisms to perform object
deletion to complement the Edit menu entry. The Build Model entry under the Model menu will be
implemented at the model validation stage of the project and is covered in Chapter 18. The Start and
Stop entries under the Simulation menu will be implemented at the Signal Propagation stage of the
project. However, the Numerical Solver item, used to generate a dialog window with which simula-
tion parameters may be entered, will be completed in the next chapter. The Close All Documents
item under the Window menu will close all Child frame–based documents and prompt the user to
save if appropriate. This is related to data serialization and will be addressed in Chapter 25.
toolbar-based functionality, including the appropriate toolbar resource, toolbar button, corresponding
menu item, action, and desired developmental result. The Open and Save buttons corresponding to the
File menu entries of the same name will be addressed in Chapter 25. The Start Simulation and Stop
Simulation entries will be implemented in the Signal Propagation stage of the project. The Numerical
Solver dialog window is implemented in the following chapter.
The developer will notice that all of these toolbar button functions correspond to their menu item
equivalents; hence, functionality need only be added to the event-handler function for the shared ID
concerned, and both the menu item and toolbar button selection will yield the same functional result.
13.5 SUMMARY
A review is made of existing functionality to determine the essential items to be added to the project.
The Main frame–based, Child frame–based, and Context menus are examined, and the important
items to be added for the Main frame–based menu concern serialization and those of the Child frame–
based menu involve serialization, model building, and simulation execution. The Context menu should
be extended to facilitate context-based actions, e.g., the setting of item properties through the use of
a dialog window and the deletion of individual or grouped items. The Main Frame, Common Blocks,
and Common Operations toolbars are reviewed to guide further development, and the essential func-
tionality concerns serialization, model building, simulation execution, and the setting of numerical
solver properties. Extensions to the project, including GUI elements, model building and computation,
and serialization, are individually documented in the relevant chapters that follow.
14 Context Menu Extension
14.1 INTRODUCTION
The Context menu is extended to allow the user to right-click on a diagram item, and based upon
the location of the mouse cursor at the point of menu invocation, the appropriate context-specific
functional operation occurs. A Numerical Solver dialog is added to the project that allows the user
to set numerical solver properties that are used in model computation, and the dialog is invoked
either through the use of the Numerical Solver entry of the Simulation menu, a Common Operations
toolbar button, or quite easily via the Set Properties entry of the Context menu when the user
right-clicks an empty portion of the palette. In addition, the Set Properties entry invokes functions
to allow the user to set the properties of blocks and connections. A Delete Grouped Items entry
is added to the Context and Edit menus, which allows the user to delete a selection of previously
circumscribed items on the palette. The Context-menu-based approach allows for a more efficient
usage of the DiagramEng application modeling features.
Here, the Numerical Solver dialog is to be added to the project by implementing these six key steps,
to allow the setting of the application’s numerical solver properties.
331
332 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 14.1
Numerical Solver Dialog Window Controls: Objects,
Properties, and Settings
Object Property Setting
Group Box ID ID_NUM_SOLVER_DLG_GB_TIME
Caption Time Parameters
Static Text ID ID_NUM_SOLVER_DLG_TXT_TSTART
Caption Start Time:
Edit Box ID ID_NUM_SOLVER_DLG_TSTART
Static Text ID ID_NUM_SOLVER_DLG_TXT_TSTOP
Caption Stop Time:
Edit Box ID ID_NUM_SOLVER_DLG_TSTOP
Static Text ID ID_NUM_SOLVER_DLG_TXT_TSTEPSIZE
Caption Time Step Size:
Edit Box ID ID_NUM_SOLVER_DLG_TSTEPSIZE
Static Text ID ID_NUM_SOLVER_DLG_TXT_TSTEPTYPE
Caption Time Step Type:
Combo Box ID ID_NUM_SOLVER_DLG_CB_TSTEPTYPE
Data Fixed-step
Variable-step
Group Box ID ID_NUM_SOLVER_DLG_GB_INTEGRATOR
Caption Integration Parameters
Static Text ID ID_NUM_SOLVER_DLG_TXT_INTEGRATOR
Caption Integration Method:
Combo Box ID ID_NUM_SOLVER_DLG_CB_INTEGRATOR
Data Euler (1st Order)
Runge-Kutta (4th Order)
Static Text ID ID_NUM_SOLVER_DLG_TXT_ATOL
Caption Absolute Error Tolerance:
Edit Box ID ID_NUM_SOLVER_DLG_ATOL
Static Text ID ID_NUM_SOLVER_DLG_TXT_RTOL
Caption Relative Error Tolerance:
Edit Box ID ID_NUM_SOLVER_DLG_RTOL
Group Box ID ID_NUM_SOLVER_DLG_GB_WARNING
Caption Warning
Static Text ID ID_NUM_SOLVER_DLG_TXT_WARNING
Caption Warning Message:
Edit Box ID ID_NUM_SOLVER_DLG_WARNING
Multiline Checked
Auto HScroll Checked
Auto VScroll Checked
Button ID ID_NUM_SOLVER_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
Context Menu Extension 333
FIGURE 14.1 Numerical Solver dialog window showing the controls as specified in Table 14.1.
TABLE 14.2
Dialog Window Controls, Variable Names, Categories, and Types
for the IDD_NUM_SOLVER_DLG Resource
Control Variable Name Category Type
ID_NUM_SOLVER_DLG_TSTART m_dTimeStart Value double
ID_NUM_SOLVER_DLG_TSTOP m_dTimeStop Value double
ID_NUM_SOLVER_DLG_TSTEPSIZE m_dTimeStepSize Value double
ID_NUM_SOLVER_DLG_CB_TSTEPTYPE m_strTimeStepType Value CString
ID_NUM_SOLVER_DLG_CB_INTEGRATOR m_strIntegrationMethod Value CString
ID_NUM_SOLVER_DLG_ATOL m_dATOL Value double
ID_NUM_SOLVER_DLG_RTOL m_dRTOL Value double
ID_NUM_SOLVER_DLG_WARNING m_strWarning Value CString
TABLE 14.3
Objects, IDs, Class and Event-Handler Functions for the CNumericalSolverDialog Class
Object ID Class COMMAND Event-Handler
OK button ID_NUM_SOLVER_DLG_BTN_OK CNumericalSolverDialog OnNumericalSolverDlgBtnOk()
Cancel button IDCANCEL (default) CNumericalSolverDialog OnCancel()
void CNumericalSolverDialog::OnNumericalSolverDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CNumericalSolverDialog::OnNumericalSolverDlgBt
nOk()\n”, MB_OK, 0);
// Update variable values with the Dlg Wnd control values
UpdateData(TRUE);
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
CSystemModel::CSystemModel(void)
{
…
// Time parameters
Context Menu Extension 335
m_dTimeStart = 0.0;
m_dTimeStop = 10.0;
m_dTimeStepSize = 0.01;
m_strTimeStepType = “Fixed-step”;
// Integration parameters
m_dATOL = 0.0001;
m_dRTOL = 0.0001;
m_strIntegrationMethod = “Runge-Kutta (4th Order)”;
m_strWarning = “OK”;
…
}
14.2.6 Add a Dialog Window Parameter Input Function to the Underlying Class
To allow a transfer of the numerical-solver-related variables between the CSystemModel and
CNumericalSolverDialog classes, a public member function is to be added to the CSystemModel
class with the prototype void CSystemModel::DlgWndParameterInput(void): edit the
latter as shown to obtain consistent numerical input.
The header file for the CNumericalSolverDialog class is required by the CSystemModel
function, since an “oDlg” object of type CNumericalSolverDialog is used. Hence add #include
“NumericalSolverDialog.h” at the top of the “SystemModel.cpp” source file.
void CSystemModel::DlgWndParameterInput()
{
int input = 0; // flag denoting correct input
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class vars using the CSystemModel class vars
oDlg.m_dTimeStart = m_dTimeStart;
oDlg.m_dTimeStop = m_dTimeStop;
oDlg.m_dTimeStepSize = m_dTimeStepSize;
oDlg.m_strTimeStepType = m_strTimeStepType;
oDlg.m_strIntegrationMethod = m_strIntegrationMethod;
oDlg.m_dATOL = m_dATOL;
oDlg.m_dRTOL = m_dRTOL;
oDlg.m_strWarning = m_strWarning;
m_dTimeStepSize = oDlg.m_dTimeStepSize;
m_strTimeStepType = oDlg.m_strTimeStepType;
m_strIntegrationMethod = oDlg.m_strIntegrationMethod;
m_dATOL = oDlg.m_dATOL;
m_dRTOL = oDlg.m_dRTOL;
m_strWarning = oDlg.m_strWarning;
Initially an “oDlg” dialog object of type CNumericalSolverDialog is instantiated with the values of
the CSystemModel class. Then, the while loop is executed to obtain correct user input through the
dialog window: the time-based parameters must be consistent, i.e., the initial time, t0, final time, tf,
and time-step size, δt, are subject to the conditions 0 ≤ t0 < tf and 0 < δt ≤ (tf − t0).
Note that upon compilation of the code, the following error occurred: “error C2065:
‘IDD_NUM_SOLVER_DLG’: undeclared identifier”. To resolve this, add #include “Resource.h” at
the top of the “NumericalSolverDialog.h” header file as shown in bold in the following.
#if !defined(AFX_NUMERICALSOLVERDIALOG_H__96D85E00_
E756_467A_890C_76E3FF10F3E0__INCLUDED_)
#define AFX_NUMERICALSOLVERDIALOG_H__96D85E00_
E756_467A_890C_76E3FF10F3E0__INCLUDED_
Context Menu Extension 337
void CDiagramEngDoc::OnSimNumericalSolver()
{
// TODO: Add your command handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CDiagramEngDoc::OnSimNumericalSolver()\n”, MB_OK, 0);
// Transfer values bw CNumericalSolverDialog and CSystemModel
m_SystemModel.DlgWndParameterInput();
// DiagramEng (end)
}
Now if the developer builds and runs the DiagramEng application, the user is able to update the
simulation parameters, through the Numerical Solver dialog, for the underlying model.
TABLE 14.4
Child Frame-Based Menu Item, ID, Caption, Prompts
(Status Bar and Tooltips), and Settings
Menu Item Property Setting
Edit/Delete Grouped Items ID ID_EDIT_DELETE_GROUPED_ITEMS
Caption Delete &Grouped Items
Prompts Delete items enclosed by rectangle\nDelete
Grouped Items
TABLE 14.5
Context Menu Item, ID, Caption, and Prompts (Status Bar and Tooltips)
Menu Item ID Caption Prompts
Delete Grouped Items ID_EDIT_DELETE_ Delete &Grouped Items Delete items enclosed by
GROUPED_ITEMS rectangle\nDelete Grouped Items
Context Menu Extension 339
list<CConnection*>::iterator it_con;
list<CConnection*> &con_list = GetSystemModel().GetConnectionList();
list<CPoint>::iterator it_pt;
// -- IF A RUBBER BAND HAS BEEN CREATED
if(m_iRubberBandCreated == 1)
{
// Create a temp tracker since m_RectTracker will have its coords
updated when the rubber band is moved.
temp_tracker = m_RectTracker;
// -- ITERATE THROUGH BLOCKS
it_blk = blk_list.begin();
while(it_blk != blk_list.end() )
{
// Get block properties
blk_posn = (*it_blk)->GetBlockPosition();
blk_width = (*it_blk)->GetBlockShape().GetBlockWidth();
// Determine if item lies within rubber band
intersected = DetermineCurrentAndIntersectRects
(temp_tracker, blk_posn, (blk_width*0.5) );
if(intersected)
{
// Delete block
delete *it_blk; // delete actual block pointed to by it_blk
it_blk = blk_list.erase(it_blk); // delete element at
offset it_blk in list (that held the block)
}
else // only increment the iterator if there were no
intersection
{
it_blk++;
}
}// end for it_blk
// -- ITERATE THROUGH ALL CONNECTIONS (of this model)
it_con = con_list.begin();
while(it_con != con_list.end() )
{
// -- ITERATE THROUGH ALL BEND POINTS FOR THIS CONNECTION
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
it_pt = bend_pts_list.begin();
while(it_pt != bend_pts_list.end() )
{
// BEND POINT
bend_pt = *it_pt;
// Determine if item lies within rubber band
intersected = DetermineCurrentAndIntersectRects
(temp_tracker, bend_pt, delta);
if(intersected)
{
// Unsnap any tail points connected to this bend
point prior to delete bend point
DisconnectTailPointFromBendPoint(&(*it_pt) );
// pass address of the bend_pt
340 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Delete connection
delete *it_con; // delete actual connection pointed to by
it_con
it_con = con_list.erase(it_con); // delete element at
offset it_con in list (that held the connection)
}
else
{
it_con++;
}
}// end for it_con
// Reset member vars.
m_iRubberBandCreated = 0; // end the rubber band state
SetKeyFlagTrack(0); // reset the key-based track flag since
tracking aborted
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
UpdateAllViews (NULL); // indicate that sys. should redraw.
}// end if m_iRubberBandCreated
}
Context Menu Extension 341
(a) (b)
(c) (d)
(e) (f )
(g) (h)
FIGURE 14.2 Paired before and after figures of selection and deletion operations, respectively: (a/b) dele-
tion of bend points and an attached connection, (c/d) deletion of a block and a connection with a disconnection
of tail points, (e/f) deletion of a block and connection emanating from a bend point, and (g/h) deletion of a
group of objects.
Context Menu Extension 343
void CDiagramEngDoc::OnSetItemProperties()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int item_clicked = 0;
// Set properties for blocks
item_clicked = DoubleLeftClickBlock(m_ptContextMenu);
// Set properties for connections and connection bend points
if(item_clicked == 0)
{
item_clicked = SetConnectionProperties(m_ptContextMenu);
}
// Set properties for the Numerical Solver
if(item_clicked == 0)
{
OnSimNumericalSolver();
}
// DiagramEng (end)
}
TABLE 14.6
Context Menu Items, IDs, Captions, and Prompts
Menu Item ID Caption Prompt
Delete Item IDM_DELETE_ITEM &Delete Item Delete selected item\nDelete
selected item
Delete Grouped ID_EDIT_DELETE_GROUPED_ITEMS Delete &Grouped Delete items enclosed by
Items Items rectangle\nDelete Grouped Items
Insert Bend IDM_INSERT_BEND_POINT &Insert Bend Point Insert bend point\nInsert bend point
Point
Set Properties IDM_SET_ITEM_PROPERTIES &Set Properties Set item properties\nSet item
properties
344 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
and if so, a simple message box appears (no dialog window parameter input has been built for con-
nection objects as yet). Finally if no block or connection head or bend point was clicked, then the
click point is deemed to have taken place on an empty part of the palette, and hence the numerical
solver dialog window is presented via a call to OnSimNumericalSolver(), to allow user-defined
parameter input.
Now add a new member function to the CDiagramEngDoc class, to be used to set the proper-
ties of Connection objects and their bend points, with the prototype, int CDiagramEngDoc::
SetConnectionProperties(CPoint m_ptContextMenu), and edit the function as follows.
The SetConnectionProperties() function iterates through the system model’s list of con-
nections, first checking whether the connection head point was clicked: if so, a simple message is
presented, the “click_flag” is set to 1 and the function returns. If not, the connection’s bend points
list is retrieved and a test is made to determine whether any of its bend points have been clicked;
the action then proceeds as for a head point but with a different message. If neither a head point nor
bend point was clicked, the function simply returns “click_flag = 0”. At this stage in the project, no
properties are actually set for the connection, but the structure may be used later when pursuing
connection-centric signal propagation.
14.5 SUMMARY
The Context menu is extended with entries that invoke context-based functionality relevant to the
point at which the menu is invoked. A Numerical Solver dialog window is added to the project,
using six key steps, and works in conjunction with the CSystemModel class allowing the user to
set model solver properties: the dialog may be invoked by the selection of a Simulation menu entry,
a Common Operations toolbar button, or, most easily, through the Context menu. The deletion of
grouped items is also implemented through the addition of a OnEditDeleteGroupedItems()
function and requires the user to circumscribe the items with a CRectTracker object, and then upon
right-clicking in the interior of the region and choosing Delete Grouped Items on the Context menu,
the enclosed items are deleted. The Context menu is also extended to allow the setting of model
diagram item properties using the OnSetItemProperties() function, which invokes the appro-
priate block dialog window or Numerical Solver dialog window.
REFERENCE
1. C++ Reference, http://en.cppreference.com/w/cpp
15 Setting Port Properties
15.1 INTRODUCTION
Block-based data is input by the user by double-clicking on a block or choosing Set Properties from
the Context menu to invoke a block-specific dialog window. The underlying mechanism to invoke the
correct block dialog window involves the CDiagramEngView::OnLButtonDblClk() function
that calls the CDiagramEngDoc::DoubleLeftClickBlock() method, which then invokes the
particular Block class’ overriding BlockDlgWndParameterInput() function that creates
the relevant dialog window and updates the Block class’ variable values with those of its related
Dialog class. For example, the CConstantBlock class’ member variable values are updated with the
CConstantBlockDialog class’ values entered by the user through the Constant block dialog window.
Thus far, all the dialog-window-based entered data is saved to the particular CBlock-derived
class’ member variables. However, although the Divide block (CDivideBlock working with
CDivideBlockDialog) accepts input for the number of multiplication and division inputs, and the
Sum block (CSumBlock working with CSumBlockDialog) allows input for the number of addition
and subtraction inputs, as shown by their dialog windows in Figure 15.1, the number and placement
of input and output ports do not yet reflect the entered settings. Hence functionality is to be added
herein, to set the numbers of ports to be consistent with those entered by the user.
347
348 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
(a) (b)
FIGURE 15.1 Dialog windows showing input port multiplicity settings: (a) Divide block and (b) Sum block.
{
delete_ports = 0;
}
}// end while
}
// Simple msg.
sMsg.Format(“\n CDivideBlock::ResetInputPorts(), vec_input_ports.
size() = %d\n”, vec_input_ports.size() );
//AfxMessageBox(sMsg, nType, nIDhelp);
// Calculate the port position angles to locate the ports on the left
side of the Divide block.
n_inputs = n_mult_ports + n_divide_ports;
CalculatePortPositionAngles(n_inputs);
// Set flags and redraw the doc
pDoc->SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
pDoc->UpdateAllViews(NULL); // indicate that sys. should redraw.
}
In the first section of the ResetInputPorts() function, a check is made to determine whether
any connections are attached to existing ports; if so, they are left connected, if not, then the port is
deleted. This action results in either no ports being present on the block or only ports with connec-
tions attached. Thereafter a count is made to determine the number of remaining multiplication and
division ports, “cnt_mults” and “cnt_divides”, respectively.
The next two sections concern the addition or deletion of multiplication and division ports sepa-
rately. If the current number of multiplication ports, “cnt_mults”, is less than that entered by the
user, “m_iNMultiplyInputs”, or, equivalently, “n_mult_ports”, then CPort objects are constructed
and added to the vector of input ports, “vec_input_ports”. If, however, less ports are desired than
those present, then the connection object that is attached to the port should be disconnected, the port
deleted, and the count, “cnt_mults”, decremented until the correct number of ports remain.
A similar procedure takes place for the division ports: if the number desired is greater than that
present, then more ports are added, if less, then connections are disconnected, ports deleted, and the
count, “cnt_divides”, decremented.
d
y
pi
b
α θi
h
x
pi+1
Divide block
FIGURE 15.2 Divide block, of width, w, and height, h, showing two ports, pi and pi+1, where port pi is located
using a port position angle θi = π − α.
GetParentSystemModel()->UpdateConnectionPointHead(*it_port);
// deref the iterator to get the ptr-to-CPort obj.
i++; // incr the multiplier of the no. of dists
}
}
The developer will notice that in the for loop of the CalculatePortPositionAngles()
function, the index, i, is used to multiply the inter-port distance spacing, d, to correctly set the
vertical displacement, b, which is then used to determine α and then the port position angle, θi,
of port pi.
void CDivideBlock::ResetOutputPorts()
{
int size; // size of vec of output ports
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
vector<CPort*>::iterator it_port; // iterator for vector of CPort ptrs.
// Get the system model
CDiagramEngDoc *pDoc = GetDocumentGlobalFn(); // get a ptr to
the doc.
// Get vec of output ports
vector<CPort*> &vec_output_ports = GetVectorOfOutputPorts();
// vector of input ports
size = vec_output_ports.size();
// Msg. box
//sMsg.Format(“\n CDivideBlock::ResetOutputPorts(): size = %d\n”,
size);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Check size not greater than one, since only one output port per
block
if(size > 1)
{
sMsg.Format(“\n CDivideBlock::ResetOutputPorts(): WARNING!
size = %d != 1\n”, size);
AfxMessageBox(sMsg, nType, nIDhelp);
}
// Reset angle, posn, and arrow direc of output port(s)
for(it_port = vec_output_ports.begin(); it_port != vec_output_ports.
end(); it_port++)
354 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
(*it_port)->SetPortPositionAngle(0.0);
(*it_port)->CalculatePortPosition();
(*it_port)->SetPortArrowDirection(e_right_arrow);
// Update any connection tail end point associated with the port.
GetParentSystemModel()->UpdateConnectionPointTail(*it_port);
// deref the iterator to get the ptr-to-CPort obj.
}
// Set flags and redraw the doc
pDoc->SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
pDoc->UpdateAllViews(NULL); // indicate that sys. should redraw.
}
void CDivideBlock::BlockDlgWndParameterInput()
{
int n_inputs = 0; // number of input ports
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class CDivideBlockDialog : public
CDialog
CDivideBlockDialog oDlg;
// Set the dialog class vars using the block class vars
oDlg.m_iMultType = m_iMultType;
oDlg.m_iNDivideInputs = m_iNDivideInputs;
oDlg.m_iNMultiplyInputs = m_iNMultiplyInputs;
// While less than two input ports get user input
while(n_inputs < 2)
{
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign CDivideBlockDialog variable values to CDivideBlock
variable values.
m_iNDivideInputs = oDlg.m_iNDivideInputs;
m_iNMultiplyInputs = oDlg.m_iNMultiplyInputs;
m_iMultType = oDlg.m_iMultType;
// Print msg with variable value.
//sMsg.Format(“\n CDivideBlock::BlockDlgWndParameterInput(),
m_iMultType = %d\n”, m_iMultType);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
// Check input for correctness and warn user if approp.
n_inputs = m_iNDivideInputs + m_iNMultiplyInputs;
if(n_inputs < 2)
Setting Port Properties 355
{
sMsgTemp.Format(“\n CDivideBlock::BlockDlgWndParameter
Input()\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“ No. of input ports = %dn”, n_inputs);
sMsg += sMsgTemp;
sMsgTemp.Format(“ Two or more input ports are required!\n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
sMsg.Format(“”); // reset the msg since within a while loop
}
}
// Reset ports
ResetInputPorts(m_iNMultiplyInputs, m_iNDivideInputs);
ResetOutputPorts();
}
{
CPort *p_input_port = new CPort(*this);
p_input_port->SetPortName(“+”); // “*” sign assoc. with add
input port
vec_input_ports.push_back(p_input_port); // add port to the
end of the vector
}
}
else if(n_add_ports < cnt_adds)
{
// -- DISCONNECT CONNECTIONS AND DELETE ADD PORTS
it_port = vec_input_ports.begin();
while( (delete_ports == 1) && (it_port != vec_input_ports.end() ) )
{
if( (*it_port)->GetPortName() == “+”)
{
// Disconnect the connection object from the port
pDoc->DisconnectEndPointFromPort(*it_port);
// Delete port
delete *it_port; // delete actual port pointed to by
it_port
it_port = vec_input_ports.erase(it_port); // delete
element at offset it_port in vec (that held the port)
cnt_adds––;
}
else
{
it_port++;
}
if(n_add_ports == cnt_adds)
{
delete_ports = 0;
}
}// end while
}
{
if( (*it_port)->GetPortName() == “-”)
{
// Disconnect the connection object from the port
pDoc->DisconnectEndPointFromPort(*it_port);
// Delete port
delete *it_port; // delete actual port pointed to by
it_port
it_port = vec_input_ports.erase(it_port); // delete
element at offset it_port in vec (that held the port)
cnt_subtracts––;
}
else // only increment the iterator if there were no deletion
{
it_port++;
}
if(n_subtract_ports == cnt_subtracts)
{
delete_ports = 0;
}
}// end while
}
// Simple msg.
sMsg.Format(“\n CSumBlock::ResetInputPorts(), vec_input_ports.
size() = %d\n”, vec_input_ports.size() );
//AfxMessageBox(sMsg, nType, nIDhelp);
// Calculate the port position angles to locate the ports on the left
hemicircle of the Sum block.
n_inputs = n_add_ports + n_subtract_ports;
CalculatePortPositionAngles(n_inputs);
// Set flags and redraw the doc
pDoc->SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
pDoc->UpdateAllViews (NULL); // indicate that sys. should redraw.
}
The ResetInputPorts() function for the Sum block works in a similar manner for the Divide
block presented earlier. In the first section of the function, a check is made to determine whether any
connections are attached to existing ports: if so, they are left connected, if not, the port is deleted.
Thereafter, a count is made to determine the number of remaining addition and subtraction ports.
The next two sections concern the addition or deletion of ports. If the current number of ports is
less than that entered by the user, then CPort objects are constructed and added to the appropriate
port vector. If, however, less ports are desired than that present, then the connection that is attached
to the port should be disconnected, the port deleted, and the port count decremented until the cor-
rect number remains.
p0
pi y
θi
δ x
p2
Sum block
p3
FIGURE 15.3 Sum block with port pi, position angle θi = π/2 + iδ, and inter-port arc spacing δ = π/(n − 1).
Sum block. Consider Figure 15.3 that shows a circular Sum block with a local (x,y) coordinate system at
the center of the block; n ports, pi, for i ∈ [0,n − 1] (here for simplicity n = 4), with equidistant angular
arc spacing, δ = π/(n − 1), from each other; and port (pi) position angle, θi = π/2 + iδ, measured positively
in the anticlockwise direction from the positive x axis.
Now add a public member function to the CSumBlock class with the prototype, void CSumBlock::
CalculatePortPositionAngles(int n_inputs), and edit the function as follows.
{
(*it_port)->SetPortArrowDirection(e_up_arrow);
// input ports point to the right
(*it_port)->SetPortPositionAngle( (i+1)*90);
}
else if(i == 3)
{
(*it_port)->SetPortArrowDirection(e_down_arrow);
// input ports point to the right
(*it_port)->SetPortPositionAngle(90);
}
(*it_port)->CalculatePortPosition();
GetParentSystemModel()->UpdateConnectionPointHead(*it_port);
// deref the iterator to get the ptr-to-CPort obj.
i++;
}// end for
}// end if
// If no. of input ports > 3, then place ports automatically.
if(n_inputs > 3)
{
i = 0;
arc_spacing = 180.0/(n_inputs - 1); // angular arc spacing bw
ports within 180 semicircle.
// Simple msg.
sMsg.Format(“\n CSumBlock::CalculatePortPositionAngles(),
arc_spacing = %lf\n”, arc_spacing);
AfxMessageBox(sMsg, nType, nIDhelp);
for(it_port = vec_input_ports.begin(); it_port != vec_input_ports.
end(); it_port++)
{
// Get port position angle
theta = 90.0 + i*arc_spacing;
(*it_port)->SetPortPositionAngle(theta);
(*it_port)->SetPortArrowDirection(e_right_arrow);
// Manually set the arrow direction if close to the vertical
if(fabs(theta – 90.0) < eps)
{
(*it_port)->SetPortArrowDirection(e_down_arrow);
}
if(fabs(theta – 270.0) < eps)
{
(*it_port)->SetPortArrowDirection(e_up_arrow);
}
// Calculate the new port position and snap any connections
to port
(*it_port)->CalculatePortPosition();
GetParentSystemModel()->UpdateConnectionPointHead(*it_port);
// deref the iterator to get the ptr-to-CPort obj.
// Increment mult. of arc_spacing.
i++;
}// end for
}// end if
}
Setting Port Properties 361
void CSumBlock::ResetOutputPorts()
{
int size; // size of vec of output ports
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
vector<CPort*>::iterator it_port; // iterator for vector
of CPort ptrs.
// Get the system model
CDiagramEngDoc *pDoc = GetDocumentGlobalFn(); // get a ptr to the doc.
// Get vec of output ports
vector<CPort*> &vec_output_ports = GetVectorOfOutputPorts()
// vector of input ports
size = vec_output_ports.size();
// Msg. box
//sMsg.Format(“\n CSumBlock::ResetOutputPorts(): size = %d\n”, size);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Check size not greater than one, since only one output port per block
if(size > 1)
{
sMsg.Format(“\n CSumBlock::ResetOutputPorts(): WARNING!
size = %d != 1n”, size);
AfxMessageBox(sMsg, nType, nIDhelp);
}
// Reset angle, posn, and arrow direc of output port(s)
for(it_port = vec_output_ports.begin(); it_port != vec_output_ports.
end(); it_port++)
{
(*it_port)->SetPortPositionAngle(0.0);
(*it_port)->CalculatePortPosition();
(*it_port)->SetPortArrowDirection(e_right_arrow);
// Update any connection tail end point associated with the port.
GetParentSystemModel()->UpdateConnectionPointTail(*it_port);
// deref the iterator to get the ptr-to-CPort obj.
}
// Set flags and redraw the doc
pDoc->SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
pDoc->UpdateAllViews(NULL); // indicate that sys. should redraw.
}
362 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CSumBlock::BlockDlgWndParameterInput()
{
int n_inputs = 0; // number of input ports
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class CSumBlockDialog : public CDialog
CSumBlockDialog oDlg;
// Set the dialog class var using the block class var
oDlg.m_iNAddInputs = m_iNAddInputs;
oDlg.m_iNSubtractInputs = m_iNSubtractInputs;
// While less than two input ports get user input
while(n_inputs < 2)
{
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign CSumBlockDialog variable values to CSumBlock
variable values.
m_iNAddInputs = oDlg.m_iNAddInputs;
m_iNSubtractInputs = oDlg.m_iNSubtractInputs;
// Print msg with variable value.
//sMsg.Format(“\n CSumBlock::BlockDlgWndParameterInput(),
m_iNAddInputs = %d, m_iNSubractInputs = %d\n”,
m_iNAddInputs, m_iNSubtractInputs);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
// Check input for correctness and warn user if approp.
n_inputs = m_iNAddInputs + m_iNSubtractInputs;
if(n_inputs < 2)
{
sMsgTemp.Format(“\n CSumBlock::BlockDlgWndParameterInput()\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“ No. of input ports = %d\n”, n_inputs);
sMsg += sMsgTemp;
sMsgTemp.Format(“ Two or more input ports are required!\n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
sMsg.Format(“”); // reset the msg since within a while loop
}
}
// Reset ports
ResetInputPorts(m_iNAddInputs, m_iNSubtractInputs);
ResetOutputPorts();
}
Setting Port Properties 363
(a) (b)
(c) (d)
FIGURE 15.4 The Divide and Sum blocks: (a) their default number of ports with connections attached,
(b) an increase in the number of ports preserving the connections, (c) an increase in the number of ports not
preserving the connection order (zero multiplication ports and four division ports for the Divide block and
four input ports for the Sum block), and (d) automatic detachment of all input port connections when switching
completely from one port sign type to another.
Now upon running the program, a user-defined number of ports may be added to the Divide and
Sum blocks, and connections may be attached to their ports, as shown in Figure 15.4. If more ports
are desired, in addition to an existing connection, then they are added; if fewer ports are required,
then ports are deleted and connections detached automatically.
The immediately recognizable problem with the current method of inputting multiplication,
division, addition, and subtraction ports is that the user does not know the sign associated with
the port and has limited control over where the ports should be located. Furthermore, there is an
automatic reordering of the ports on the left face of the Divide block or on the left semicircle of
the Sum block that places them from the top down, ordered in a sequential manner with multi-
plication ports preceding division ports (Divide block), and addition ports preceding subtraction
ports (Sum block). These problems will be addressed later through the use of a Set Properties
entry on the Context menu, which will allow the user to define the sign type and the port posi-
tion angle, θi.
Now add a public member function to the CBlock class taking a pointer-to-CPort argument, i.e.,
the address of the port, and returning an integer, identifying port connectivity: int CBlock::
CheckPortConnection(CPort *port). Edit the function as shown in the following,
checking the reference-to-port, “m_pRefToPort”, for an input port and the reference-from-port,
“m_pRefFromPort”, for an output port.
(a) (b)
FIGURE 15.5 Port icons indicating connectivity of connection objects: (a) icons present, indicating no con-
nections in the connected state, and (b) icons absent, indicating proper connection to ports.
Figure 15.5 indicates block input and output port icons prior to connection and their absence as a
result of proper connection.
{
// Draw port if not connected to a connection object
if(CheckPortConnection(*it_port) == 0)
{
(*it_port)->DrawPort(pDC); // call CPort::DrawPort()
}
// Draw port signs
(*it_port)->DrawPortSign(pDC);
}
}
Both the vector of input and output ports are iterated over, and DrawPortSign() is called upon
the pointer-to-CPort object by dereferencing the “it_port” iterator. Now, add a public member func-
tion to the CPort class with the prototype void CPort::DrawPortSign(CDC *pDC) and edit
the function as follows.
// -- SET SIGN COORDS BASED UPON PORT NAME, I.E. PORT SIGN: ‘*’, ‘/’,
‘+’, ‘−’, or ‘_’
port_name = GetPortName();
if(port_name == “_”) // underscore not minus
Setting Port Properties 367
{
// No sign to be drawn
bounding_rect = 0;
}
else if(port_name == “*”)
{
// Set coords of multiplication sign ‘x’
pt[0].x = sign_posn.x − 0.3*sign_width;
pt[0].y = sign_posn.y − 0.3*sign_width;
pt[1].x = sign_posn.x + 0.3*sign_width;
pt[1].y = sign_posn.y + 0.3*sign_width;
pt[2].x = pt[0].x;
pt[2].y = pt[1].y;
pt[3].x = pt[1].x;
pt[3].y = pt[0].y;
The DrawPortSign() function draws the correct sign type, “×”, “/”, “+”, or “−”, based on the
CString member variable, “m_strPortName”, denoting the name, or more specifically the identifier,
of the input ports, “×”, “/”, “+”, or “−”, and output port, “_”. Initially the block and sign parameters
are set, then a call to CalculatePortSignPosition() is made to determine the correct posi-
tion of the port sign. This function is very similar to CalculatePortPosition(), introduced
in Chapter 4, but uses a scaling factor to place the sign associated with the port on the interior
of the block toward the block center. The following changes should be made to implement the
CalculatePortSignPosition() function:
1. Add a private CPoint member variable to the CPort class named “m_ptPortSignPosition”.
2. Add a public member method to the CPort class with the prototype CPoint CPort::
CalculatePortSignPosition(double scale).
3. Add a public member function to the CPort class to set the port sign position, with proto-
type void CPort::SetPortSignPosition(CPoint sign_posn).
4. Add a public constant member function to the CPort class to get the port sign position, with
prototype, CPoint CPort::GetPortSignPosition(void) const.
{
count = int(m_dPortPositionAngle/360.00);
m_dPortPositionAngle = m_dPortPositionAngle - count*360.00;
}
if(m_dPortPositionAngle < 0.00)
{
count = int(fabs(m_dPortPositionAngle/360.00) );
m_dPortPositionAngle = m_dPortPositionAngle + (count + 1)*360.00;
}
//sMsg.Format(“\n CPort::CalculatePortSignPosition(),
m_dPortPositionAngle = %lf, theta = %3.15lf, 2*pi+eps = %3.15lf\n”,
m_dPortPositionAngle, theta, 2*pi+eps);
//AfxMessageBox(sMsg, nType, nIDhelp);
sign_posn.x = m_ptPortPosition.x;
sign_posn.y = blk_posn.y + height*0.5;
}
if( (theta >= (2*pi − theta_crit) ) && (theta <= (2*pi + eps) ) )
{
r = width/(2*cos(theta) );
sign_posn.x = blk_posn.x + width*0.5;
//sign_posn.y = blk_posn.y − r*sin(theta);
sign_posn.y = m_ptPortPosition.y;
}
break;
case e_triangle:
// Get lengths of subtriangle sides: hypotenuse h, opp. side
length d., and base side length width/2.
d = (width/2)*tan(pi/6);
h = width/sqrt(3);
if(e_blk_direc == e_right) // triangle pointing to the
right.
{
if( (theta >= 0) && (theta < 2*pi/3) )
{
c = h/(2*sin(5*pi/6 − theta) ); // length of ray from CM
to port sign posn near bndy of triangle.
sign_posn.x = blk_posn.x + c*cos(theta);
sign_posn.y = blk_posn.y - c*sin(theta);
}
if( (theta >= 2*pi/3) && (theta <= 4*pi/3) )
{
sign_posn.x = blk_posn.x − d;
//sign_posn.y = blk_posn.y − d*tan(pi − theta);
sign_posn.y = m_ptPortPosition.y;
}
if( (theta > 4*pi/3) && (theta <= (2*pi + eps) ) )
{
c = h/(2*sin(theta − 7*pi/6) );
sign_posn.x = blk_posn.x + c*cos(theta);
sign_posn.y = blk_posn.y − c*sin(theta);
}
}
else if(e_blk_direc == e_left) // triangle pointing to the left.
{
if( (theta >= 0) && (theta <= pi/3) )
{
sign_posn.x = blk_posn.x + d;
//sign_posn.y = blk_posn.y − d*tan(theta);
sign_posn.y = m_ptPortPosition.y;
}
if( (theta > pi/3) && (theta <= pi) )
{
c = h/(2*sin(theta - pi/6) ); // length of ray from CM to
port sign posn near bndy of triangle.
sign_posn.x = blk_posn.x + c*cos(theta);
sign_posn.y = blk_posn.y − c*sin(theta);
}
372 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
break;
default:
// no code for now
break;
}// end switch
return sign_posn;
}
(a) (b)
(c) (d)
FIGURE 15.6 The port and port sign graphics: (a) default appearance, (b) connections attached resulting in
the disappearance of the port icon, (c) more ports added, maintaining connectivity but automatically reorga-
nizing the port layout, and (d) input ports reduced in number, resulting in automatic detachment of relevant
connection objects and reorganization of the port layout.
void CDiagramEngDoc::OnSetItemProperties()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int item_clicked = 0;
// Set properties for blocks
item_clicked = DoubleLeftClickBlock(m_ptContextMenu);
// Set properties for block ports
if(item_clicked == 0)
{
item_clicked = SetBlockPortProperties(m_ptContextMenu);
}
374 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Now add a public member function to the CDiagramEngDoc class with the prototype
int CDiagramEngDoc::SetBlockPortProperties(CPoint m_ptContextMenu)
and edit this function as shown in the following program. This is used to iterate through
all the blocks of the system model to determine whether a block was clicked, and if so, a
SetPortProperties() function should be called to alter the appropriate port’s properties.
The developer will note that different conditions exist to determine whether the mouse pointer, at
the point of invoking the context menu, is upon the block: (1) for an ellipse block, the Euclidean
distance between the point and the center of the block is used (effectively a disc of radius r); (2)
for a rectangular block, the distances in the x and y directions are used; and (3) for the triangle
block, the Euclidean distance is compared with a disc of radius h, representing the hypotenuse of
a subtriangle within the equilateral triangle modeling the block (see Chapter 4 for further details).
have different port properties than those of other blocks. Hence, add a public virtual member function
to the CBlock class with the prototype virtual int CBlock::SetPortProperties(CPoint
point) and edit the base class version of this function as shown follows.
port_flag = 1;
break;
}
}
Setting Port Properties 377
The SetPortProperties() function iterates over the vector of input and output ports and
checks whether the point at which the Context menu was invoked lies sufficiently close to a block
port; if so, the to-be-added Port Properties dialog window is displayed, to allow the user to set spe-
cific port properties, e.g., the port position angle, the port arrow direction, and, if appropriate, the
sign of the port.
block SetPortProperties() functions. The following general steps should be made in order
to implement the desired dialog-based port property setting functionality; specific details are pro-
vided in the following sections.
TABLE 15.1
Port Properties Dialog Window Controls
Object Property Setting
Static Text ID ID_PORT_PROPS_DLG_TXT_ANGLE
Caption Position Angle:
Edit Box ID ID_PORT_PROPS_DLG_ANGLE
Group Box ID ID_PORT_PROPS_DLG_GB_DIREC
Caption Arrow Direction
Radio Button (0) ID ID_PORT_PROPS_DLG_RB_LEFT
Group Checked
Caption Left
Radio Button (1) ID ID_PORT_PROPS_DLG_RB_UP
Group Unchecked
Caption Up
Radio Button (2) ID ID_PORT_PROPS_DLG_RB_RIGHT
Group Unchecked
Caption Right
Radio Button (3) ID ID_PORT_PROPS_DLG_RB_DOWN
Group Unchecked
Caption Down
Group Box ID ID_PORT_PROPS_DLG_GB_SIGN
Caption Sign
Group Box ID ID_PORT_PROPS_DLG_GB_MULT
Caption Multiplication
Radio Button (0) ID ID_PORT_PROPS_DLG_RB_MULT
Group Checked
Caption Multiply
Radio Button (1) ID ID_PORT_PROPS_DLG_RB_DIVIDE
Group Unchecked
Caption Divide
Group Box ID ID_PORT_PROPS _DLG_GB_ADD
Caption Addition
Radio Button (0) ID ID_PORT_PROPS_DLG_RB_ADD
Group Checked
Caption Add
Radio Button (1) ID ID_PORT_PROPS_DLG_RB_SUBTRACT
Group Unchecked
Caption Subtract
Button ID ID_PORT_PROPS_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
for the CDivideBlock and CSumBlock classes. Hence, some additions and alterations need to be
made to the CPortPropertiesDialog class:
1. A member variable should be added to the CPortPropertiesDialog class and used to switch
on/off various dialog window controls.
2. The CPortPropertiesDialog constructor should be altered to allow object construction
using an additional input argument.
3. An OnInitDialog() function should be added to the class to switch on/off the neces-
sary controls.
380 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 15.7 Port Properties dialog window showing the controls as specified in Table 15.1: (a) all controls
enabled, (b) Addition controls disabled, and (c) Multiplication controls disabled.
TABLE 15.2
Dialog Window Controls, Variable Names, Categories, and Types
for the IDD_PORT_PROPERTIES_DLG Resource
Control Variable Name Category Type
ID_PORT_PROPS_DLG_ANGLE m_dPortPositionAngle Value double
ID_PORT_PROPS_DLG_RB_LEFT m_iPortArrowDirection Value int
ID_PORT_PROPS_DLG_RB_MULT m_iPortSignMultiplication Value int
ID_PORT_PROPS_DLG_RB_ADD m_iPortSignAddition Value int
TABLE 15.3
Corresponding Member Variables of the CPortPropertiesDialog
and CPort Classes
Type CPortPropertiesDialog Member Variables CPort Member variables
double m_dPortPositionAngle m_dPortPositionAngle
int m_iPortArrowDirection m_ePortArrowDirec
int m_iPortSignMultiplication m_iPortSignMultiplication
(unnecessary)
int m_iPortSignAddition m_iPortSignAddition (unnecessary)
Add a new private integer member variable to the CPortPropertiesDialog class with the name
“m_iSignFlag”, since this flag will be used to toggle the sign-based controls on the corresponding
dialog window object (IDD_PORT_PROPERTIES_DLG).
Modify the existing CPortPropertiesDialog() constructor from the original to the one
that follows, introducing an integer input argument in the function header and performing member
variable initialization using this integer variable as shown.
Setting Port Properties 381
Also alter the “PortPropertiesDialog.h” header file and change the original constructor function
prototype to the new version with the integer flag-like variable “sign_flag” as follows.
BOOL CPortPropertiesDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
// DiagramEng (start)
if(m_iSignFlag == 0)
{
// Disable all sign-based controls
GetDlgItem(ID_PORT_PROPS_DLG_GB_SIGN)->EnableWindow(FALSE);
// sign group box
GetDlgItem(ID_PORT_PROPS_DLG_GB_MULT)->EnableWindow(FALSE);
// mult/divide sign group box
GetDlgItem(ID_PORT_PROPS_DLG_RB_MULT)->EnableWindow(FALSE);
// mult sign radio button
382 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
GetDlgItem(ID_PORT_PROPS_DLG_RB_DIVIDE)->EnableWindow(FALSE);
// divide sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_GB_ADD)->EnableWindow(FALSE);
// add/subtract sign group box
GetDlgItem(ID_PORT_PROPS_DLG_RB_ADD)->EnableWindow(FALSE);
// add sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_RB_SUBTRACT)->EnableWindow(FALSE);
// subtract sign radio button
}
else if(m_iSignFlag == 1)
{
// Enable all Multiplication sign-based controls
GetDlgItem(ID_PORT_PROPS_DLG_GB_SIGN)->EnableWindow(TRUE);
// sign group box
GetDlgItem(ID_PORT_PROPS_DLG_GB_MULT)->EnableWindow(TRUE);
// mult/divide sign group box
GetDlgItem(ID_PORT_PROPS_DLG_RB_MULT)->EnableWindow(TRUE);
// mult sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_RB_DIVIDE)->EnableWindow(TRUE);
// divide sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_GB_ADD)->EnableWindow(FALSE);
// add/subtract sign group box
GetDlgItem(ID_PORT_PROPS_DLG_RB_ADD)->EnableWindow(FALSE);
// add sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_RB_SUBTRACT)->EnableWindow(FALSE);
// subtract sign radio button
}
else if(m_iSignFlag == 2)
{
// Enable all Addition sign-based controls
GetDlgItem(ID_PORT_PROPS_DLG_GB_SIGN)->EnableWindow(TRUE);
// sign group box
GetDlgItem(ID_PORT_PROPS_DLG_GB_MULT)->EnableWindow(FALSE);
// mult/divide sign group box
GetDlgItem(ID_PORT_PROPS_DLG_RB_MULT)->EnableWindow(FALSE);
// mult sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_RB_DIVIDE)->EnableWindow(FALSE);
// divide sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_GB_ADD)->EnableWindow(TRUE);
// add/subtract sign group box
GetDlgItem(ID_PORT_PROPS_DLG_RB_ADD)->EnableWindow(TRUE);
// add sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_RB_SUBTRACT)->EnableWindow(TRUE);
// subtract sign radio button
}
else if(m_iSignFlag == 3)
{
// Enable all sign-based controls
GetDlgItem(ID_PORT_PROPS_DLG_GB_SIGN)->EnableWindow(TRUE);
// sign group box
GetDlgItem(ID_PORT_PROPS_DLG_GB_MULT)->EnableWindow(TRUE);
// mult/divide sign group box
GetDlgItem(ID_PORT_PROPS_DLG_RB_MULT)->EnableWindow(TRUE);
// mult sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_RB_DIVIDE)->EnableWindow(TRUE);
// divide sign radio button
Setting Port Properties 383
GetDlgItem(ID_PORT_PROPS_DLG_GB_ADD)->EnableWindow(TRUE);
// add/subtract sign group box
GetDlgItem(ID_PORT_PROPS_DLG_RB_ADD)->EnableWindow(TRUE);
// add sign radio button
GetDlgItem(ID_PORT_PROPS_DLG_RB_SUBTRACT)->EnableWindow(TRUE);
// subtract sign radio button
}
// DiagramEng (end)
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
Upon compiling the program, the error message “error C2065: ‘CPortPropertiesDialog’ : unde-
clared identifier” is presented. To resolve this, add #include “PortPropertiesDialog.h” at the top of
the “Block.cpp” source file. This is required since a CPortPropertiesDialog object is created in the
CBlock::SetPortProperties() function.
TABLE 15.4
Objects, IDs, Class, and Event-Handler Functions for the CPortPropertiesDialog Class
Object ID Class COMMAND Event-Handler
OK button ID_PORT_PROPS_DLG_BTN_OK CPortPropertiesDialog OnPortPropertiesDlgBtnOk()
Cancel button IDCANCEL (default) CPortPropertiesDialog OnCancel()
384 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
controls of the CPortPropertiesDialog, “oDlg”, dialog window object. However, the Divide and Sum
blocks must have Multiplication and Addition signs enabled, respectively. Hence, add overriding func-
tions as follows and edit them as shown in the following program:
1. Add a public member function to the CDivideBlock class with the prototype: int CDivide
Block::SetPortProperties(CPoint point).
2. Add a public member function to the CSumBlock class with the prototype: int CSum
Block::SetPortProperties(CPoint point).
if(oDlg.m_iPortSignMultiplication == 0)
{
(*it_port)->SetPortName(“*”);
}
else
{
(*it_port)->SetPortName(“/”);
}
UpdateSignCount();
// DO NOT SET PORT SIGN FOR OUTPUT PORT SINCE THEY HAVE
NO SIGN ASSOCIATION
oDlg.m_iPortArrowDirection =
(*it_port)−>GetPortArrowDirection();
port_name = (*it_port)->GetPortName();
if(port_name == “+”)
{
oDlg.m_iPortSignAddition = 0;
}
else
{
oDlg.m_iPortSignAddition = 1;
}
if(oDlg.m_iPortSignAddition == 0)
{
(*it_port)->SetPortName(“+”);
}
else
{
(*it_port)->SetPortName(“−”);
}
UpdateSignCount();
// DO NOT SET PORT SIGN FOR OUTPUT PORT SINCE THEY HAVE NO
SIGN ASSOCIATION
// Update any connection tail end point associated with the
port.
GetParentSystemModel()->UpdateConnectionPointTail(*it_port);
// deref the iterator to get the ptr-to-CPort obj.
port_flag = 1;
break;
}
}
return port_flag;
}
The developer will notice the call to UpdateSignCount() in both versions of the
SetPortProperties() functions. This simply updates the sign count of the class con-
cerned: “m_iNMultiplyInputs” and “m_iNDivideInputs” for the CDivideBlock class, and
“m_iNAdds” and “m_iNSubtracts” for the CSumBlock class. Hence, add a public member
function to both the CDivideBlock and CSumBlock classes with the following prototype void
UpdateSignCount(void) and edit the functions as shown. These functions simply iterate
through the vector of input ports and make a count of the sign type stored as the port name.
void CDivideBlock::UpdateSignCount()
{
CString port_name; // port name, i.e. port sign
vector<CPort*>::iterator it_port; // vector iterator.
{
// Get port name, i.e. the port sign.
port_name = (*it_port)->GetPortName();
void CSumBlock::UpdateSignCount()
{
CString port_name; // port name, i.e. port sign
vector<CPort*>::iterator it_port; // vector iterator.
// Get vector of input and output ports
vector<CPort*> &vec_of_input_ports = GetVectorOfInputPorts();
vector<CPort*> &vec_of_output_ports = GetVectorOfOutputPorts();
// Reset member variables
m_iNAddInputs = 0;
m_iNSubtractInputs = 0;
Now upon running the program, the user may alter the port properties, e.g., the location, arrow
direction and sign type, and then upon using the block dialog window to set block-based proper-
ties, the correct number of ports of a particular sign type is correctly reflected in the block dialog
window, as shown in Figure 15.8.
390 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
(a)
(b)
(c)
(e) (d)
FIGURE 15.8 Setting of port properties: (a) Divide block in the default state; (b) changing of port posi-
tion, direction, and sign using the port properties dialog window; (c) Divide block displaying updated port;
(d) Divide block parameter input dialog window showing updated input count; and (e) automatic realignment
of ports after using the block parameter input dialog window (here to increase the port count).
void CDiagramEngDoc::OnDeleteItem()
{
// TODO: Add your command handler code here
// DiagramE3ng (start)
int item_deleted = 0;
// Delete a block
item_deleted = DeleteBlock();
if(item_deleted == 1)
{
return;
}
// Delete a block port
item_deleted = DeletePort();
if(item_deleted == 1)
{
return;
}
// Delete a connection bend point
item_deleted = DeleteConnectionBendPoint();
if(item_deleted == 1)
{
return;
}
// Delete a connection line
item_deleted = DeleteConnection();
if(item_deleted == 1)
{
return;
}
// DiagramEng (end)
}
Add a new public member function to the CDiagramEngDoc class with the prototype, int
DeletePort(void), and edit the code as follows:
int CDiagramEngDoc::DeletePort()
{
int port_flag = 0; // flag indicating whether port
properties were changed
double blk_width; // block width
double sign_width; // port sign width
CPoint point = m_ptContextMenu; // point at which context menu
was activated
CPoint sign_posn; // sign position
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CBlock*>::iterator it_blk; // block iterator
list<CConnection*>::iterator it_con; // connection iterator
vector<CPort*>::iterator it_port; // port iterator
// Get connection list
list<CConnection*> &con_list = GetSystemModel().GetConnectionList();
392 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Delete port
delete *it_port; // delete actual port pointed to by
it_port
it_port = vec_output_ports.erase(it_port); // delete
element at offset it_port in vec (that held the port)
// Delete only one port per block then exit while loop.
port_flag = 1;
break;
}
else
{
it_port++;
}
}// end it_port
*/
// Check port_flag to exit it_blk loop
if(port_flag == 1)
{
break;
}
}// end it_blk
return port_flag;
}
394 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The DeletePort() function iterates through the block list and checks whether the point at which
the Context menu is invoked is sufficiently close to an input port; if so, any connection that may be
attached to the port is detached and the port deleted from the vector. The developer will notice that
the section concerning the deletion of output ports is commented out (for now), since output ports
should not be deleted from blocks that possess them. This can be changed, in future, by simply
removing the comments as desired, if in fact required.
In addition, if a port is deleted from a Divide or Sum block, then the count of the multiplica-
tion and division, or addition and subtraction signs, must be updated prior to subsequent setting
of block properties, via the Set Properties entry on the Context menu, in particular, program-
matically through the BlockDlgWndParameterInput() functions. Hence, add the function
call to UpdateSignCount() as shown in bold to the BlockDlgWndParameterInput()
functions of the CDivideBlock and CSumBlock classes as shown in the following. This will
result in the correct number of the relevant sign type being displayed in the block properties
dialog window.
void CDivideBlock::BlockDlgWndParameterInput()
{
int n_inputs = 0; // number of input ports
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Update the sign count since a port may have been deleted
UpdateSignCount();
// Set the dialog class vars using the block class vars
oDlg.m_iMultType = m_iMultType;
oDlg.m_iNDivideInputs = m_iNDivideInputs;
oDlg.m_iNMultiplyInputs = m_iNMultiplyInputs;
if(n_inputs < 2)
{
sMsgTemp.Format(“\n CDivideBlock::BlockDlgWndParameter
Input()\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“ No. of input ports = %d\n”, n_inputs);
sMsg += sMsgTemp;
sMsgTemp.Format(“ Two or more input ports are required!\n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
sMsg.Format(“”); // reset the msg since within a
while loop
}
}
// Reset ports
ResetInputPorts(m_iNMultiplyInputs, m_iNDivideInputs);
ResetOutputPorts();
}
void CSumBlock::BlockDlgWndParameterInput()
{
int n_inputs = 0; // number of input ports
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class CSumBlockDialog : public CDialog
CSumBlockDialog oDlg;
// Update the sign count since a port may have been deleted
UpdateSignCount();
// Set the dialog class var using the block class var
oDlg.m_iNAddInputs = m_iNAddInputs;
oDlg.m_iNSubtractInputs = m_iNSubtractInputs;
// While less than two input ports get user input
while(n_inputs < 2)
{
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign CSumBlockDialog variable values to CSumBlock
variable values.
m_iNAddInputs = oDlg.m_iNAddInputs;
m_iNSubtractInputs = oDlg.m_iNSubtractInputs;
// Print msg with variable value.
//sMsg.Format(“\n CSumBlock::BlockDlgWndParameterInput(),
m_iNAddInputs = %d, m_iNSubractInputs = %d\n”,
m_iNAddInputs, m_iNSubtractInputs);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
if(n_inputs < 2)
{
sMsgTemp.Format(“\n CSumBlock::BlockDlgWndParameter
Input()\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“ No. of input ports = %d\n”, n_inputs);
sMsg += sMsgTemp;
sMsgTemp.Format(“ Two or more input ports are required!\n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
sMsg.Format(“”); // reset the msg since within a while loop
}
}
// Reset ports
ResetInputPorts(m_iNAddInputs, m_iNSubtractInputs);
ResetOutputPorts();
}
Now upon running the program, the user may attach a connection to a port and then delete the port
resulting in a detachment of the connection, as shown in Figure 15.9.
(a) (b)
(c) (d)
FIGURE 15.9 Port deletion: (a) Divide block with connections attached, (b) deletion of ports resulting
in detachment of connections, (c) Divide block parameter input dialog showing updated input count, and
(d) automatic realignment of ports after using the block parameter input dialog window.
Setting Port Properties 397
15.9 SUMMARY
The setting of port properties involves the specification of the number of block ports and also
their position, direction, and sign type, if appropriate. Functionality was added for the Divide
and Sum blocks to allow the user to change the number of input ports of a particular sign type
(“×”, “/”, “+”, “−”) and involved the functions ResetInputPorts(), ResetOutputPorts(),
and CalculatePortPositionAngles(), where the resetting methods are called from the
BlockDlgWndParameterInput() function. The drawing of block ports, depending on
their connection status, was implemented using the CBlock methods: DrawBlockPorts()
and CheckPortConnection(). The drawing of port signs, to be placed next to the ports for
the Divide and Sum blocks, was performed using the CPort methods: DrawPortSign() and
CalculatePortSignPosition().
The setting of specific port properties involved adding a SetBlockPortProperties()
function to the CBlock class that calls the virtual CBlock::SetPortProperties() function
to be overridden by the Divide and Sum block classes. A Port Properties dialog resource was added
to the project and an instance of which created in the SetPortProperties() function to accept
the user-specified port properties. In addition, the CPortPropertiesDialog() constructor was
amended to include a flag-like variable used to initialize the Port Properties dialog controls. Finally,
the deletion of ports was implemented by extension of the OnDeleteItem() function and the
addition of the DeletePort() method, both of the CDiagramEngDoc class.
16 Key-Based Item Movement
16.1 INTRODUCTION
The drawing functionality of the DiagramEng application allows blocks to be connected by
connection objects whose head and tail points are attached to block input and output ports, respec-
tively. However, at present, fine-scale adjustment of the blocks and bend points to align the diagram
as desired can only be performed using the mouse, which is cumbersome: no keyboard character
input can assist the user. Hence, a new Context menu entry named Fine Movement is required that
should allow the arrow keys, left, “ ”, right, “ ”, up, “ ”, and down, “ ”, to control incremental
movement of a diagram entity, i.e., a block or connection bend point, one pixel at a time. The topics
concerning the implementation of this functionality are as listed and specific details are presented
in the sections that follow.
void CDiagramEngDoc::OnFineMoveItem()
{
// TODO: Add your command handler code here
// DiagramEng (start)
// Set the member var. flag concerning fine movement
m_iFineMoveFlag = 1;
// DiagramEng (end)
}
399
400 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 16.1
Context Menu Settings: IDs, Captions, and Prompts (Status Bar and Tooltips),
and the New IDM_FINE_MOVE_ITEM Entry
ID Caption Prompts
IDM_DELETE_ITEM &Delete Item Delete selected item\nDelete selected item
ID_EDIT_DELETE_GROUPED_ITEMS Delete &Grouped Delete items enclosed by rectangle\nDelete grouped
Items items
IDM_FINE_MOVE_ITEM &Fine Move Item Fine movement of item\nFine movement of item
IDM_INSERT_BEND_POINT &Insert Bend Point Insert bend point\nInsert bend point
IDM_SET_ITEM_PROPERTIES &Set Properties Set item properties\nSet item properties
Now accessor methods are required to set and retrieve the value of the member variable,
“m_iFineMoveFlag”. Hence, add two public member functions to the CDiagramEngDoc class with
the following declarations and definitions.
// DiagramEng (start)
int key_flag = 0; // local key flag
char lchar; // local character
lchar = char(nChar); // typecast the nChar
// Get a ptr to the doc
CDiagramEngDoc *pDoc = GetDocument();
// -- CHECK THAT THE “T” CHARACTER HAS BEEN PRESSED
if(lchar == ‘T’)
{
key_flag = 1;
pDoc->SetKeyFlagTrack(key_flag); // set the m_iKeyFlagTrack
in the CDiagramEngDoc class
}
// -- CHECK THAT THE FINE MOVE FLAG HAS BEEN SET INDICATING FINE
MOVEMENT OF AN ITEM
if(pDoc->GetFineMoveFlag() > 0)
{
// -- CHECK KEY PRESSED
if(lchar == 37) // LEFT ARROW
{
key_flag = 37;
pDoc->FineMoveItem(key_flag); // move item left
}
else if(lchar == 38) // UP ARROW
{
key_flag = 38;
pDoc->FineMoveItem(key_flag); // move item up
}
else if(lchar == 39) // RIGHT ARROW
{
key_flag = 39;
pDoc->FineMoveItem(key_flag); // move item right
}
else if(lchar == 40) // DOWN ARROW
{
key_flag = 40;
pDoc->FineMoveItem(key_flag); // move item down
}
else // NOT AN ARROW KEY
{
key_flag = 0;
402 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The OnKeyDown() function first checks that the member variable, “m_iFineMoveFlag”, is greater
than “0”: if it is “0”, then no fine movement action can occur, if greater than “0”, then it can occur.
Then a check is made to determine which key was pressed, and if an arrow key was pressed,
then the FineMoveItem() function is called passing in the (integer) direction identifier in which
motion should occur.
The FineMoveItem() function simply calls two item movement functions concerning blocks and
connection bend points, passing in the direction in which they should be moved. However, if upon
activation of the Context menu, the mouse pointer was not on a block or bend point, then these func-
tions perform no item movement and return “0”, and hence the fine movement action is ended in the
final conditional section by setting the “m_iFineMoveFlag” variable to “0”.
Hence, add a public member function to the CDiagramEngDoc class with the prototype,
int CDiagramEngDoc::FineMoveBlock(int key_flag), and edit the function as follows.
move_flag = 1;
}
else if(key_flag == 38) // UP ARROW
{
// Update block posn.
blk_posn.y = blk_posn.y − delta_y;
(*it_blk)->SetBlockPosition(blk_posn);
ptr_to_blk = *it_blk; // record the address of the
block for subsequent movt.
move_flag = 1;
}
else if(key_flag == 39) // RIGHT ARROW
{
// Update block posn.
blk_posn.x = blk_posn.x + delta_x;
(*it_blk)->SetBlockPosition(blk_posn);
ptr_to_blk = *it_blk; // record the address of the
block for subsequent movt.
move_flag = 1;
}
else if(key_flag == 40) // DOWN ARROW
{
// Update block posn.
blk_posn.y = blk_posn.y + delta_y;
(*it_blk)->SetBlockPosition(blk_posn);
ptr_to_blk = *it_blk; // record the address of the
block for subsequent movt.
move_flag = 1;
}
if(move_flag == 1)
{
// Set flag
m_iFineMoveFlag = 2;
// Mark the document as changed
UpdateAllViews(NULL); // this fn calls OnDraw to
redraw the system model.
SetModifiedFlag(TRUE); // prompt the user to save
break; // break out of for loop
}
}// end if dist
}// end it_blk
}
else if(m_iFineMoveFlag == 2) // -- IF THIS IS NOT THE FIRST
TIME INTO THE FN, THEN MOVE THE BLOCK USING IT’S ADDRESS
{
// -- FILTER THE KEY/DIRECTION (screen (0,0) top left cnr.,
+ve x to right, +ve y downward
if(key_flag == 37) // LEFT ARROW
{
// Update block posn.
blk_posn = ptr_to_blk->GetBlockPosition();
blk_posn.x = blk_posn.x − delta_x;
ptr_to_blk->SetBlockPosition(blk_posn);
Key-Based Item Movement 405
move_flag = 1;
}
else if(key_flag == 38) // UP ARROW
{
// Update block posn.
blk_posn = ptr_to_blk->GetBlockPosition();
blk_posn.y = blk_posn.y − delta_y;
ptr_to_blk->SetBlockPosition(blk_posn);
move_flag = 1;
}
else if(key_flag == 39) // RIGHT ARROW
{
// Update block posn.
blk_posn = ptr_to_blk->GetBlockPosition();
blk_posn.x = blk_posn.x + delta_x;
ptr_to_blk->SetBlockPosition(blk_posn);
move_flag = 1;
}
else if(key_flag == 40) // DOWN ARROW
{
// Update block posn.
blk_posn = ptr_to_blk->GetBlockPosition();
blk_posn.y = blk_posn.y + delta_y;
ptr_to_blk->SetBlockPosition(blk_posn);
move_flag = 1;
}
if(move_flag == 1)
{
// Mark the document as changed
UpdateAllViews(NULL); // this fn calls OnDraw to
redraw the system model.
SetModifiedFlag(TRUE); // prompt the user to save
}
}
return move_flag;
}
On the first entry to this function, “m_iFineMoveFlag” is equal to “1”, and the block list is iterated
over to determine which block was selected by the user upon activation of the Context menu. If a
block was selected, i.e., the distance between the point of the right-click event and the block is less
than or equal to one quarter of the block width, then movement action is processed according to the
“key_flag” direction-specification variable, and the address of the block is statically stored (“static
CBlock *ptr_to_blk = NULL” in combination with “ptr_to_blk = *it_blk”;) for subsequent entry
into the function. Prior to exiting the function the “m_iFineMoveFlag” variable is set to “2”, indi-
cating that subsequent movement concerns the stored block. Thereafter, key down events result in
the second part of the conditional section, “else if(m_iFineMoveFlag == 2)”, being executed, where
the block to be moved is retrieved through the statically stored pointer-to-CBlock, “ptr_to_blk”,
variable and the new position updated.
If no block was selected, then the flag-like variable, “move_flag” is “0” and is returned to the
FineMoveItem() function, which will then end the fine movement process by setting the member
variable, “m_iFineMoveFlag”, to “0”.
406 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
if(move_flag == 1)
{
// Set flag
m_iFineMoveFlag = 3;
// Mark the document as changed
UpdateAllViews(NULL); // this fn calls OnDraw to
redraw the system model.
SetModifiedFlag(TRUE); // prompt the user to save
break; // break out of for loop
}
}// end if dist
}// end it_pt
}// end it_con
}
else if(m_iFineMoveFlag == 3) // -- IF THIS IS NOT THE
FIRST TIME INTO THE FN, THEN MOVE THE BEND POINT USING IT’S
ADDRESS
{
// -- FILTER THE KEY/DIRECTION (screen (0,0) top left cnr.,
+ve x to right, +ve y downward
if(key_flag == 37) // LEFT ARROW
{
// Update CPoint bend point posn by dereferencing the ptr.
(*ptr_to_bend_pt).x = (*ptr_to_bend_pt).x − delta_x;
sys_model.UpdateConnectionPointTailToBendPoint(ptr_to_bend_pt);
// update any tail pt connected to bend pt.
move_flag = 1;
}
else if(key_flag == 38) // UP ARROW
{
// Update CPoint bend point posn. by dereferencing the ptr.
(*ptr_to_bend_pt).y = (*ptr_to_bend_pt).y − delta_y;
sys_model.UpdateConnectionPointTailToBendPoint(ptr_to_bend_pt);
// update any tail pt connected to bend pt.
move_flag = 1;
}
else if(key_flag == 39) // RIGHT ARROW
{
// Update CPoint bend point posn. by dereferencing the ptr.
(*ptr_to_bend_pt).x = (*ptr_to_bend_pt).x + delta_x;
sys_model.UpdateConnectionPointTailToBendPoint(ptr_to_bend_pt);
// update any tail pt connected to bend pt.
move_flag = 1;
}
else if(key_flag == 40) // DOWN ARROW
{
// Update CPoint bend point posn. by dereferencing the ptr.
(*ptr_to_bend_pt).y = (*ptr_to_bend_pt).y + delta_y;
sys_model.UpdateConnectionPointTailToBendPoint(ptr_to_bend_pt);
// update any tail pt connected to bend pt.
move_flag = 1;
}
if(move_flag == 1)
{
// Mark the document as changed
Key-Based Item Movement 409
The developer will notice that the indicator-like member variable, “m_iFineMoveFlag”, has four
different roles, as summarized in Table 16.2: (0) The fine movement action is terminated, (1) a block
or bend point address is to be recorded, (2) a block is to be moved, or (3) a connection bend point
is to be moved.
Hence, as a result of fine movement control of block and bend points, better diagrams may be
drawn faster and with greater motion control. Figure 16.1 shows a typical control flow diagram rep-
resenting: (a) a linear equation, e.g., f(x) = kx + c, and (b) a differential equation involving feedback,
.
e.g., x(t) = f(t) + kx(t), where the drawn lines are perfectly horizontal or vertical.
410 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 16.2
The Four Roles of the “m_iFineMoveFlag” Member Variable
for Fine-Movement Action
“m_iFineMoveFlag” Action
0 Fine movement action is terminated
1 Fine movement is initiated and a block or bend point address is to be
recorded
2 Fine movement is in progress and recorded block is to be moved
3 Fine movement is in progress and recorded bend point is to be moved
(a)
(b)
FIGURE 16.1 Control diagrams with perfectly horizontal and vertical control lines: (a) a linear equation
.
model (f(x) = kx + c) and (b) a differential equation model (x (t) = f(t) + kx(t)).
Key-Based Item Movement 411
16.9 SUMMARY
The key-based pixel-by-pixel movement of diagram blocks and bend points is performed through
the introduction of a Fine Move Item entry on the Context menu, which upon selection invokes the
OnFineMoveItem() function that sets the value of a multiple-role, flag-like member variable,
“m_iFineMoveFlag”, used to process the fine-movement action. The OnKeyDown() function of
the CDiagramEngView class is used to process keyboard input and calls the FineMoveItem()
function of the CDiagramEngDoc class, which in turn calls FineMoveBlock() and
FineMoveConnectionBendPoint(), to move blocks and bend points, respectively. Finally,
the control flow of the fine movement process is presented in a listed sequence.
REFERENCE
1. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
17 Reversing Block Direction
17.1 INTRODUCTION
Block diagrams can currently be drawn where the block direction, specified by the enumerated
type, “EBlockDirection”, is set to “e_right” by default, implying that the block points to the right.
However, if the user wants to reverse the direction of a block, e.g., a Gain block to be used in a
feedback rather than a feed-forward path, where the block points in the direction of the signal flow,
then a “reverse block” option should be available to provide this change. The general steps concern-
ing the implementation of this functionality are as listed where specific details are provided in the
sections that follow.
void CDiagramEngDoc::OnReverseBlockDirection()
{
// TODO: Add your command handler code here
// DiagramEng (start)
double dist; // Euclidean dist bw. block posn and point posn.
double width; // block width
CPoint blk_posn; // block posn.
CPoint point; // local point var.
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CBlock*>::iterator it_blk; // iterator
// Get the point at which the context menu was invoked
point = m_ptContextMenu; // init a local copy.
413
414 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 17.1
Context Menu Settings: IDs, Captions, and Prompts (Status Bar and Tooltips),
and the New IDM_REVERSE_BLOCK_DIREC Entry
ID Caption Prompts
IDM_DELETE_ITEM &Delete Item Delete selected item\nDelete selected
item
ID_EDIT_DELETE_GROUPED_ITEMS Delete &Grouped Items Delete items enclosed by
rectangle\nDelete grouped items
IDM_FINE_MOVE_ITEM &Fine Move Item Fine movement of item\nFine movement
of item
IDM_INSERT_BEND_POINT &Insert Bend Point Insert bend point\nInsert bend point
IDM_REVERSE_BLOCK_DIREC &Reverse Block Reverse block direction\nReverse
block direction
IDM_SET_ITEM_PROPERTIES &Set Properties Set item properties\nSet item properties
// Print msg.
//sMsg.Format(“\n CDiagramEngDoc::OnReverseBlockDirection(), point
(x,y) = (%d,%d)\n”, point.x, point.y);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Get a copy of the blk_list in the system model
list<CBlock*> &blk_list = GetSystemModel().GetBlockList();
// MUST BE A REFERENCE!
// -- ITERATE THROUGH THE BLOCK LIST TO FIND SELECTED BLOCK
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
blk_posn = (*it_blk)->GetBlockPosition();
width = (*it_blk)->GetBlockShape().GetBlockWidth();
dist = sqrt(pow( (blk_posn.x - point.x),2) +
pow( (blk_posn.y - point.y),2) );
if(dist <= 0.5*width*0.5)
{
(*it_blk)->ReverseBlockDirection(); // reverse block and all
its properties
SetModifiedFlag(TRUE); // set the doc. as
modified to prompt the user to save
UpdateAllViews(NULL); // redraw the doc if
block direction changed
break; // exit the for loop
}
}// end it_blk
// DiagramEng (end)
}
The OnReverseBlockDirection() function iterates through the list of blocks and compares
the position of the block with that of the point “m_ptContextMenu” at which the Context menu was
invoked; if it is sufficiently close, then ReverseBlockDirection() is called to reverse the
direction of the block.
Reversing Block Direction 415
void CBlock::ReverseBlockDirection()
{
double theta; // port position angle in degrees
(not radians)
vector<CPort*>::iterator it_port; // iterator for ptr-to-CPort
EBlockDirection e_blk_direc; // block direc. (of local coord
sys x-axis)
EPortArrowDirection e_port_direc; // port direc, i.e. direc in which
arrow points.
// -- CHANGE THE BLOCK’S DIRECTION
e_blk_direc = GetBlockShape().GetBlockDirection();
if(e_blk_direc == e_left)
{
GetBlockShape().SetBlockDirection(e_right);
}
else if(e_blk_direc == e_right)
{
GetBlockShape().SetBlockDirection(e_left);
}
// -- CHANGE BLOCK PORT DIRECTIONS AND POSITIONS
// Input ports
for(it_port = m_vecInputPorts.begin(); it_port != m_vecInputPorts.
end(); it_port++)
{
// Change port arrow direction
e_port_direc = (*it_port)->GetPortArrowDirection();
if(e_port_direc == e_left_arrow)
{
(*it_port)->SetPortArrowDirection(e_right_arrow);
}
else if(e_port_direc == e_right_arrow)
{
(*it_port)->SetPortArrowDirection(e_left_arrow);
}
// Change port position angle and recalculate position
theta = (*it_port)->GetPortPositionAngle();
theta = 180.0 - theta;
(*it_port)->SetPortPositionAngle(theta);
(*it_port)->CalculatePortPosition();
// Update any connection head end point associated with the
port.
m_pParentSystemModel->UpdateConnectionPointHead(*it_port);
// deref the iterator to get the ptr-to-CPort obj.
}
416 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Output ports
for(it_port = m_vecOutputPorts.begin(); it_port != m_vecOutputPorts.
end(); it_port++)
{
// Change port arrow direction
e_port_direc = (*it_port)->GetPortArrowDirection();
if(e_port_direc == e_left_arrow)
{
(*it_port)->SetPortArrowDirection(e_right_arrow);
}
else if(e_port_direc == e_right_arrow)
{
(*it_port)->SetPortArrowDirection(e_left_arrow);
}
// Update any connection tail end point associated with the port.
m_pParentSystemModel->UpdateConnectionPointTail(*it_port);
// deref the iterator to get the ptr-to-CPort obj.
}
}
(a) (b)
(c) (d)
FIGURE 17.1 Reversing block direction: (a) connections attached in the feed-forward direction, (b) blocks
reversed with connections attached to corresponding ports, (c) detachment of connections showing correct
port arrow directions, and (d) default block-dialog-based port resetting.
418 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
(a)
(b)
FIGURE 17.2 Diagrams representing equations modeling physical systems using block reversal: (a) a
reversed Gain block in a differential equation model and (b) reversed Gain, Integrator, and Derivative blocks
used to model an equation representing a RLC electrical circuit.
Furthermore, more complicated diagrams may be drawn using the reversed block directions,
as shown in Figure 17.2: (a) a differential equation model using a reversed Gain block and (b) a
resistor–inductor–capacitor (RLC) electrical circuit model using reversed Gain, Integrator, and
Derivative blocks.
17.7 SUMMARY
The mechanism to reverse the direction of a block is added to the project. A Reverse Block entry is
added to the Context menu, which upon selection invokes an OnReverseBlockDirection()
event-handler function of the CDiagramEngDoc class that calls ReverseBlockDirection() of
the CBlock class for a selected block to reverse its direction. In addition, accessor methods are added
to the CBlockShape and CPort classes to get and set the block direction and port arrow direction,
respectively. Diagrams can then be drawn with signal paths entering blocks in both the forward and
reverse directions.
Part II
Model Computation and Data
INTRODUCTION
Part II consists of Chapters 18 through 25 and provides detailed instructions to implement diagram-
based modeling and computation functionality. Model validation is explored to determine the
type of diagram drawn, i.e., whether it contains feedback/algebraic loops: if a feedback loop is
not present, then a non-feedback-based connection-centric signal propagation method is pursued,
including block-based data operations to compute the model. However, if one or more feedback
loops do exist, then a general Newton-method-based nonlinear equation solver is used to compute
the system model, where the feedback or algebraic loops denote a set of simultaneous equations. Six
key examples from the domains of mechanical engineering and nonlinear dynamics are explored
and the graphical output presented may be used to confirm the underlying physics of the problems
studied. In addition, serialization is implemented to allow the user to save and restore system model
data and graphical output data for future use.
419
18 Model Validation
18.1 INTRODUCTION
A diagram model is a pictorial representation of a system of equations used to model a real-world
system. The blocks represent source-signal generators, mathematical operations, subsystems, and
computed output and are connected by connections that propagate signals, i.e., data values, from
one block to the next. Mathematical operation blocks operate on input signal values, which then
result in the generation of an output signal to be conveyed along the output connection to another
block. In order to execute a model drawn on the palette, it first must be validated: this involves
checking the model and its connectivity to allow the propagating of signals along connections link-
ing the model blocks together.
1. Validation of model blocks—determines whether essential blocks have been placed on the
palette to allow for proper model computation
2. Validation of block ports—identifies any disconnected ports that should either have con-
nections attached or be deleted
3. Validation of connectivity—determines whether connection objects are connected to input
and output ports or to connection bend points
void CDiagramEngDoc::OnModelBuild()
{
// TODO: Add your command handler code here
// DiagramEng (start)
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Display a msg. box
sMsg.Format(“\n CDiagramEngDoc::OnModelBuild()\n\n”);
//AfxMessageBox(sMsg, nType, nIDhelp);
// -- MODEL VALIDATION
m_SystemModel.ValidateModel();
// DiagramEng (end)
}
421
422 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The ValidateModel() function should be added to the CSystemModel class, so too the func-
tions that this member calls to perform all the necessary model validation. Hence, add the following
functions to the CSystemModel class with the prototypes as indicated and edit the code as shown
in the following sections.
1. void CSystemModel::ValidateModel(void)
2. int CSystemModel::ValidateModelBlocks(void)
3. int CSystemModel::ValidateModelBlockPorts(void)
4. int CSystemModel::ValidateModelConnections(void)
void CSystemModel::ValidateModel()
{
int i;
int error1 = 0; // error in model blocks: (0) no error,
(1) error
int error2 = 0; // error in model ports: (0) no error,
(1) error
int error3 = 0; // error in mode connections: (0) no error,
(1) error
int n_flashes = 3; // no. of times red flashes after black
int n_its; // no. of loop iterations as a fn of n_flashes
BOOL bkgd_erase = TRUE; // specifies whether the background is to be
erased
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string
DWORD nMilliSec = 200; // 32 bit unsigned integer: no. of milliseconds
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg
CDiagramEngDoc *pDoc = GetDocumentGlobalFn(); // declare pDoc to be a
ptr to CDiagramEngDoc.
{
SetModelStatusFlag(1); // results in ModelErrorPenColor()
setting pen_color to red.
}
// Get the position of the first view in the list of views
for the current doc.
POSITION posn = pDoc->GetFirstViewPosition();
// Get a pointer to the view identified by posn.
CView *pView = pDoc->GetNextView(posn);
// Invalidate the entire client area of CWnd using the pView
pointer-to-CView.
pView->Invalidate(bkgd_erase);
// Update the client by sending a WM_PAINT msg. if the update
region is not empty.
pView->UpdateWindow();
// Suspend execution of the current thread for no. of
milliseconds specified.
Sleep(nMilliSec);
}// end for
}
else
{
sMsg.Format(“\n No build errors. \n”);
AfxMessageBox(sMsg, nType, nIDhelp);
}
// -- CLEAR CONNECTIONS IN ERROR
m_lstConnectionError.clear(); // Note: if this is not cleared, then
grouped item deletion will result in a crash.
// -- RESET MODEL STATUS AND REDRAW
SetModelStatusFlag(0);
pDoc->SetModifiedFlag(TRUE); // prompt the user to save
pDoc->UpdateAllViews(NULL); // this fn calls OnDraw to redraw the
system model.
}
The ValidateModel() function simply calls the other validation functions to check that model
blocks, ports, and connections are in order. If the error values returned equal “0,” then there are no
validation errors in the system model, and the execution continues to the SetModelStatusFlag()
call, which sets a model status flag to “0” indicating that no model errors need to be highlighted and
that the validation stage is complete (see the following text for further details concerning the model
status flag). If the model is in error, then this flag is set to “1” in ValidateModelBlockPorts()
and ValidateModelConnections(), indicating that there are connectivity errors that are sub-
sequently drawn in red (see ModelErrorPenColor() below).*
The purpose of the for loop in the ValidateModel() function is to repeatedly switch the
model status flag on and off, resulting in a flashing of the errors from black to red on the screen,
i.e., on the CView object. The pointer-to-CDiagramEngDoc, “pDoc”, is used to get the position
of the first View, and this position is used to get a pointer-to-CView, upon which Invalidate()
and UpdateWindow() may be called, to redraw the system model via automatic invocation of
the DrawSystemModel() function: this allows the View to be updated sufficiently fast such
that the flashing effect may be observed.
* The developer will actually see the color changes when running the DiagramEng application: however, the red color in
the figures is discernable as a lighter shade of grey than the black color.
424 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Now the aforementioned model status-related members need to be added to the CSystemModel class.
Hence, add a private integer member variable, named “m_iModelStatusFlag”, to the CSystemModel
class to record the status of the model and initialize this variable to “0” in the CSystemModel con-
structor as shown in bold in the following.
CSystemModel::CSystemModel(void)
{
// Init.
m_strModelName = “system_model”;
// Time parameters
m_dTimeStart = 0.0;
m_dTimeStop = 1.0;
m_dTimeStepSize = 0.1;
m_strTimeStepType = “Fixed-step”;
// Integration parameters
m_dATOL = 0.0001;
m_dRTOL = 0.0001;
m_strIntegrationMethod = “Runge-Kutta (4th Order)”;
m_strWarning = “OK”;
m_iModelStatusFlag = 0;
}
Now add two accessor methods to retrieve and set the value of the “m_iModelStatusFlag” member
variable with the declarations and definitions shown:
int CSystemModel::ValidateModelBlocks()
{
int error = 0; // error in model
int n_source = 0; // no. of model source blocks
int n_output = 0; // no. of model output blocks
CString blk_name; // blk name
CString sMsg; // string to be displayed
CString sMsgTemp; // temp msg.
Model Validation 425
(a) (b)
FIGURE 18.1 Validation of model blocks: (a) the original model without a source or sink block and (b) the
automatic addition of a Linear Function source block and an Output sink block.
{
if( (*it_port) == (*it_con)->GetRefToPort() )
{
port_connected = 1;
break;
}
}
if(port_connected == 0)
{
error = 1; // flag indicates that model has a block
port error
n_in_dc++; // increment the no. of disconnected
inputs
}
}// end port
{
sMsgTemp.Format(“\n Total no. of disconnected input ports =
%d. “, n_in_dc);
sMsg += sMsgTemp;
}
if(n_out_dc > 0)
{
sMsgTemp.Format(“\n Total no. of disconnected output ports =
%d.”, n_out_dc);
sMsg += sMsgTemp;
}
AfxMessageBox(sMsg, nType, nIDhelp);
}
return error;
}
Finally, if there is a port that is not connected, i.e., if there is an error, then the SetModifiedFlag()
and UpdateAllViews() functions should be called to redraw the model: this is required since the
drawing of disconnected ports is done with a red pen color (RGB(255,0,0)) in the validation stage;
otherwise, the pen color is black (RGB(0,0,0)).
To draw in the correct pen color at the model-validating stage, a new function is required that
sets the pen color to red if the model is in error. Hence, add a public member function to the
CSystemModel class with the prototype, void CSystemModel::ModelErrorPenColor(int
*pen_color), and edit it as shown in the following (note how the address of the “pen_color” vari-
able should be passed into this function).
void CSystemModel::ModelErrorPenColor(int *pen_color)
{
// Note: incoming argument &pen_color passed
// If the model build resulted in an error
if(m_iModelStatusFlag == 1)
{
*pen_color = RGB(255,0,0); // White = RGB(255,255,255),
Black = RGB(0,0,0)
}
}
case e_right_arrow:
pt_array[0].x = m_ptPortPosition.x − 0.5*d;
pt_array[0].y = m_ptPortPosition.y − d;
pt_array[1].x = m_ptPortPosition.x + 0.5*d;
pt_array[1].y = m_ptPortPosition.y;
pt_array[2].x = pt_array[0].x;
pt_array[2].y = m_ptPortPosition.y + d;
// Shift pts as desired
if(shift_right)
{
for(i=0;i<3;i++)
{
pt_array[i].x = pt_array[i].x + 0.5*d;
}
}
break;
case e_down_arrow:
pt_array[0].x = m_ptPortPosition.x + d;
pt_array[0].y = m_ptPortPosition.y − 0.5*d;
pt_array[1].x = m_ptPortPosition.x;
pt_array[1].y = m_ptPortPosition.y + 0.5*d;
pt_array[2].x = m_ptPortPosition.x − d;
pt_array[2].y = pt_array[0].y;
// Shift pts as desired
if(shift_down)
{
for(i=0;i<3;i++)
{
pt_array[i].y = pt_array[i].y − 0.5*d;
}
}
break;
default:
// Use e_right_arrow case
pt_array[0].x = m_ptPortPosition.x − 0.5*d;
pt_array[0].y = m_ptPortPosition.y − d;
pt_array[1].x = m_ptPortPosition.x + 0.5*d;
pt_array[1].y = m_ptPortPosition.y;
pt_array[2].x = m_ptPortPosition.x − 0.5*d;
pt_array[2].y = m_ptPortPosition.y + d;
// Shift pts as desired
if(shift_right)
{
for(i=0;i<3;i++)
{
pt_array[i].x = pt_array[i].x + 0.5*d;
}
}
break;
}// end switch
// -- FILL THE TRIANGULAR PORT POLYGON
// Create a brush
brush_color = RGB(200,200,200); // White = RGB(255,255,255),
Black = RGB(0,0,0)
Model Validation 431
Now upon running the program, if block ports are not connected by connections then these are dis-
played in red. The developer will be able to see this clearly on the screen: the ports of Figure 18.2(b)
are slightly lighter in shade than those of Figure 18.2(a).
(a)
(b)
FIGURE 18.2 Validation of block ports: (a) disconnected blocks in the drawing stage prior to validating the
model, and (b) upon validating the model, ports of disconnected blocks shown in a lighter shade (red, when
running the program).
{
break; // break from it_blk
}
}// end it_blk
// -- CHECK THAT THE CONNECTION TAIL POINT IS CONNECTED TO
ANOTHER CONNECTION’S BEND POINT
tail_pt = (*it_con)->GetConnectionPointTail();
// current connection’s tail point
for(it_con2 = m_lstConnection.begin(); it_con2
!= m_lstConnection.end(); it_con2++)
{
// Other connection’s bend points list
list<CPoint> &bend_pts_list = (*it_con2)-
>GetConnectionBendPointsList();
sMsg += sMsgTemp;
sMsgTemp.Format(“\n Total no. of disconnected connections = %d.”,
n_discon);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
}
return error;
}
The developer will notice that a list of “connections in error”, i.e., “m_lstConnectionError”, is used:
this is a list of pointers-to-CConnection and records those connections that are currently in a dis-
connected state. Hence, add a private member variable to the CSystemModel class to record the
disconnected connections of type, “list<CConnection*>” with name, “m_lstConnectionError”.
Add a public accessor function to the CSystemModel class to get the connection list by reference,
with the prototype: list<CConnection*> &GetConnectionListError(void), and edit
the function as shown in the following.
list<CConnection*> &CSystemModel::GetConnectionListError()
{
// Return the connection list by reference
return m_lstConnectionError;
}
The developer will also note that this error-recording connection list is cleared by the call
“m_lstConnectionError.clear()”, upon each entry to the function and also at the end
of the model validating stage in the ValidateModel() function. This is necessary since, for
each validation of the model, the list should only record disconnected connections. Note also
that “delete *it_con” followed by “m_lstConnectionError.erase(it_con)” is
not performed to clear the list since doing so would delete the actual connection objects of the
system model. In addition, if clear() is not called at the end of the model validating stage,
and if a previously disconnected connection were to be deleted from the model diagram by the
user, then the “m_lstConnectionError” would hold a pointer to a Connection object that would
no longer exist and the program would crash. Hence, the list, “m_lstConnectionError”, must be
promptly cleared at the end of the model validating stage.
Now, the disconnected connections, i.e., the “connections in error” recorded in the list
“m_lstConnectionError”, need to be drawn in red for easy identification by the user. Various
changes need to be made to the program that affect existing functions and are introduced in the
following sections.
Now, this setting up of the pen’s attributes, i.e., the style, width, and color, should be performed in
the DrawSystemModel() function, higher up in the function call tree, rather than in the various
functions that are called by DrawSystemModel(). Then, depending upon what is to be drawn,
e.g., a connected connection as opposed to a disconnected connection, the pen’s attributes may be
changed and the pointer-to-class-device-context, “pDC”, will then reflect those changes. Hence,
the following general changes need to be made, where specific details are provided in the sections
that follow:
// Set length
dDeltaLength = GetDocumentGlobalFn()->GetDeltaLength();
length = 0.2*dDeltaLength;
// Move to the tail pt.
pDC->MoveTo(m_ptTail);
// Iterate through the bend pts. drawing lines bw. them.
for(it_pt = m_lstBendPoints.begin(); it_pt != m_lstBendPoints.end();
it_pt++)
{
// Draw a line to the bend pt.
pDC->LineTo(*it_pt);
// Set the current bend pt. as the pt prior to the head_pt: used
for DrawArrowHead().
final_pt = *it_pt;
}
// Line to head pt.
pDC->LineTo(m_ptHead);
// Iterate through the bend pts. drawing ellipses at each pt.
for(it_pt = m_lstBendPoints.begin(); it_pt != m_lstBendPoints.end();
it_pt++)
{
// Draw an ellipse about the bend pt.
bend_pt = *it_pt;
top_left.x = (int)(bend_pt.x − length*0.5);
top_left.y = (int)(bend_pt.y − length*0.5);
bottom_right.x = (int)(bend_pt.x + length*0.5);
bottom_right.y = (int)(bend_pt.y + length*0.5);
pDC->Ellipse(top_left.x, top_left.y, bottom_right.x,
bottom_right.y);
}
// Draw arrowhead
DrawArrowHead(pDC, final_pt, m_ptHead);
}
Firstly, the current pen is extracted using GetCurrentPen(), then an extended pen object of type
EXTLOGPEN structure is declared and subsequently obtained through the call to GetExtLogPen()
by passing its address [1]. Then the color is extracted from the extended pen’s structural field.
Model Validation 439
As a result, the brush that is created can use the same color as that stored in the pointer-to-CDC,
“pDC”, and, hence, the lines that are drawn and the polygon that is filled are of uniform color. The
modified and more elegant version of the DrawArrowHead() function is shown in the following.
Note that only the previous brush needs to be reset since there is no selection (SelectObject())
of a new local pen in this function: only a retrieval of the current pen (GetCurrentPen()).
{
theta = −theta; // negate theta if y-cmpt of u < 0, since acos
result is an element from [0,pi] radians ONLY (non-neg).
}
// Global position vecs of arrowhead triangle vertices
A.x = head.x + long(cos(theta)*(−(h+d) ) − sin(theta)*(−length*0.5) );
A.y = head.y + long(sin(theta)*(−(h+d) ) + cos(theta)*(−length*0.5) );
B.x = head.x;
B.y = head.y;
C.x = head.x + long(cos(theta)*(−(h+d) ) − sin(theta)*(length*0.5) );
C.y = head.y + long(sin(theta)*(−(h+d) ) + cos(theta)*(length*0.5) );
// -- EXTRACT PEN COLOR FROM PEN
CPen *lpen = pDC->GetCurrentPen();
EXTLOGPEN elPen;
lpen->GetExtLogPen(&elPen);
pen_color = elPen.elpColor;
// Draw arrowhead
pDC->MoveTo(A);
pDC->LineTo(B);
pDC->LineTo(C);
pDC->LineTo(A);
(a)
(b)
FIGURE 18.3 Validation of a model: (a) prior to validating the disconnected model and (b) upon validat-
ing the model showing disconnected block ports and connections in a lighter shade (red, when running the
program), and connected connections in black.
Model Validation 441
Now upon validating the system model, the disconnected block ports and connections are presented
in red (when running the program) or in a lighter shade of gray as shown in Figure 18.3, but the con-
nected connections are still in the black color. The View then flashes the errors in red several times
before ending the validating stage.
CPort* CDiagramEngDoc::SnapConnectionHeadPointToPort
(CPoint &head_pt)
{
// Passing in a point by reference, and returning a prt-to-CPort
“m_pRefToPort”.
double disc_r = 0.1*m_dDeltaLength;
double dist_to_port;
CPoint port_posn;
list<CBlock*>::iterator it_blk;
442 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
int port_connected = 0;
double disc_r = 0.1*m_dDeltaLength;
double dist_to_port;
CPoint port_posn;
CString sMsg; // main msg string
CString sMsgTemp; // temp msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg
list<CBlock*>::iterator it_blk;
list<CBlock*> blk_list = GetSystemModel().GetBlockList();
vector<CPort*> vec_input_ports;
vector<CPort*>::iterator it_port;
// Iterate through block list
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
vec_input_ports = (*it_blk)->GetVectorOfInputPorts();
Model Validation 443
Now upon running the code, the user may attach a connection to a port and attempt to connect
another connection to the same port; as a result, an error message will be displayed indicating that
an alternative port should be chosen (see Figure 18.4).
(a) (b)
(c) (d)
FIGURE 18.4 Preserving uniqueness of port-based connectivity: (a) a connection head point connected to a
port, (b) an attempt to connect another head point to the same port, (c) the resulting error message, and (d) the
selection of an alternative port.
CPoint port_posn;
list<CBlock*>::iterator it_blk;
list<CBlock*> blk_list = GetSystemModel().GetBlockList();
vector<CPort*> vec_output_ports;
vector<CPort*>::iterator it_port;
// Iterate through block list
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
vec_output_ports = (*it_blk)->GetVectorOfOutputPorts();
for(it_port = vec_output_ports.begin(); it_port
!= vec_output_ports.end(); it_port++)
{
port_posn = (*it_port)->GetPortPosition();
dist_to_port = sqrt(pow(tail_pt.x - port_posn.x,2) +
pow(tail_pt.y - port_posn.y,2) );
if(dist_to_port <= disc_r)
{
tail_pt = port_posn;
return (*it_port); // return CPort*, i.e. a from_port
}
}
}
// Return 0 if no “m_pRefFromPort” returned earlier
return 0;
}
Model Validation 445
18.5 SUMMARY
The validating process is required before model computation in order to check that the model is
correctly structured. A ValidateModel() function is used to call three entity-specific func-
tions: (1) ValidateModelBlocks() checks that there exists at least one source and sink
block in the model, (2) ValidateModelBlockPorts() checks the connection status of block
ports and reports those that are disconnected, and (3) ValidateModelConnections()
records any connections that are not fully connected to input and output ports or connec-
tion bend points. Changes are made to the program to draw model entities in the correct color,
depending on the presence of model errors; the setting up of the pen’s attributes is done higher
in the call tree, in CSystemModel::DrawSystemModel(), rather than in the functions
that are called from within this function, i.e., DrawConnection() and DrawArrowHead()
of the CConnection class. Finally, the CDiagramEngDoc automatic connection functions,
SnapConnectionHeadPointToPort() and SnapConnectionTailPointToPort()
were modified with a call to CBlock::CheckPortConnection() to prevent the user from
attaching more than one connection head or tail point to the same port.
REFERENCE
1. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
19 Non-Feedback-Based
Signal Propagation
19.1 INTRODUCTION
A feedback loop exists in a model when a connection emanating from a branch point is directed or
fed back into the original direction of signal flow, reentering a prior block in the ordered sequence
of block operations. An algebraic loop results when the output of a block operation is used as a part
of the very same input to the block; e.g., output y = u1 + u2 involves the inputs u1 and u2, but input
u2 = f(y). That is, the block output is a function of its input, as expected, yet that input is a function,
although usually indirectly, of the same block’s output. The feedback loop and algebraic loop are
essentially equivalent in the context of using model diagrams to represent mathematical equations.
The material in this chapter introduces the mechanism for the detection of algebraic loops and
for performing signal propagation for models not containing feedback loops, called non-feedback-
based signal propagation or simply “direct signal propagation.” However, before implementation
instructions are given, a brief discussion of important concepts in the field of signals and systems
is made here to refresh the reader of fundamentals that should be considered throughout signal
propagation-related code development.
447
448 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
being built in this work perform mathematical operations and may be used to model a continu-
ous system, e.g., a differential equation representing a mass–spring–damper dynamical system.
However, all operations are performed at finite time discretizations. For example, the Derivative
block (to be introduced in Chapter 21) uses a finite difference calculation to determine the numeri-
cal derivative f′(t) of a function f(t) at time t, i.e.,
f (t ) − f (t − h )
f ʹ (t ) ≈ (19.1)
h
where the time-step size h = δt. Similarly, the Integrator block performs a finite difference calcula-
tion to determine the integral of a function at a certain time. For example, in the case of Euler inte-
gration, introduced in Chapter 21 when the integral of the function f(t, y(t)) over the interval [t0, t1] is
t1
∫ f (t, y) dt ≈ hf (t , y )
t0
0 0 (19.2)
A system with memory is one that is dependent on information stored at times other than the cur-
rent time [1]. For example, the numerical derivative (19.1) stores information at the current and
previous time points, and the numerical integral (19.3), introduced earlier, stores information at
the previous time point that is used to determine the value at the current time point. Hence, the
Derivative and Integral blocks are memory-based due to their storing of prior data or information.
then a time shift t + δt in the input results in the identical time shift in the output, i.e.,
However, if
y(t ) = tu(t ) (19.6)
since y(t) is explicitly a function of time, i.e., the input u(t) is premultiplied by time t, then it is not
time invariant. The interested reader should consult Ref. [1] for further details.
Source block
ke(ti)
FIGURE 19.2 Simple model involving a feedback loop and Source, Sum, Gain, and Output blocks.
For example, consider the system modeled in Figure 19.1 with a simple feedback loop: the output
of the Sum block is multiplied by a gain of k, and this is then fed back into the Sum block. Let the
output of the Linear Function, Sum, and Gain blocks be f(t), e(t), and g(t), respectively, as shown in
Figure 19.2.
Hence, the system being modeled may be described as follows:
g (t ) k
⇒ =
f (t ) (1 − k )
where the leading matrix is A, the output vector x = [e(t), g(t)]T, and the right-hand side vector
b = [f(t), 0]T. The problem with this system lies with the gain constant k: if k = 1, then the rows of A
are linearly dependent, and hence the inverse, A−1, does not exist. As a result, the numerical solution
of (19.8) will be erroneous.
Non-Feedback-Based Signal Propagation 451
(a) (b)
FIGURE 19.3 Model diagrams showing types of signal propagation: (a) direct signal propagation with no
feedback loops and (b) a simple linear equation model involving a feedback loop.
FIGURE 19.4 Direct signal propagation from the Signal Generator and Constant source blocks to an Output
sink block involving two math operation blocks, Gain and Sum: the diagram is intentionally shown in its dis-
connected state where distinct ports and connections are clearly visible.
452 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
However, before a signal can be propagated in a direct manner, the connectivity of the model
needs to be analyzed to detect the possible existence of feedback, equivalently algebraic, loops.
If loops do not exist, then direct signal propagation can proceed; if they do, then an alternative
method of equation computation is required, e.g., the computation of a set of simultaneous nonlinear
equations (this is the subject of Chapters 22 and 23).
G6
E4 H7 F5 C2 I8 D3 J9
B1 A0
FIGURE 19.6 General representation of a model with two feedback loops, where the letters and numbers
represent the order in which the blocks were placed on the palette.
TABLE 19.1
Block-to-Block Connectivity Matrix
A0 B1 C2 D3 E4 F5 G6 H7 I8 J9
A0
B1 1
C2 1 1
D3 1 1
E4 1
F5 1 1
G6 1
H7 1
I8 1
J9
TABLE 19.2
Tours through Block Diagram
Tours Ending at an Output Block Tours Ending in a Loop-Repeated Block
E→H→F→C→A E→H→F→B→H
E→H→F→C→I→D→J E→H→F→C→I→D→G→C
The functions that are required by and are related to the DetermineLoopsFromConnectivity
Matrix() method are as follows:
void CSystemModel::DetermineModelFeedbackLoops()
{
int i; // row index
int j; // col index
int ncols; // number of columns of block connectivity
matrix
int nrows; // number of rows of block connectivity matrix
int **BlockConMatrix = NULL;// block connectivity matrix
CPort *from_port = NULL; // port from which a connection emanates
CPort *to_port = NULL; // port to which a connection is attached
list<CBlock*>::iterator it_blk;
list<CBlock*> &blk_list = GetBlockList();
list<CConnection*>::iterator it_con;
list<CPoint>::iterator it_pt;
vector<CPort*>::iterator it_port;
// MEMORY ALLOC
BlockConMatrix = new int *[nrows];
for(i=0; i<nrows; i++)
{
BlockConMatrix[i] = new int[ncols]; // allocate an array of ints,
return & to BlockConMatrix[i]
}
// MEMORY DELETE
if(BlockConMatrix != NULL)
{
for(i=0; i<nrows; i++)
{
delete [] BlockConMatrix[i];
}
}
delete [] BlockConMatrix;
return;
}
The developer will notice the introduction of several functions here as indicated in the general
steps listed earlier. However, not listed previously is GetRefToBlock(), which is called upon
a “to_port” object of type pointer-to-CPort, to get a reference to the block upon which the port
resides. Hence, add a public member function to the CPort class with the prototype, CBlock
&GetRefToBlock(void), and edit it as follows.
CBlock &CPort::GetRefToBlock()
{
return m_rRefToBlock;
}
void CSystemModel::ValidateModel()
{
…
// -- INFORM USER OF END OF BUILD MODEL STAGE
if( (error1 == 1) || (error2 == 1) || (error3 == 1) )
{
…
}
else
{
sMsg.Format(“\n No build errors. \n”);
AfxMessageBox(sMsg, nType, nIDhelp);
if(m_iNSourceLoopTours > 0)
Non-Feedback-Based Signal Propagation 457
{
sMsg.Format(“\n Note! The number of source-to-loop tours in
the model is: %d. \n”, m_iNSourceLoopTours);
AfxMessageBox(sMsg, nType, nIDhelp);
}
}
…
}
The developer will notice the presence of two new member variables “m_iNSourceOutputTours”
and “m_iNSourceLoopTours”. These are used to record the number of tours from a source block that
end in either an Output block or loop-repeated node (feedback loop), respectively, and these values
are set in the SaveTourVector() function (see Section 19.5.2.8). Hence, add two new private
integer member variables named “m_iNSourceOutputTours” and “m_iNSourceLoopTours” to the
CSystemModel class, initializing them to 0 as shown in the ValidateModel() function previously.
The DetermineModelFeedbackLoops() function initially constructs a block con-
nectivity matrix “BlockConMatrix” to be populated with values in a similar manner to that
shown in Table 19.1. A call is made to set the reference-from ports of secondary connections
not directly connected to a block, but rather emanating from a bend point of a primary connec-
tion, to share the same output port as the primary connection. A nested loop then iterates over
the blocks, ports, and connections to determine the reference-from and reference-to ports of
connection objects defining block-to-block connectivity. This connectivity is then recorded in
the block connectivity matrix by the AssignBlockConnectivityMatrixValues() func-
tion. Thereafter, connections that emanate from bend points have their reference-from ports set
to NULL, returning them to the state they were in prior to the function being called: this is the
case since they are not physically attached to a port, but rather a bend point. Finally, the pres-
ence of feedback loops defined by the connectivity matrix needs to be determined using the
DetermineLoopsFromConnectivityMatrix() function.
void CSystemModel::SetRefFromPortOfConnectionFromBendPoint()
{
int ref_from_port_flag; // flag indicating whether m_pRefFromPort
was set
CPort *from_port = NULL; // port from which primary connection
emanates
list<CPoint>::iterator it_pt;
list<CConnection*>::iterator it_con;
list<CConnection*>::iterator it_con_bend_pt;
{
if( (*it_con)->GetRefFromPoint() != NULL) // if there is a ref-
from POINT for this connection
{
connections_from_bend_pt_list.push_back(*it_con);
}
}
// Set the reference-from-port for connections emanating from bend
points.
it_con_bend_pt = connections_from_bend_pt_list.begin();
while(connections_from_bend_pt_list.size() > 0)
{
// Init. ref_from_port_flag: (0) no m_pRefFromPort was set,
(1) m_pRefFromPort was set.
ref_from_port_flag = 0;
// Iterate over PRIMARY connection list to find bend points with
SECONDARY connections attached to them
for(it_con = m_lstConnection.begin(); it_con != m_lstConnection.
end(); it_con++)
{
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList(); // get the bend_pts_list
// Iterate over bend points list of the current primary
connection
for(it_pt = bend_pts_list.begin(); it_pt != bend_pts_list.
end(); it_pt++)
{
// Determine connectivity of a secondary connection’s
tail pt. to a primary connection’s bend pt.
if( ( (*it_con_bend_pt)->GetConnectionPointTail().x ==
(*it_pt).x) && ( (*it_con_bend_pt)-
>GetConnectionPointTail().y == (*it_pt).y) )
{
from_port = (*it_con)->GetRefFromPort(); // get the
primary connection’s ref-from-port
// A ref-from-port of a PRIMARY connection is NULL if
the primary connection is not directly
// connected to a block port.
// A ref-from-port of a TERTIARY connection is NULL
if it is connected to a bend pt. of a
// SECONDARY connection that has not yet had its
m_pRefFromPort assigned (to be that of its
// primary connection).
if(from_port != NULL)
{
// Set the m_pRefFromPort of the SECONDARY
connection that is attached to a PRIMARY
// connection’s bend point, to be the same as the
PRIMARY connection’s m_pRefFromPort.
(*it_con_bend_pt)->SetRefFromPort(from_port);
// Erase the connection from the
connections_from_bend_pt_list since its
m_pRefFromPort
Non-Feedback-Based Signal Propagation 459
if(ref_from_port_flag == 1)
{
break; // break from it_con (primary connections)
}
}// end for it_con
input port the connection enters. Add a public member function to the CSystemModel
class with the prototype, void CSystemModel::AssignBlockConnectivity
MatrixValues(CBlock *from_block, CBlock *to_block, int **BlockConMatrix),
and edit it as shown.
void CSystemModel::AssignBlockConnectivityMatrixValues(CBlock *from_block,
CBlock *to_block, int **BlockConMatrix)
{
int i = 0; // row index: “from” block
int j = 0; // col index: “to” block
list <CBlock*>::iterator it_blk;
list<CBlock*> &blk_list = GetBlockList();
// Obtain the row index, i.e. the “from” block
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
if(from_block == (*it_blk) )
{
break;
}
i++; // increment the i-th “from” block row index
}
// Obtain the column index, i.e. the “to” block
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
if(to_block == (*it_blk) )
{
break;
}
j++; // increment the j-th “to” block col index
}
// The connection from the “from” block to the “to” block is denoted
by unity.
BlockConMatrix[i][j] = 1;
}
The model’s block list is iterated over, and if the “from_block” is the current block, then the loop
is exited and the index used to denote the row of the block connectivity matrix. Similarly, if the
“to_block” is the current block, then the index is used to denote the column of the matrix. A con-
nection between the blocks is denoted by a “1,” and “0” otherwise. The developer will notice
that the numbers representing the model blocks are based on the order in which the blocks were
added to the model’s block list, i.e., the order in which they were placed on the palette by the user
(later a reordering of blocks will be performed, but the logic to find the connectivity matrix is still
applicable).
{
BuildTour(A, nrows, ncols, node_vec, n_nodes, tour_vec);
}
// MEMORY DELETE
delete [] node_vec;
delete [] tour_vec;
return;
}
Initially the array “node_vec” is filled, by default, with “−1” values. Then the connectivity matrix
is checked for null columns, which indicate that no block on row i is connected to the block repre-
sented by column j, in the forward-direction sense. Hence, the column number represents a source
node. In total, a number of “n_nodes” are added to the node vector, “node_vec”. Not all blocks can
be source blocks: there must be at least one Output block in the model, in which case “n_nodes” is
less than the total number of blocks represented by “nrows” and equivalently “ncols”.
// Copy the incoming tour vec into the local tour vec
for(j=0; j<ncols; j++)
{
tour_vec[j] = in_tour_vec[j];
}
PrintVector(tour_vec, ncols);
PrintVector(node_vec, ncols);
// -- ITERATE OVER ONLY THE NODES IN THE NODE VECTOR FROM WHICH
CONNECTIONS TO OTHER NODES ARE PREMISSABLE
for(node=0; node<n_nodes; node++)
{
// Get the offset position in the tour vector into which the next
node should be placed
posn = FindCurrentTourPosition(in_tour_vec, ncols);
// Get starting node, i.e. a node in the node vector (of source
nodes or branch nodes)
i = node_vec[node];
tour_vec[posn] = i;
tour_end = 0;
PrintVector(tour_vec, ncols);
// Check for repeated entries here, after the new node addition
to the tour_vec: if there is a repeat, end tour.
if(CheckRepeatedEntries(tour_vec, ncols) > 0)
{
tour_end = 1;
SaveTourVector(tour_vec, ncols); // save tour_vec since tour
has had an entry added but a loop exists
}
// -- WHILE TOUR NOT COMPLETE
while(tour_end == 0)
{
// Get the offset position in the EVOLVING TOUR VECTOR into
which the next node should be placed
posn = FindCurrentTourPosition(tour_vec, ncols);
if(posn >= ncols) // if the posn is after the end of the
array, then the tour vector is filled.
{
tour_end = 1;
SaveTourVector(tour_vec, ncols); // save tour_vec since
tour has ended due to no further connections
break; // break from while loop
}
// Find the number of connections i.e. branch nodes to the
current node
n_branch_nodes = NumberOfOnesOnMatrixRow(A, i, ncols);
// If there are no 1’s in the cols then there are no
connecting nodes
if(n_branch_nodes == 0)
{
tour_vec[posn] = −1; // −1 indicates no fwd connection
exists from the current node
tour_end = 1; // end tour
Non-Feedback-Based Signal Propagation 465
PrintVector(tour_vec, ncols);
}
// If there is > one 1, in the cols, then there is > 1
connecting node
else if(n_branch_nodes > 1)
{
// MEMORY ALLOC
branch_node_vec = new int[ncols];
PrintVector(branch_node_vec, ncols);
tour_end = 1;
// MEMORY DELETE
delete [] branch_node_vec;
}
{
ResetTourVector(tour_vec, in_tour_vec, ncols);
}
}// end while
// Get the time of the end of this loop
seconds = time(NULL); // get seconds elapsed since 1-1-1970
strftime(t_string, 20, “%H:%M:%S”, localtime(&seconds) );
// get the local time as a string
// Print msg. end of current node/block
sMsgTemp.Format(“\n BuildTour()\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n End of for node = %d\n time: %s\n”, node,
t_string);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
}// end for
// MEMORY DELETE
delete [] tour_vec;
return;
}
The workings of the BuildTour() function are somewhat difficult to understand since recursion
is involved. Hence, the interested reader may like to experiment with the exploratory code provided
in Appendix C, a Win32 Console Application titled NodeArcConnectivity that finds tours in a node-
arc incidence matrix of 1s and 0s. The numerous output statements clarify the order of operations in
the BuildTour() function and the manner in which it is recursively called.
At the start of the BuildTour() function mentioned earlier, the tour vector to be constructed
is assigned the incoming tour, since the function is called recursively, and an incomplete tour needs
to be initially copied before it can be worked upon further to be completed.
The main for loop iterates over the nodes/blocks in the node array/vector “node_vec”: initially
the “node_vec” contains all source nodes from which a tour may start, but during the tour build-
ing process, this “node_vec” contains the branch nodes, i.e., nodes that may be branched to, from a
branch point. For the previous example, the initial node vector contains source block E4 followed
by “−1” values for the remaining entries. However, three branches, and hence recursive calls, occur:
(1) F5 branches to B1 and C2, (2) C2 branches to A0 and I8, and (3) D3 branches to G6 and J9.
Hence, when iterating through the algorithm, these branch nodes will be used in the node vector.
At the start of the for loop, the current tour position, found using FindCurrentTourPosition(),
is that of the first negative number in the incoming “tour_vec”: initially the elements are initialized to be
uniquely negative numbers, and node/blocks visited have uniquely nonnegative numbers. The node to
be added to the tour is that in the “node_vec” for the current iteration. The CheckRepeatedEntries()
function simply checks the tour for repeated numbers, signifying a feedback loop in the model: if there
are repeated nodes, then a call is made to SaveTourVector() (discussed in the following).
The while loop iterates until a block-to-block tour is complete. Initially the cur-
rent position in the evolving “tour_vec” at which a new entry should be placed is obtained
(FindCurrentTourPosition()). Then three clauses of an if-else statement concerning the
number of branch nodes, “n_branch_nodes”, found by a call to NumberOfOnesOnMatrixRow()
are tested: (1) if there are no branch nodes, then the tour ends at an Output block, and the tour may
be saved using SaveTourVector(); (2) if there is just one branch node, then the tour progresses
to that connected node; and (3) if there is more than one branch node, then the nodes are found
using FindBranchNodes(), and the BuildTour() function is recursively called, passing in
“branch_node_vec”, to continue the search process.
Non-Feedback-Based Signal Propagation 467
At the end of the while loop, if a tour was found, the tour vector, “tour_vec”, is set to be
the (original) incoming tour vector, “in_tour_vec”, since the sequence of nodes up to, but not
beyond, the branch point needs to be reestablished for subsequent tours emanating from the
branch point.
Finally, in the SaveTourVector() function, the number of tours that end at an Output block
(“m_iNSourceOutputTours”) or at a loop-repeated node (“m_iNSourceLoopTours”) signifying a
feedback loop is incremented.
The reader is encouraged to experiment with the aforementioned Win32 Console Application,
NodeArcConnectivity, and view all the print statements indicating the progressive building of
tours that exist in the sample model. In addition, the developer may like to use the debugger to
step through the functions observing the changing program state (see Appendix D on how to use
the debugger.).
{
branch_node_vec[j] = −1;
}
// Iterate over the cols of A for the current row (i), recording
those with one’s (1), indicating node connections.
for(j=0; j<ncols; j++)
{
if(A[i][j] == 1)
{
branch_node_vec[cnt] = j;
cnt++;
}
}
n_branch_nodes = cnt; // update the number of connecting/branch nodes
return 0;
}
return n_repeats;
}
// Global Functions
CDiagramEngDoc* GetDocumentGlobalFn(void);
double **ConvertStringToDouble(CString string, int &nrows, int &ncols);
char *StripInputString(char *input_str);
int DetermineDataDimsByStrpbrk(char *in_str, int &nrows, int &ncols,
int &max_row_length);
int DetermineDataDimsByStrtok(char *in_str, int &nrows, int &ncols,
int &max_row_length);
double **ConvertStringMatrixToDoubleMatrix(char **str_matrix, int nrows,
int ncols);
int PrintMatrix(double **M, int nrows, int ncols);
int PrintMatrix(int **M, int nrows, int ncols);
int PrintVector(double *v, int ncols);
Non-Feedback-Based Signal Propagation 471
Now add the new PrintMatrix() function to the “DiagramEngDoc.cpp” source file, beneath the
existing global functions, and edit the code as shown in the following.
// Global Functions
CDiagramEngDoc* GetDocumentGlobalFn(void);
double **ConvertStringToDouble(CString string, int &nrows, int &ncols);
char *StripInputString(char *input_str);
472 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Now add the new integer version of PrintVector() to the “DiagramEngDoc.cpp” source file,
beneath the existing double version, and edit the code as shown in the following.
// Signal
class CSignal
{
public:
CSignal(void);
∼CSignal(void);
// Accessor methods
CString GetSignalName(void);
void SetSignalData(double **matrix, int nrows, int ncols);
void SetSignalName(CString name);
void SetSignalValidity(int validity);
private:
int m_iNrows; // no. of rows of signal matrix
int m_iNcols; // no. of cols of signal matrix
int m_iValidity; // signal data-based validity
double **m_dSignalMatrix; // matrix signal value
CString m_strSignalName; // signal name
};
The developer will also note that the class member variables should be initialized as shown in the
following class constructor CSignal(); the numerical variables are assigned to nullity, and the
signal name is at present a generic placeholder name, “signal_name”:
CSignal::CSignal(void)
{
// Init.
m_iNrows = 0;
m_iNcols = 0;
m_iValidity = 0;
m_dSignalMatrix = NULL;
m_strSignalName = “signal_name”;
}
However, since a CSignal class has a matrix member variable, “m_dSignalMatrix”, of dimension
“m_iNrows” (number of rows) by “m_iNcols” (number of columns), which requires the alloca-
tion of memory to hold the connection-based signal data, the CSignal::∼CSignal() destructor
should deallocate this memory when called. Furthermore, when a connection object is deleted
by the user, hence calling the CConnection::∼CConnection() destructor, its contained
CSignal object, “m_pSignal”, should be destroyed through a call to the CSignal::∼CSignal()
destructor.
474 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
CConnection::∼CConnection(void)
{
// Delete the connection-held signal data
if(m_pSignal != NULL)
{
delete m_pSignal;
m_pSignal = NULL;
}
}
CSignal::∼CSignal(void)
{
int i;
// MEMORY DELETE
if(m_dSignalMatrix != NULL)
{
for(i=0; i<m_iNrows; i++)
{
delete [] m_dSignalMatrix[i];
}
delete [] m_dSignalMatrix;
m_dSignalMatrix = NULL;
}
}
Now if a connection object is deleted by the user and if it holds any CSignal data, this data will be
deleted, preventing a memory leak.
void CDiagramEngDoc::OnSimStart()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int error_flag = 0; // flag indicating whether model is in error or
has feedback loops
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Display a msg. box
//sMsg.Format(“\n CDiagramEngDoc::OnSimStart()\n\n”);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Validate model
error_flag = m_SystemModel.ValidateModel();
// Propagate signals
if( (error_flag == 0) && (m_SystemModel.GetNFeedbackLoops() == 0) )
{
// m_SystemModel.SignalPropagationDirect();
}
else if( (error_flag == 0) && (m_SystemModel.GetNFeedbackLoops() > 0) )
{
// m_SystemModel.SignalPropagationSystemOfEquations();
}
else
{
sMsgTemp.Format(“\n CDiagramEngDoc::OnSimStart() \n\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Model is in error and simulation cannot
start. \n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Please resolve model errors, before rebuilding/
restarting. \n”);
476 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
}
// DiagramEng (end)
}
int CSystemModel::ValidateModel()
{
int i;
int error1 = 0; // error in model blocks: (0) no error, (1) error
int error2 = 0; // error in model ports: (0) no error, (1) error
int error3 = 0; // error in mode connections: (0) no error,
(1) error
int error_flag = 0; // error flag returned if there are any build
errors
…
if( (error1 == 1) || (error2 == 1) || (error3 == 1) )
{
error_flag = 1;
…
}
…
return error_flag;
}
void CDiagramEngDoc::OnModelBuild()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int error_flag = 0; // error flag for model validation
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Validate Model
error_flag = m_SystemModel.ValidateModel();
Non-Feedback-Based Signal Propagation 477
void CSystemModel::SignalPropagationDirect()
{
int n_TimeSteps; // total no. of time steps for
numerical integration
int out_blk_state = 0; // flag denoting output-block
operation state
int std_blk_state = 0; // flag denoting standard-block
operation state
int t_cnt; // time counter
clock_t tv1; // init time for execution time calc.
clock_t tv2; // final time for execution time calc.
double delta_t = m_dTimeStepSize; // time step size of system model
simulation
double t; // i-th time point t_i = t
double t_execution; // execution time
double t_init = m_dTimeStart; // time init of system model
simulation
CPort *ref_from_port = NULL; // output port from which connection
emanates
CString sMsg; // string to be displayed
CString sMsgTemp; // temp msg.
478 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
sMsgTemp.Format(“\n SignalPropagationDirect()\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n WARNING! Unsuccessful
(non-Output) block operation!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Aborting signal propagation!\n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
break; // break from for loop
}
else if(std_blk_state == 0) // NO SIGNAL DATA AVAILABLE
FOR CURRENT CONNECTION, HENCE KEEP ITERATING
{
// No action necessary: keep iterating until signal
data available upon connection
}
else if(std_blk_state == 1) // DELETE LOCAL CONNECTION
IF OPERATION SUCCESSFUL
{
con_list_copy.erase(it_con);
break; // break from for loop
}
}// end for
if(std_blk_state == -1)
{
break; // break from while loop
}
}// end while
Initially the actual connection list is obtained by reference, and then the reference-from ports of
connections emanating from bend points are set to be the same reference-from ports as their par-
ent connections, via the call to SetRefFromPortOfConnectionFromBendPoint(). Then a
copy of the connection list is obtained for the purpose of obtaining signal data (later).
At the start of the main iteration time-based loop, the total number of simulation time-steps,
“n_TimeSteps”, is determined through the call to DetermineNTimeSteps(), and then the simu-
lation is iterated over for all time-steps using a “for(t_cnt)” loop, up to and including the last time-
step: since “t_cnt” starts from 0, a total of “n_TimeSteps + 1” iterations actually occur. The while
loop together with the contained “for(it_con)” loop iterates over a list of connections to allow the
block from which the connection emanates, to generate signal data through the OperateOnData()
function. This obtains the block’s input signal data, performs a block operation, and then writes the
output data to the output signal to be transmitted along the connection to the next block.
Output blocks, however, do not have output signals but rather only input signals, and hence they are
iterated over separately, and OperateOnData() is called to determine the result to be displayed.
The OperateOnData() function is to be provided as a virtual function in the base CBlock class
(later) and is to be overridden by all the CBlock-derived classes. The developer will notice that the
returned value from OperateOnData() can be “−1,” “0,” or “1”: “−1” indicates that an operational
error occurred and signal propagation is terminated; “0” indicates that a read/write error occurred
and that no signal was read from, or written to, a connection, so execution continues until signal data
are available; and “1” indicates that the block operation was a success, the local connection is then
deleted, and iteration continues over the remaining connections. The variables, “std_blk_state” (stan-
dard non-Output block state) and “out_blk_state” (Output block state), for non-Output and Output
blocks, respectively, receive the returned result and are used to filter the course of action.
At the end of each time-step, ResetSignalData() is called on the pointer-to-CSignal object
returned by GetSignal(), which itself is called upon the pointer-to-CConnection object, to reset
each connection’s CSignal-based data. In addition, a copy of the connection list needs to be re-
obtained through a call to GetConnectionList(), since the previous copy was iterated over and
its elements erased after successful block operation (OperateOnData()).
Finally, after the time-based iteration has finished (“for(t_cnt)”), all the reference-from ports of
connections emanating from bend points need to be reset to NULL to leave the program in the state
it was in, prior to the call to SignalPropagationDirect(): only after resetting the reference-
from ports of these connections can the function return.
int CSystemModel::DetermineNTimeSteps()
{
int n_TimeSteps; // no. of integer time steps
double delta_t = m_dTimeStepSize; // time step size of system model
simulation
double t_init = m_dTimeStart; // time init of system model
simulation
double t_final = m_dTimeStop; // time final of system model
simulation
double t_final_revised; // time final revised given
integer no. of time steps
CString sMsg; // string to be displayed
CString sMsgTemp; // temp msg.
482 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The total number of time-steps, “n_TimeSteps”, is simply the difference between the final time,
“m_dTimeStop”, and initial time, “m_dTimeStart”, divided by the simulation time-step size
“m_dTimeStepSize”. No class member variable is used to record this, since it can be evalu-
ated by calling the DetermineNTimeSteps() function, and hence there is no need for a
GetNTimeSteps() member function of the CSystemModel class (at least for now).
void CSignal::ResetSignalData()
{
int i;
// MEMORY DELETE
// Delete the arrays held at each of the entries of the column vector
if(m_dSignalMatrix != NULL)
{
for(i=0; i<m_iNrows; i++)
Non-Feedback-Based Signal Propagation 483
{
delete [] m_dSignalMatrix[i];
}
delete [] m_dSignalMatrix;
}
// -- RESET VARS
m_iNrows = 0;
m_iNcols = 0;
m_iValidity = 0;
m_dSignalMatrix = NULL;
return;
}
19.7.1.8 Add a CSystemModel::SignalPropagationSystemOfEquations()
Function
The other signal propagation function is SignalPropagationSystemOfEquations() and is
used for the computation of models with feedback loops. For now, this function is empty and will
be pursued later in the project. Add a public member function to the CSystemModel class with the
prototype, void CSystemModel::SignalPropagationSystemOfEquations(void),
and edit it as follows.
void CSystemModel::SignalPropagationSystemOfEquations()
{
// Material to be added later.
}
Hence, add a virtual member function to the CBlock class with the prototype:
virtual int CBlock::OperateOnData(int t_cnt, double t, double delta_t).
Edit the code as shown in the following, where a message box is used to display the time-based param-
eters passed into the function, and for now, “valid” is set to “1”, indicating a successful operation:
FIGURE 19.7 Simple model showing a Constant block linked directly to an Output block to test direct signal
propagation.
Non-Feedback-Based Signal Propagation 485
This function initially calls DetermineNTimeSteps() to determine the total number of time-
steps, “n_TimeSteps”, of the simulation. This is required since the signal data stored using the
variable, “m_dOutputMatrix”, should have enough memory allocated to hold all output data signals
passed to the Output block: in this case, “n_TimeSteps + 1” multiples of the incoming data struc-
ture’s column-count (amount of columns) are required, since “t_cnt” starts from 0.
For example, consider a data matrix, “m_dOutputMatrix”, of the following form:
where fs(t), for s ∈ {1, …, 4} (four signals are used here for simplicity), are the individual signals
stored in a 2 × 2 matrix, for each time point ti ∈ [t0, tn], for initial and final simulation time points,
t0 and tn, respectively. The total number of time-steps is n, the total number of time points is n + 1,
and the number of columns of each signal matrix generated at each time point is two; hence, the
total columns of the entire matrix, “m_dOutputMatrix”, equivalently, M, is 2(n + 1).
The data that are being passed as signals along the single connection object entering the Output
block are read using a call to ReadDataFromSingleInputSignal(): if the data are invalid,
i.e., are unavailable, the function returns; if not, the matrix input is stored in the member variable
“m_dOutputMatrix” (after the “if(t_cnt == 0)” section).
If the data read in are valid, execution continues to the “if(t_cnt == 0)” section. On the first itera-
tion, if “m_dOutputMatrix” is not NULL, i.e., if it holds data, then these data are deleted and new
memory allocated to be of size “m_iNrows” by “m_iNcols”. This is performed since between one
rebuilding and execution of the entire block diagram model and another, the signal data size may
have changed due to intervening user input, and hence the old model’s output data structure needs
to be renewed and reinitialized:
4. Add a public constant accessor method to return the data member with prototype, double
**COutputBlock::GetOutputMatrix(void) const, and edit it as follows:
5. Finally, if an Output block is deleted from the system model, then any allocated memory
that would not be deallocated in the COutputBlock::OperateOnData() function
itself should be freed in the COutputBlock destructor, as shown in the following.
COutputBlock::∼COutputBlock(void)
{
int i;
// MEMORY DEALLOCATION
if(valid)
{
break; // exit for it_port
}
}// end it_port
return valid;
}
19.8.1.5 Add Accessor Methods to Get and Set Signal Data and Validity
The WriteDataToOutputSignal() and ReadDataFromSingleInputSignal() func-
tions call the CSignal class accessor functions: SetSignalData(), SetSignalValidity(),
GetSignalData(), and GetSignalValidity(). Hence these should be added to the CSignal
class with the following declarations and definitions:
The GetSignalData() function shown in the following simply assigns the incoming arguments,
“nrows” and “ncols”, and the values of the private member variables, “m_iNrows” and “m_iNcols”,
respectively, and returns the CSignal private member variable, “m_dSignalMatrix”.
The SetSignalData() function in the following initially deletes any existing signal data
and then creates memory, using the “nrows” and “ncols” input dimensional arguments, for
the private CSignal member variable, “m_dSignalMatrix”, and assigns the incoming “matrix”
data to “m_dSignalMatrix”. The deletion of memory is required in case it was not deleted by a
ResetSignalData() call elsewhere in the program flow. This may occur when a secondary
connection object is attached indirectly to a block, and ResetSignalData() is called on the
primary connection object directly attached to the block: the primary connection object’s signal
data would be reset, but the secondary connection object’s data may not be. Hence, to prevent a
possible memory leak, the member variable, “m_dSignalMatrix”, is deleted if it is non-NULL
upon entry into the function.
if(m_dSignalMatrix != NULL)
{
for(i=0; i<m_iNrows; i++)
{
delete [] m_dSignalMatrix[i];
}
delete [] m_dSignalMatrix;
}
// -- MEMORY NEW
// Allocate an array of ptrs-to-double and return the & to
m_dSignalMatrix
// Note: an array is of type ptr, hence an array of ptrs is of type
ptr-ptr.
m_dSignalMatrix = new double *[nrows];
return;
}
int CSignal::GetSignalValidity()
{
return m_iValidity;
}
void CSignal::SetSignalValidity(int valid)
{
// Assign integer validity
m_iValidity = valid;
}
494 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
(a)
(c) (b)
(d)
FIGURE 19.8 Simulation windows: (a) Constant block dialog window showing a column vector, (b) Numerical
Solver dialog window showing input parameters, (c) output from the SignalPropagationDirect()
method, and (d) output from the COutputBlock::OperateOnData() method, showing accumulated data
from the Constant block for 10 time-steps with 11 data points.
Non-Feedback-Based Signal Propagation 495
The developer will notice that there are 11 columns of data printed in Figure 19.8d, yet there
are only 10 time-steps (“n_TimeSteps”) involved, where t0 = 0.0 s. This is the case since in the
OperateOnData() function of the COutputBlock, a total number of columns, “ncols_total”, is
set as “ncols_total = (n_TimeSteps + 1)*ncols;”, where “ncols” is the number of columns of the sig-
nal data of the incoming connection; here, “ncols” is “1” since a single constant column vector (c) is
being generated at each time-step. The extra column (+1) is present since “t_cnt” starts from “0” in
SignalPropagationDirect(); hence there is one more data point than there are a number of
time-steps. In brief, data for time points t0 up to and including tn are saved.
Now that a simple signal may be propagated from one block to another over one connection, as
shown by the previous example, involving a Constant and an Output block, the OperateOnData()
functions for the remaining CBlock-derived classes and additional functions to allow them to work
need to be added to the project. Part III, after Chapter 21, explains the addition of the remaining
block-based functions in order that correct signals may be propagated for all blocks through the
model using the direct signal propagation method.
19.9 SUMMARY
Signal propagation can be performed once a model has been validated and is of essentially two
different forms: (1) direct signal propagation, used where there are no feedback loops in a model,
and (2) a simultaneous equation-based approach, used when feedback or algebraic loops exist.
The key functions introduced to the project to determine the presence of feedback or algebraic
loops are (1) DetermineModelFeedbackLoops(), to determine model feedback loops;
(2) SetRefFromPortOfConnectionFromBendPoint(), to set a connection’s reference-
from port; (3) AssignBlockConnectivityMatrixValues(), to assign values in a matrix
denoting block connectivity; and (4) DetermineLoopsFromConnectivityMatrix(), to
determine feedback loops from the connectivity matrix. The key function required to deter-
mine the loops from the connectivity matrix is BuildTour(), which recursively builds tours
using the block connectivity matrix that end at either an Output block or a repeated node, sig-
nifying a feedback loop. Various additions are made to allow direct signal propagation to work,
including the SignalPropagationDirect() function in the CSystemModel class and
the virtual OperateOnData() function in the CBlock class, to be overridden by the derived
block classes, to perform block operations on input data, and to generate output signal data.
Finally, the ReadDataFromSingleInputSignal() and WriteDataToOutputSignal()
methods are added to the CBlock class to read and write signals on connection objects, using
CSignal::GetSignalData() and CSignal::SetSignalData(), respectively.
REFERENCES
1. Oppenheim, A. V., Willsky, A. S., and Nawab, S. H., Signals and Systems, 2nd edn., Prentice Hall, Upper
Saddle River, NJ, 1997.
2. Ogata, K., Modern Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.
3. Shabana, A. A., Computational Dynamics, 2nd edn., John Wiley & Sons, New York, 2001.
20 Graph Drawing
20.1 INTRODUCTION
The Output block currently displays numerical data to the screen via a message box using an
AfxMessageBox() function call. This mechanism is suitable only if specific numerical data val-
ues need to be verified, but typically, the user wants to see a graphical relation between the depen-
dent variable, e.g., f(t), f(t), or F(t), for scalar, vector, and matrix functions, respectively, and the
independent variable t. That is, the output signal—a scalar, vector, or matrix value—is to be plotted
with respect to time for all time points of the simulation. The following instructions indicate how
graph-like drawing is added to the DiagramEng project to display the data of the Output block in a
separate view window. The general steps to add this functionality are as listed while specific details
are provided in the sections that follow:
497
498 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 20.1 Output block dialog window showing the controls as specified in Table 20.1.
TABLE 20.1
Dialog Object, Properties, and Settings for the Output
Block Dialog Window (IDD_OUTPUT_BLK_DLG)
Object Property Setting
Group box ID ID_OUTPUT_BLK_DLG_GPBOXNOT
Caption Textual Output Settings
Radio button ID ID_OUTPUT_BLK_DLG_RB_STDNOT
Group Checked
Caption Standard notation
Radio button ID ID_OUTPUT_BLK_DLG_RB_SCINOT
Group Unchecked
Caption Scientific notation
Group box ID ID_OUTPUT_BLK_DLG_GPBOXTPTS
Caption Graphical Output Settings
Radio button ID ID_OUTPUT_BLK_DLG_RB_TPTS
Group Checked
Caption Show time points
Radio button ID ID_OUTPUT_BLK_DLG_RB_NOTPTS
Group Unchecked
Caption Hide time points
Button ID ID_OUTPUT_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
Button ID ID_OUTPUT_BLK_DLG_BTN_SG
Caption &Show Graph
Graph Drawing 499
TABLE 20.2
Objects, IDs, Class, and Event-Handler Functions for the COutputBlockDialog Class
Object ID Class COMMAND Event Handler
Show Graph ID_OUTPUT_BLK_DLG_BTN_SG COutputBlockDialog OnOutputBlkDlgBtnShowGraph()
button
OK button ID_OUTPUT_BLK_DLG_BTN_OK COutputBlockDialog OnOutputBlkDlgBtnOk()
Cancel button IDCANCEL (default) COutputBlockDialog OnCancel()
void COutputBlockDialog::OnOutputBlkDlgBtnShowGraph()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
AfxMessageBox(“\n COutputBlockDialog::OnOutputBlkDlgBtnShowGraph
()\n”, MB_OK, 0);
// VIEW CONSTRUCTION
// To add later.
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
The developer will notice the “view construction” section in the code given earlier. The construction
of the Output block’s graph-like view will be added after the supporting functionality is introduced
to the project.
address returned to the member variable pointer: “m_pCOutputViewTemplate”. Hence, edit the
CDiagramEngApp::InitInstance() function with the code shown in bold in the following.
BOOL CDiagramEngApp::InitInstance()
{
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
// Change the registry key under which our settings are stored.
// TODO: You should modify this string to be something appropriate
// such as the name of your company or organization.
SetRegistryKey(_T(“Local AppWizard-Generated Applications”) );
LoadStdProfileSettings(); // Load standard INI file options
(including MRU)
// Register the application’s document templates. Document templates
// serve as the connection between documents, frame windows
and views.
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_DIAGRATYPE,
RUNTIME_CLASS(CDiagramEngDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CDiagramEngView) );
AddDocTemplate(pDocTemplate);
// DiagramEng (start)
// -- OUTPUT BLOCK VIEW TEMPLATE
m_pCOutputViewTemplate = new CMultiDocTemplate(
IDR_OUTPUTVIEW, // new menu resource
RUNTIME_CLASS(CDiagramEngDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
// RUNTIME_CLASS(CDiagramEngView) ); // old view type
RUNTIME_CLASS(COutputBlockView) ); // modifying the document
template with the new view type (derived from CView)
AddDocTemplate(m_pCOutputViewTemplate);
// DiagramEng (end)
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME) )
return FALSE;
m_pMainWnd = pMainFrame;
// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();
Graph Drawing 501
The developer will have noticed that “IDR_OUTPUTVIEW” is used as the resource ID in the
creation of a new CMultiDocTemplate object. This has not yet been created, but should be the same
menu resource as the IDR_MAINFRAME. Hence, copy and paste the IDR_MAINFRAME menu
resource in the Menu resource folder and rename it “IDR_OUTPUTVIEW”; the menus, File, View,
and Help will then be available for the Output block view.
1. Hence, add a new MFC-type class to the project named, COutputBlockView, with base
class CView.
2. Augment the class declaration with the code shown in bold in the following so that it is of
a similar structure to the existing CDiagramEngView class (also derived from CView).
3. Add the inline definition of the GetDocument() function to the “OutputBlockView.h”
header file beneath the class declaration as shown in bold in the following.
4. Since the definition of the GetDocument() function in the “OutputBlockView.h” header
file returns a pointer-to-CDocument, the header file, “DiagramEngDoc.h”, needs to be
included at the top of the “OutputBlockView.h”, also shown in bold.
#if !defined(AFX_OUTPUTBLOCKVIEW_H__251D1F63_48DA_42CF_A8C2_
D74F7218BC88__INCLUDED_)
#define AFX_OUTPUTBLOCKVIEW_H__251D1F63_48DA_42CF_A8C2_D74F7218BC88__
INCLUDED_
/////////////////////////////////////////////////////////////////////////
// COutputBlockView view
// Attributes
public:
CDiagramEngDoc* GetDocument(); // Manually added (DiagramEng)
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(COutputBlockView)
protected:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ∼COutputBlockView();
#ifdef_DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
// Generated message map functions
protected:
//{{AFX_MSG(COutputBlockView)
// NOTE - the ClassWizard will add and remove member functions
here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
// DiagramEng (start)
#ifndef _DEBUG // debug version in OutputBlockView.cpp
inline CDiagramEngDoc* COutputBlockView::GetDocument()
{ return (CDiagramEngDoc*)m_pDocument; }
#endif
// DiagramEng (end)
/////////////////////////////////////////////////////////////////////////
// COutputBlockView diagnostics
#ifdef_DEBUG
void COutputBlockView::AssertValid() const
{
CView::AssertValid();
}
Graph Drawing 503
6. Now that the COutputBlockView class has been declared, the header file
“OutputBlockView.h” needs to be included at the top of the “DiagramEng.cpp” source
file (as shown in bold in the following) since a CDocTemplate object is created using the
COutputBlockView class in the CDiagramEngApp::InitInstance() function.
// DiagramEng.cpp : Defines the class behaviors for the application.
//
#include “stdafx.h”
#include “DiagramEng.h”
#include “MainFrm.h”
#include “ChildFrm.h”
#include “DiagramEngDoc.h”
#include “DiagramEngView.h”
#include “OutputBlockView.h” // rqd. since COutputBlockView is used in
InitInstance()
The developer will notice that OnDraw() is provided in this new class since COutputBlockView
inherits publicly from CView; code will be added for this later to draw graphs within the Output
block’s view window.
#include “OutputBlockDialog.h”
#include “MainFrm.h” // rqd. since a ptr-to-CMainFrame is used in
OnOutputBlkDlgBtnShowGraph()
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// DiagramEng (start)
// theApp needs to be accessed from within the OutputBlockDialog.cpp
file, although it’s declared in the DiagramEng.cpp file.
// It is used in the fn OnOutputBlkDlgBtnShowGraph() below.
extern CDiagramEngApp theApp;
// DiagramEng (end)
/////////////////////////////////////////////////////////////////////////
// COutputBlockDialog dialog
…
void COutputBlockDialog::OnOutputBlkDlgBtnShowGraph()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n COutputBlockDialog::OnOutputBlkDlgBtnShowGraph
()\n”, MB_OK, 0);
// VIEW CONSTRUCTION
CMainFrame *main_frame = (CMainFrame *)theApp.m_pMainWnd;
// Get a pointer-to-CMainFrame.
CMDIChildWnd *pActiveChild = main_frame->MDIGetActive();
// Get a pointer to the active child window.
if(pActiveChild != 0)
{
CDocument *pDocument = pActiveChild->GetActiveDocument();
// Get a pointer to the active document.
if(pDocument != 0)
{
CDiagramEngApp *pApp = (CDiagramEngApp *)AfxGetApp();
CDocTemplate *pTemp = pApp->GetCOutputViewTemplate();
// Get the document template for the new view.
CFrameWnd *pFrame = pTemp->CreateNewFrame(pDocument,
pActiveChild); // Create a new frame.
if(pFrame != 0)
{
pTemp->InitialUpdateFrame(pFrame, pDocument);
// Update the frame.
}
}
}
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
Graph Drawing 505
The method used in this function to construct the view was originally found in the aforemen-
tioned document by Mahmood [1]. The key steps are (1) a pointer-to-CMainFrame is obtained using
“theApp”, (2) a pointer to the active child window is retrieved using MDIGetActive() called upon
“main_frame”, (3) a pointer to the active document is obtained using GetActiveDocument()
called upon “pActiveChild”, (4) the document template is retrieved for the new view using
GetCOutputViewTemplate(), (5) a new frame is created by calling CreateNewFrame()
upon the document template, and (6) the frame is updated by calling InitialUpdateFrame()
upon the document template [2].
void CDiagramEngView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
// DiagramEng (start)
InvalidateRect(NULL); // Invalidates the client area, i.e. forces a
redraw on the next WM_PAINT message.
// DiagramEng (end)
}
// DiagramEng (start)
InvalidateRect(NULL); // Invalidates the client area, i.e. forces a
redraw on the next WM_PAINT message.
// DiagramEng (end)
}
Now, upon running the application and clicking on the Show Graph button, an empty
COutputBlockView window appears as shown in Figure 20.2 (right window).
506 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 20.2 Output block (COutputBlock) (left) with its related empty view (COutputBlockView) (right).
void COutputBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Create a dialog object. of class COutputBlockDialog : public
CDialog
// COutputBlockDialog oDlg;
COutputBlockDialog oDlg(this);
// Set the dialog class vars using the block class vars
oDlg.m_iNotation = m_iNotation;
oDlg.m_iTimePtDisplay = m_iTimePtDisplay;
// Return val of DoModal() fn of ancestor class CDialog is checked to
determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign COutputBlockDialog variable values to COutputBlock
variable values.
m_iNotation = oDlg.m_iNotation;
m_iTimePtDisplay = oDlg.m_iTimePtDisplay;
// Print msg with variable value.
sMsg.Format(“\n COutputBlock::BlockDlgWndParameterInput(),
m_iNotation = %d\n”, m_iNotation);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
}
#if !defined(AFX_OUTPUTBLOCKDIALOG_H__5208CAE3_D388_416F_8FA9_
F54111FEF27E__INCLUDED_)
#define AFX_OUTPUTBLOCKDIALOG_H__5208CAE3_D388_416F_8FA9_F54111FEF27E__
INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// OutputBlockDialog.h : header file
//
#include “Block.h” // rqd. since a ptr-to-COutputBlock is used in
COutputBlockDialog
/////////////////////////////////////////////////////////////////////////
// COutputBlockDialog dialog
…
508 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The declaration of the constructor in the “OutputBlockDialog.h” header file should also be amended
to reflect the modified constructor definition in the “OutputBlockDialog.cpp” source file as shown
in bold in the following:
/////////////////////////////////////////////////////////////////////////
// COutputBlockDialog dialog
class COutputBlockDialog : public CDialog
{
// Construction
public:
// COutputBlockDialog(CWnd* pParent = NULL); // standard constructor
COutputBlockDialog(COutputBlock *ref_block, CWnd* pParent = NULL);
// modified constructor
…
void COutputBlockDialog::OnOutputBlkDlgBtnShowGraph()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
int display_item = 0; // flag to control msg box display
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Display a msg.
if(display_item == 1)
{
sMsg.Format(“\n COutputBlockDialog::OnOutputBlkDlgBtnShow
Graph()\n\n”);
AfxMessageBox(sMsg, nType, nIDhelp);
}
// -- VIEW CONSTRUCTION
CMainFrame *main_frame = (CMainFrame *) theApp.m_pMainWnd;
// Get a pointer-to-CMainFrame.
CMDIChildWnd *pActiveChild = main_frame->MDIGetActive();
// Get a pointer to the active child window.
if(pActiveChild != 0)
{
CDocument *pDocument = pActiveChild->GetActiveDocument();
// Get a pointer to the active document.
if(pDocument != 0)
{
CDiagramEngApp *pApp = (CDiagramEngApp *) AfxGetApp();
CDocTemplate *pTemp;
CFrameWnd *pFrame;
pTemp = pApp->GetCOutputViewTemplate(); // Get the document
template for the new view.
pFrame = pTemp->CreateNewFrame(pDocument, pActiveChild);
// Create a new frame.
if(pFrame != 0)
{
pTemp->InitialUpdateFrame(pFrame, pDocument);
// Update the frame.
}
// Set the COutputBlockView ref-to-block
COutputBlockView *out_blk_view = (COutputBlockView *)
pFrame->GetActiveView();
out_blk_view->SetRefOutputBlock(m_pOutputBlk);
}
}
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
The net effect of these changes allows the COutputBlock data member, “m_dOutputMatrix”, to
be accessed by the COutputBlockView::OnDraw() function via the pointer-to-COutput-
Block variable, “m_pOutputBlk”, set in a step-by-step manner by the aforementioned functions:
BlockDlgWndParameterInput() of the COutputBlock class, the COutputBlockDialog()
constructor, OnOutputBlkDlgBtnShowGraph() of the COutputBlockDialog class, and
SetRefOutputBlock() of the COutputBlockView class.
FIGURE 20.3 Simple model (left) involving a source constant vector whose data is output as text in the
Output block’s view window (right).
Now the code may be compiled and run, and a simple model involving a Constant and an Output
block may be drawn as shown in Figure 20.3. A source constant vector c = [1, 2]T is generated for
t ∈ [0,5] with δt = 1, and the numerical data of “m_OutputMatrix” is output to the view window.
The developer will notice that the output matrix, “m_dOutputMatrix”, is obtained by calling
GetOutputMatrix() on the pointer-to-COutputBlock, “m_pOutputBlk”, a member variable of
the COutputBlockView class initialized in the OnOutputBlkDlgBtnShowGraph() function of
the COutputBlockDialog class.
Finally, the DrawGraph() function is called to actually draw the signal data stored within the
“m_dOutputMatrix” in the view window; the pointer-to-CDC, window dimensions, and data are
passed as arguments.
and returns zero, denoting an invalid action. If there is valid data (“m_dOutputMatrix” is non-NULL),
the function assigns the first matrix data element to the minimum and maximum values and then
iterates through the matrix to find lower or higher values, updating the “min” and “max” values,
respectively; the integer “1” is then returned, denoting a valid action.
// DRAW CURVES
for(j=0; j<nrows; j++) // for each row of dOutputMatrix
{
for(sig=0; sig<n_signals_per_row; sig++) // for one or more
signals per row
{
// CREATE AND SELECT A NEW PEN
pen_color = SelectGraphCurveColor(color_cnt);
color_cnt++;
// Create a pen
CPen lnew_pen(PS_SOLID, pen_width, pen_color);
{
pDC->MoveTo(data_pt);
}
else
{
pDC->LineTo(data_pt);
}
}
}
}
// X AXIS LABELS
pDoc->GetSystemModel().GetTimeParameters(t_init, t_final, delta_t);
for(i=0; i<=n_vertical_lines; i++)
{
text_pt.x = box_bottom_left.x + i*( (double)box_width/(double)
n_vertical_lines) − 0.1*border;
text_pt.y = box_bottom_left.y + 0.1*border;
t = t_init + ( (double)i/(double)
n_vertical_lines)*(t_final − t_init);
str_label.Format(“ %lf”, t);
pDC->TextOut(text_pt.x, text_pt.y, str_label);
}
text_pt.x = box_bottom_left.x + 0.5*box_width;
text_pt.y = box_bottom_left.y + 0.475*border;
str_label.Format(“ t (s)”);
pDC->TextOut(text_pt.x, text_pt.y, str_label);
// Y AXIS LABELS
for(i=0; i<=n_horiz_lines; i++)
{
text_pt.x = box_bottom_left.x − 0.75*border;
text_pt.y = box_bottom_left.y − i*( (double)box_height/(double)
n_horiz_lines) − 0.075*border;
f = f_min + ( (double)i/(double)n_horiz_lines)*data_range;
str_label.Format(“ %lf”, f);
pDC->TextOut(text_pt.x, text_pt.y, str_label);
}
text_pt.x = box_bottom_left.x − 0.95*border;
text_pt.y = box_bottom_left.y − 0.475*box_height;
str_label.Format(“ f(t)”);
pDC->TextOut(text_pt.x, text_pt.y, str_label);
// Graph title
text_pt.x = box_bottom_left.x + 0.4*box_width;
text_pt.y = box_top_left.y − 0.5*border;
str_label.Format(“ Plot of f(t) vs. t”);
pDC->TextOut(text_pt.x, text_pt.y, str_label);
Initially, a pen is created and selected and then the graph-bounding box rectangular coordinates
are specified and the box drawn. A fixed number of horizontal and vertical lines are drawn evenly
over the box.
Graph Drawing 519
Then the number of independent curves per row of numerical data is determined by dividing
the total number of columns of “m_dOutputMatrix” by the number of time points at which signal
output was generated. For example, if the matrix of output signals (“m_dOutputMatrix”)
where si(tk) are the components of the signal data for i ∈ [1, S] and k ∈ [0, n], where S denotes the
total number of signals (four in this example) and n denotes the final time-point index of the simu-
lation, then the number of signals per row (for this example) is 2(n + 1)/(n + 1) = 2. Note that each
signal submatrix is generated for each time point of the simulation.
A nested for loop is then entered to iterate over the rows of data (“for(j)”), the signals per row
(“for(sig)”), and all time points for the particular signal (“for(i)”). The point on the graph to be
drawn, denoted “data_pt”, has its x and y components specified according to the current time point
across the horizontal time-based domain and the fraction of the signal value of the total data range
scaled within the box height, in the vertical domain, respectively. Curves are drawn in a unique
color using the to-be-added SelectGraphCurveColors() function.
Finally, a call is made to GetTimeParameters() to obtain the initial (t0) and final (tn) simula-
tion times and the time-step size (δt), and then the x and y axes text labels are placed on the graph
beside the vertical and horizontal grid lines, respectively, using the function TextOut().
CString sMsg;
sMsg.Format(“\n OnDraw(): pDoc = %p\n”, pDoc);
AfxMessageBox(sMsg, MB_OK, 0);
CString sMsg;
sMsg.Format(“\n DrawGraph(): pDoc = %p\n”, pDoc);
AfxMessageBox(sMsg, MB_OK, 0);
520 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
int COutputBlockView::SelectGraphCurveColor(int k)
{
int blue; // blue color cmpt.
int green; // green color cmpt.
int pen_color; // red = RGB(255,0,0), green = RGB(0,255,0),
blue = RGB(0,0,255), Black = RGB(0,0,0), White = (255,255,255)
int random_no; // integer random number generated
int red; // red color cmpt.
double uniformly_rand; // uniformly random number
random_no = rand();
uniformly_rand = double(random_no)/double(RAND_MAX);
green = int(uniformly_rand*255);
random_no = rand();
uniformly_rand = double(random_no)/double(RAND_MAX);
blue = int(uniformly_rand*255);
break;
}// end switch
return pen_color;
}
The switch statement is executed, where initially k = 0 and the random number generator is seeded
in the event that more than 10 curve colors are required. The actual random number is generated
using rand() and scaled uniformly to select a value between 0 and 255 in order to yield an appro-
priate pen color value (RGB(red, green, blue) ). The first 10 colors are specifically set to be
contrasting in nature.
The CSystemModel time-based parameters—the initial time, t0; final time, tn; and time-step size,
δt—are simply returned by reference.
Now upon running the DiagramEng application both the system model and its associated output
view windows may be generated and viewed simultaneously (Figure 20.4): a sine curve signal is
generated and three different gain constants are applied, resulting in three different output plots.
In addition, unique graph curve colors are used to clearly differentiate between the curves in a view:
this is visible when running the code but different shades of gray are visible in Figure 20.5.
522 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 20.4 System model with three Output blocks and their associated views.
FIGURE 20.5 Simple system model with a gain vector applied to a source signal resulting in five curves
of uniquely different colors (visible when running the code) or shades of gray (as displayed here in the text).
Include the “OutputBlockView.h” header file at the top of the “DiagramEngDoc.cpp” source
file as shown in bold in the following since a pointer-to-COutputBlockView is used in
DeleteOutputBlockView().
// DiagramEngDoc.cpp : implementation of the CDiagramEngDoc class
//
#include “stdafx.h”
#include “DiagramEng.h”
#include “DiagramEngDoc.h”
// DiagramEng (start)
#include <math.h> // rqd. since OnDeleteItem() uses math fns.
#include “afxext.h” // rqd. since TrackItem() uses a
CRectTracker obj.
#include “BlockLibDlg.h” // rqd. since OnEditAddMultipleBlocks() has
a CBlockLibDlg obj.
//#include “TreeDialog.h” // rqd. since OnEditAddMultipleBlocks() has
a CTreeDialog obj.
#include “OutputBlockView.h” // rqd. since DeleteOutputBlockView()
declares a ptr-to-COutputBlockView
// DiagramEng (end)
…
CDiagramEngDoc::DeleteBlock() or CDiagramEngDoc::OnEditDeleteGrouped
Items(). The pointer-to-CBlock is passed into the function and used to identify the correct
block-related view to be deleted.
The call to GetFirstViewPosition() gets the first view in the list of views for the current
document, i.e., for the instance of CDiagramEngDoc. GetNextView() is used to iterate through
the list of views. The first conditional (if) statement checks to verify that the pointer-to-CView is
not NULL and that it is a kind of COutputBlockView object. The second conditional (if) statement
checks that the address of the block (“m_pOutputBlk”) to which the view is associated is the same
as that of the block, “ptr_blk”, to be deleted, i.e., the correct Output block’s view window will be
destroyed. If so, the pointer-to-COutputBlock, “m_pOutputBlk”, of the COutputBlockView class
is set to NULL through a call to SetRefOutputBlock(NULL), preventing attempted access to
nonexistent data, and the associated view window is destroyed using a call to DestroyWindow().
int CDiagramEngDoc::DeleteBlock()
{
int count = 0; // counter
int delete_blk = 0; // blk deletion flag
double dist; // Euclidean dist bw. block posn and point posn.
double width; // block width
CPoint blk_posn; // block posn.
CPoint point; // local point var.
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CBlock*>::iterator it_blk; // iterator
list<CBlock*>::iterator it_er = NULL; // element to erase
// Get the point at which the context menu was invoked
point = m_ptContextMenu; // init a local copy.
// Print msg.
//sMsg.Format(“\n CDiagramEngDoc::DeleteBlock(), point
(x,y) = (%d,%d)\n”, point.x, point.y);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Get a copy of the blk_list in the system model
list<CBlock*> &blk_list = GetSystemModel().GetBlockList();
// MUST BE A REFERENCE!
// Iterate through the list to find which item to delete.
for(it_blk = blk_list.begin(); it_blk != blk_list.end(); it_blk++)
{
blk_posn = (*it_blk)->GetBlockPosition();
width = (*it_blk)->GetBlockShape().GetBlockWidth();
dist = sqrt(pow( (blk_posn.x − point.x),2) +
pow( (blk_posn.y − point.y),2) );
if(dist <= 0.5*width*0.5)
Graph Drawing 525
{
//sMsg.Format(“\n CDiagramEngDoc::DeleteBlock(),
blk(x,y) = (%d,%d), point(x,y) = (%d,%d)\n”, blk_posn.x,
blk_posn.y, point.x, point.y);
//AfxMessageBox(sMsg, nType, nIDhelp);
// Record which block to erase
delete_blk = 1;
it_er = it_blk;
break;
}
}
// Delete the item in the list
if(delete_blk == 1)
{
// Dereference block’s ports (allowing previously connected
connections to be reassigned to new ports)
DisconnectEndPointsFromPorts(*it_er);
// Delete Output Block View
DeleteOutputBlockView(*it_er);
// Delete block
delete *it_er; // delete actual block pointed to by it_er
blk_list.erase(it_er); // delete element at offset it_er in list
(that held the block)
count = m_SystemModel.GetBlockList().size();
//sMsg.Format(“\n CDiagramEngDoc::DeleteBlock(), size = %d\n”,
count);
//AfxMessageBox(sMsg, nType, nIDhelp);
}
// Set as modified and redraw the doc.
SetModifiedFlag(TRUE); // set the doc. as having been modified to
prompt user to save
UpdateAllViews(NULL); // indicate that sys. should redraw.
// Return a flag indicating whether an item was deleted
return delete_blk;
}
void CDiagramEngDoc::OnEditDeleteGroupedItems()
{
// TODO: Add your command handler code here
int intersected = 0;
int intersected_head = 0;
int intersected_tail = 0;
double blk_width;
double delta = 0.25*m_dDeltaLength;
526 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
CPoint bend_pt;
CPoint blk_posn;
CPoint head_pt;
CPoint tail_pt;
CRectTracker temp_tracker;
list<CBlock*> &blk_list = GetSystemModel().GetBlockList();
list<CBlock*>::iterator it_blk;
list<CConnection*>::iterator it_con;
list<CConnection*> &con_list = GetSystemModel().GetConnectionList();
list<CPoint>::iterator it_pt;
// -- IF A RUBBER BAND HAS BEEN CREATED
if(m_iRubberBandCreated == 1)
{
// Create a temp tracker since m_RectTracker will have its coords
updated when the rubber band is moved.
temp_tracker = m_RectTracker;
// -- ITERATE THROUGH BLOCKS
it_blk = blk_list.begin();
while(it_blk != blk_list.end() )
{
// Get block properties
blk_posn = (*it_blk)->GetBlockPosition();
blk_width = (*it_blk)->GetBlockShape().GetBlockWidth();
// Determine if item lies within rubber band
intersected = DetermineCurrentAndIntersectRects
(temp_tracker, blk_posn, (blk_width*0.5) );
if(intersected)
{
// Delete Output Block View
DeleteOutputBlockView(* it_blk);
// Delete block
delete *it_blk; // delete actual block pointed to by
it_blk
it_blk = blk_list.erase(it_blk); // delete element at
offset it_blk in list (that held the block)
}
else // only increment the iterator if there were no
intersection
{
it_blk++;
}
}// end for it_blk
…
}// end if m_iRubberBandCreated
}
// DRAW CURVES
time_pt_display = m_pOutputBlk->GetTimePtDisplay(); // get time
point display value
for(j=0; j<nrows; j++) // for each row of dOutputMatrix
{
for(sig=0; sig<n_signals_per_row; sig++) // for one or more
signals per row
528 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
…
// FOR EACH DATA/TIME POINT OF A SIGNAL
for(i=0; i<n_data_pts; i++)
{
…
if(time_pt_display == 0)
{
pDC->Ellipse(dot_top_left.x, dot_top_left.y,
dot_bottom_right.x, dot_bottom_right.y);
}
…
}
}
}
// X AXIS LABELS
notation = m_pOutputBlk->GetNotation(); // get notation type
pDoc->GetSystemModel().GetTimeParameters(t_init, t_final, delta_t);
// get time parameters
if(notation == 0)
{
str_label.Format(“ %lf”, t);
}
else
{
str_label.Format(“ %-5.3e”, t);
}
pDC->SetTextColor(RGB(0,0,255) );
pDC->TextOut(text_pt.x, text_pt.y, str_label);
}
text_pt.x = box_bottom_left.x + 0.5*box_width;
text_pt.y = box_bottom_left.y + 0.475*border;
str_label.Format(“ t (s)”);
pDC->SetTextColor(RGB(0,0,0) );
pDC->TextOut(text_pt.x, text_pt.y, str_label);
// Y AXIS LABELS
for(i=0; i<=n_horiz_lines; i++)
{
text_pt.x = box_bottom_left.x − 0.85*border;
text_pt.y = box_bottom_left.y − i*( (double)box_height/(double)
n_horiz_lines) − 0.075*border;
f = f_min + ( (double)i/(double)n_horiz_lines)*data_range;
if(notation == 0)
{
str_label.Format(“ %lf”, f);
}
Graph Drawing 529
else
{
str_label.Format(“ %5.3e”, f);
}
pDC->SetTextColor(RGB(0,0,255) );
pDC->TextOut(text_pt.x, text_pt.y, str_label);
}
text_pt.x = box_bottom_left.x − 0.95*border;
text_pt.y = box_bottom_left.y − 0.465*box_height;
str_label.Format(“ f(t)”);
pDC->SetTextColor(RGB(0,0,0) );
pDC->TextOut(text_pt.x, text_pt.y, str_label);
// Graph title
text_pt.x = box_bottom_left.x + 0.4*box_width;
text_pt.y = box_top_left.y − 0.5*border;
str_label.Format(“ Plot of f(t) vs. t”);
pDC->TextOut(text_pt.x, text_pt.y, str_label);
void COutputBlock::BlockDlgWndParameterInput()
{
CString sMsg; // string to be displayed
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Set the dialog class vars using the block class vars
oDlg.m_iNotation = m_iNotation;
oDlg.m_iTimePtDisplay = m_iTimePtDisplay;
// Update the view, since changing the notation type changes the
numbers on the graph.
CDiagramEngDoc *pDoc = GetDocumentGlobalFn();
pDoc->SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
pDoc->UpdateAllViews(NULL);
}
}
As a result, both standard and scientific notation may be used to display the numerical x and y axes
values, and the user may switch between showing and hiding the time points, denoted as small circles,
on the graph. For example, consider Figure 20.6 that shows the solution y(t) of the differential equation
1.411600
1.254755
1.097911
0.941066
f (t)
0.784222
0.627378
0.470533
0.313689
0.156844
0.000000
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(a) t (s)
1.412e+000
1.255e+000
1.098e+000
9.411e–001
f (t)
7.842e–001
6.274e–001
4.705e–001
3.137e–001
1.568e–001
0.000e+000
0.000e+000 2.000e+000 4.000e+000 6.000e+000 8.000e+000 1.000e+001 1.200e+001 1.400e+001 1.600e+001 1.800e+001 2.000e+001
(b) t (s)
FIGURE 20.6 (a) Solution to (20.2) showing standard textual notation with circular time points. (b) Solution
to (20.2) showing scientific textual notation without circular time points.
Graph Drawing 531
of an underdamped mass–spring–damper system, where m, b, k, y(t), and u(t) are the mass, damp-
ing constant, spring constant, output mass displacement from the equilibrium position, and external
force input to the system (this will be treated in detail in a later chapter): Figure 20.6a shows the
standard notation and circular time point display, and Figure 20.6b shows scientific notation without
the circular time point display.
20.8 SUMMARY
To draw a graph of the Output block’s data, six incremental steps were taken: (1) adding general
structure to display an empty view window, (2) supplementing existing classes with methods and
variables to access the output block’s data, (3) plotting data as a text string in the view window,
(4) plotting numerical data as graph curves in the view window, (5) deleting an Output block and its
related view window, and (6) adding radio button functionality to the Output block’s dialog window.
The first step, the addition of structure to display an empty view window, involved adding a
CDocTemplate object to the application class, modifying the InitInstance() function to
create a CMultiDocTemplate object, adding a new COutputBlockView class to support drawing
within its view, adding template-based functionality to the Show Graph function, and adding the
OnInitialUpdate() and OnUpdate() functions to the CDiagramEngView class.
The second step, supplementing existing classes to access Output block data, concerned addition
of a pointer-to-COutputBlock variable in both the COutputBlockView and COutputBlockDialog
classes, the construction of a COutputBlockDialog object using the “this” pointer to initial-
ize the block-pointer variable, and the addition of code to the Show Graph function to set the
COutputBlockView’s block-pointer variable.
The third step simply showed that the numerical data, “m_dOutputMatrix”, could be
written as text on the Output block’s view window through the OnDraw() function of the
COutputBlockView class.
The fourth step showed how the numerical data could be drawn as graph curves on the view
window and involved modifying the OnDraw() function, determining maximum and mini-
mum data values, and adding the DrawGraph(), SelectGraphCurveColor(), and
GetTimeParameters() functions to complete the drawing process. In addition, the dif-
ference in the usage of the GetDocument() function of the COutputBlockView class and the
GetDocumentGlobalFn() that retrieves a pointer to the active document was explained.
The fifth step involved a DeleteOutputBlockView() function that resets the pointer-
to-COutputBlock variable and closes the Output block view window prior to block deletion in the
DeleteBlock() and OnEditDeleteGroupedItems() functions of the CDiagramEngDoc class.
Finally, the sixth step completed the graph-drawing process by indicating how functionality can
be added for the radio buttons of the OutputBlockDialog dialog window to display numerical output
with either default or scientific notation.
REFERENCES
1. Mahmood, A., Multiple document interface applications, serialization and multiple
view support, Internal Article, CS440 MDI2, University of Bridgeport, Bridgeport, CT,
http://www1bpt.bridgeport.edu/sed/fcourses/cs440/Lectures/ (accessed September 3, 2009).
2. MSDN, Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0
Development System, Microsoft Corporation, 1998.
21 Block Operations
21.1 INTRODUCTION
Chapter 19 introduced the idea of signal propagation and block-based data operation using
a SignalPropagationDirect() function in the CSystemModel class and a virtual
OperateOnData() function in the CBlock class, respectively. The CConstantBlock and
COutputBlock versions of OperateOnData() were shown to work in a simple example involv-
ing a Constant and an Output block connected by a single connection object containing CSignal
data. Here, various overriding OperateOnData() functions are added, including any subsid-
iary functions to allow them to work, to some of the remaining CBlock-derived classes, in par-
ticular, CDerivativeBlock, CDivideBlock, CGainBlock, CIntegratorBlock, CLinearFnBlock,
CSignalGeneratorBlock, and CSumBlock. The implementation of the data operation functions of
the subsystem-related and Transfer Function blocks will be postponed till later.
533
534 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 21.1 Linear Function block connected to an Output block in direct signal propagation.
4
f (t)
0
0 1 2 3 4 5 6 7 8 9 10
(a) (b) t (s)
FIGURE 21.2 Linear Function block. (a) Parameter input dialog window showing the parameters, ta0 = 2.0 s,
taf = 8.0 s, f(ta0) = 0, and f′(t) = 1 (with model simulation parameters t0 = 0.0 s, tf = 10.0 s, and δt = 1.0 s), and
(b) corresponding graph.
// -- MEMORY NEW
Y_matrix = new double *[nrows]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows; i++)
{
Y_matrix[i] = new double[ncols]; // allocate an array of doubles
}
// -- MEMORY DELETE
for(i=0; i<nrows; i++)
{
delete [] Y_matrix[i]; // delete the row arrays
}
delete [] Y_matrix; // delete the array (column vector)
holding the row arrays
The OperateOnData() function of the CLinearFnBlock class simply generates a scalar linear
function value for each time (“t_cnt”) it is called. Initially, memory is allocated as a two-dimensional
array, since a matrix data structure is used in the call to WriteDataToOutputSignal().
Then, based upon the value of the time variable t, before the initial time point of signal activation
(t0 ≤ t < ta0), during the time domain of signal activation (ta0 ≤ t ≤ taf ), or after the final time point
of signal activation (taf < t ≤ tf ), the correct image value f(t) is generated and written to the output
connection’s CSignal data member.
FIGURE 21.3 Simple model involving a Signal Generator and an Output block to test generated signals.
536 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
0.5
f (t)
0
0
0.44
0.88
1.32
1.76
2.2
2.64
3.08
3.52
3.96
4.4
4.84
5.28
5.72
6.16
–0.5
–1
–1.5
(a) (b) t (s)
FIGURE 21.4 Signal Generator block. (a) Parameter input dialog window and (b) sine and square wave
functions f(t) vs. t.
Add a public virtual member function to the CSignalGeneratorBlock class with the prototype:
virtual int CSignalGeneratorBlock::OperateOnData(int t_cnt, double t,
double delta_t). Edit the function as shown in the following to generate one of three sca-
lar function values per time-step and to write it to its output connection’s CSignal data member.
// -- MEMORY NEW
Y_matrix = new double *[nrows]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows; i++)
{
Y_matrix[i] = new double[ncols]; // allocate an array of
doubles
}
Block Operations 537
// -- GENERATE SIGNAL
if(m_strFnType == “Random”) // uniformly random number [0,1]
{
if(rand_flag == 0)
{
srand( (unsigned)time(NULL) ); // seed the random number
generator with time value to get truly random numbers
rand_flag++; // seed once per simulation
}
random_no = rand();
uniformly_rand = double(random_no)/double(RAND_MAX);
y = uniformly_rand;
}
else if(m_strFnType == “Sine”) // sine wave
{
y = A*sin(omega*t + phase);
}
else if(m_strFnType == “Square”) // square wave based on sine wave
{
y = A*sin(omega*t + phase); // use the sin wave to generate
the square wave
if(y > 0)
{
y = 1;
}
else if(y == 0)
{
y = 0;
}
else if(y < 0)
{
y = −1;
}
}
// -- MEMORY DELETE
for(i=0; i<nrows; i++)
{
delete [] Y_matrix[i]; // delete the row arrays
}
delete [] Y_matrix; // delete the array (column vector)
holding the row arrays
The OperateOnData() function for the Signal Generator block is separated into three condi-
tional sections to generate the random, sine, or square wave form. The random number generator is
538 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
seeded once on the first call, and random numbers are generated and scaled to the unit interval to
be uniformly random. The sine wave is of the usual form, f(t) = Asin(ωt + ϕ), and the square wave
oscillates between +1 and −1 as shown.
FIGURE 21.5 Simple direct signal propagation model involving a Constant, Gain, and an Output block.
FIGURE 21.6 Gain block parameter input dialog window showing the gain (scalar, vector, or matrix) value
and the type of gain: Elemental, Gain*Input, and Input*Gain.
Block Operations 539
Edit the function as shown in the following to perform the three forms of input-gain operations writ-
ing the result to the output connection’s CSignal data member.
// -- MEMORY NEW
matrix_out = new double *[nrows_in]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows_in; i++)
{
matrix_out[i] = new double[ncols_in]; // allocate an array of
doubles
}
nrows_del = nrows_in;
// OPERATE ON DATA
for(i=0; i<nrows_in; i++)
{
for(j=0; j<ncols_in; j++)
{
matrix_out[i][j] = k*matrix_in[i][j];
}
}
540 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// -- MEMORY NEW
matrix_out = new double *[m_iNrows]; // allocate an array
of ptrs-to-double
for(i=0; i<m_iNrows; i++)
{
matrix_out[i] = new double[m_iNcols]; // allocate an array
of doubles
}
nrows_del = m_iNrows;
// OPERATE ON DATA
for(i=0; i<m_iNrows; i++)
{
for(j=0; j<m_iNcols; j++)
{
matrix_out[i][j] = c*m_dGainMatrix[i][j];
}
}
// ELEMENTAL OPERATION
for(i=0; i<m_iNrows; i++)
{
for(j=0; j<m_iNcols; j++)
{
matrix_out[i][j] = m_dGainMatrix[i]
[j]*matrix_in[i][j];
}
}
Block Operations 541
}
else
{
valid = −1; // operational error
}
}
else if(m_iGainType == 2) // Input*Gain OPERATION
{
// Check dimensions
if(ncols_in == m_iNrows)
{
// -- MEMORY NEW
matrix_out = new double *[nrows_in]; // allocate an array
of ptrs-to-double
for(i=0;i<nrows_in;i++)
{
matrix_out[i] = new double[m_iNcols]; // allocate an array
of doubles
}
nrows_del = nrows_in;
// Matrix mult: Input*Gain = Output
MatrixMultiply(nrows_in, ncols_in, m_iNcols, matrix_in,
m_dGainMatrix, matrix_out);
// -- MEMORY DELETE
if(matrix_out != NULL) // only delete memory if it
was in fact allocated
{
for(i=0; i<nrows_del; i++)
{
delete [] matrix_out[i]; // delete the row arrays
}
delete [] matrix_out; // delete the array (column
vector) holding the row arrays
}
return valid;
}
The CGainBlock’s OperateOnData() function is separated into three main logical condi-
tional sections: (1) the first concerns whether the gain value is a scalar, (2) the second consid-
ers whether the input value is a scalar, and (3) the third performs a nonscalar, i.e., matrix or
vector, gain operation. The third section is itself decomposed into Elemental, Gain*Input, and
Input*Gain operations: (1) the Elemental operation multiplies the elements of the gain matrix by
the elements of the input matrix taking their dimensions into consideration, (2) the Gain*Input
operation checks for consistent inner matrix dimensions and then performs matrix multiplica-
tion of the two data types, and (3) the Input*Gain operation is the reverse order of the previous,
checking for correct inner dimensions and performing matrix multiplication. All forms of gain
operation then write the output data to the output connection’s CSignal data member using the
call WriteDataToOutputSignal().
The function returns a “valid” integer argument that may have three values: “−1” is returned if
an attempt is made to perform a dimensionally inconsistent matrix operation, “0” is returned if the
data cannot be read from an input signal or written to the output connection’s CSignal object, and
“1” is returned if the operation is successful and the output data are correctly written to the output
connection’s CSignal object. Prior to returning, the output matrix memory that was created to store
the result of the gain operation is deleted.
The developer will notice that matrix multiplication is performed by the function
MatrixMultiply(). Hence, add a new global function prototype, for the MatrixMultiply()
function, to the “DiagramEngDoc.h” header file, where the existing global function prototypes are
located, as shown in the following code excerpt : void MatrixMultiply(int nrowsA, int
ncolsA, int ncolsB, double **A, double **B, double **C).
// Global Functions
CDiagramEngDoc* GetDocumentGlobalFn(void);
double **ConvertStringToDouble(CString string, int &nrows, int &ncols);
char *StripInputString(char *input_str);
int DetermineDataDimsByStrpbrk(char *in_str, int &nrows, int &ncols, int
&max_row_length);
int DetermineDataDimsByStrtok(char *in_str, int &nrows, int &ncols, int
&max_row_length);
double **ConvertStringMatrixToDoubleMatrix(char **str_matrix, int nrows,
int ncols);
void MatrixMultiply(int nrowsA, int ncolsA, int ncolsB, double **A,
double **B, double **C);
int PrintMatrix(double **M, int nrows, int ncols);
int PrintMatrix(int **M, int nrows, int ncols);
int PrintVector(double *v, int ncols);
int PrintVector(int *v, int ncols);
544 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Now add the MatrixMultiply() function to the “DiagramEngDoc.cpp” source file, beneath the
matrix and vector printing functions, and edit the code as shown in the following.
// Matrix multiplication
// A: nrowsA x ncolsA
// B: nrowsB x ncolsB
// C: nrowsA x ncolsB
// C = A*B
// Nullify C
for(i=0; i<nrowsA; i++)
{
for(j=0; j<ncolsB; j++)
{
C[i][j] = 0.0;
}
}
// Matrix multiplication
for(k=0; k<ncolsB; k++)
{
for(i=0; i<nrowsA; i++)
{
for(j=0; j<ncolsA; j++)
{
C[i][k] = C[i][k] + A[i][j]*B[j][k];
//printf(“ C[%d][%d] = %lf\n”, i, k, C[i][k]);
}
}
}
return;
}
The MatrixMultiply() function simply multiplies input matrices A and B to generate the
result C = AB. A simple example model involving a Constant, Gain, and Output block is shown in
⎡1 2 ⎤
Figure 21.5; the user may enter a constant value C = ⎢ ⎥ and a gain value k = [1, 2] and choose
T
⎣3 4 ⎦
the Input*Gain, gain type, to test that the output y = Ck = [5, 11]T.
FIGURE 21.7 Scalar summation operation involving a Signal Generator block and a Constant block.
FIGURE 21.8 Sum block parameter input dialog window specifying the number of addition and subtraction
inputs.
Add a public virtual member function to the CSumBlock class with the prototype:
virtual int CSumBlock::OperateOnData(int t_cnt, double t, double
delta_t). Edit the function as shown in the following to perform the addition and subtraction
operations writing the result to the output connection’s CSignal data member.
return valid;
}
}
}
}
}// end it_con
}// end it_port
// -- MEMORY NEW
matrix_out = new double *[nrows]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows; i++)
{
matrix_out[i] = new double[ncols]; // allocate an array
of doubles
}
nrows_del = nrows;
// MATRIX OPERATION
if(port_name == “+”)
{
for(i=0; i<nrows; i++)
{
for(j=0; j<ncols; j++)
{
matrix_out[i][j] = matrix_out[i][j]
+ matrix_in[i][j];
}
}
}
else if(port_name == “-”)
548 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
for(i=0; i<nrows; i++)
{
for(j=0; j<ncols; j++)
{
matrix_out[i][j] = matrix_out[i][j]
− matrix_in[i][j];
}
}
}
}
}// end it_con
}// end it_port
// Error msg.
if(valid == −1)
{
sMsgTemp.Format(“\n CSumBlock::OperateOnData()\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n Error in data dimensionality. \n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
}
// -- MEMORY DELETE
for(i=0; i<nrows_del; i++)
{
delete [] matrix_out[i]; // delete the row arrays
}
delete [] matrix_out; // delete the array (column vector)
holding the row arrays
return valid;
}
The OperateOnData() function of the CSumBlock is divided into two main sec-
tions. The first iterates over all the Sum block ports and determines whether the signal
data dimensions of the connections attached to the ports are consistent through a call to
GetSignalDataDimensions(); if the dimensions are inconsistent, an error message is
printed and the function returns (valid = −1), and if the dimensions are consistent, then control
flow progresses to the Sum operation section.
The Sum block iterates over all its ports, determines whether a connection is attached to the current
port, and obtains the CSignal data and type of operation through the calls to GetSignalData()
and GetSignalName(), respectively. Then a running sum is made by updating the out-
put matrix with the new data, where the port sign, “port_name”, indicates either the addition or
subtraction operation. Finally, the output data are written to the output signal through a call to
WriteDataToOutputSignal().
The developer will have noticed that the signal dimensions are obtained through a call to
GetSignalDataDimensions(). Hence, add a public member function to the CSignal class
with the prototype: void CSignal::GetSignalDataDimensions(int *nrows, int
*ncols). Edit the function as shown in the following to simply write the value of the CSignal
Block Operations 549
class member variables, “m_iNrows” and “m_iNcols”, into the incoming arguments, “nrows” and
“ncols”, respectively.
void CSignal::GetSignalDataDimensions(int *nrows, int *ncols)
{
// Addresses of nrows and ncols are passed, hence deref. with a ptr.
return;
}
f1 − f0
f (t*) = f0 + (t * − t 0 ) (21.2)
t1 − t0
may be used, where t* is the time point at which f(t) is to be extrapolated and fi = f(ti) are values of
the function at time points ti for i = 0, 1 (in this case). If t* is chosen to be t1 + h and t1 = t0 + h, hence
t0 = t1 − h, and these quantities substituted into (21.2), then
f (t1 + h) = 2 f1 − f0
Now, from the definition of the three-point derivative (21.1), using t = t1, one obtains
1
f ʹ(t1 ) ≈ (2 f (t1 ) − f (t1 − h) − f (t1 − h))
2h
f (t1 ) − f (t0 )
= (21.5)
h
550 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
That is, the formula for the derivative f ′(t1) at the current time point t1 now involves two known quan-
tities: the current and previous values of the functions, f(t1) and f(t0), respectively, or more generally,
f (t ) − f (t − h )
f ʹ (t ) ≈ (21.6)
h
Hence, this form of the derivative should be used in place of the original three-point derivative (21.1)
during the simulation for t ≥ t1.
However, as shown earlier, to compute this derivative, knowledge of two future values, f(t + h)
and f(t + 2h), is required. A linear extrapolation may again be used to transform the expression of
the derivative to involve only known values at the current time point t. Let t2 = t, this implies that
t0 = t − 2h, t1 = t − h, t3 = t + h, and t4 = t + 2h. Hence, to determine f ′(t2), an estimation through
extrapolation of f(t) is required to obtain f(t3) = f(t + h) and f(t4) = f(t + 2h). Now, using the two-point
form of the equation of a line (21.2),
f (t2 ) − f (t1 )
f (t3 ) = f (t1 ) + (t3 − t1 )
t2 − t1
= 2 f (t2 ) − f (t1 ) (21.8)
and
f (t 3 ) − f (t 2 )
f (t 4 ) = f (t 2 ) + (t 4 − t 2 )
t3 − t 2
= 2 f (t 3 ) − f (t 2 ) (21.9)
Now, the five-point derivative (21.7) may be used to determine f ′(t2) as follows:
1
f ʹ(t 2 ) ≈ ( − f (t 4 ) + 8 f (t3 ) − 8 f (t1 ) + f (t0 )) (21.10)
12h
and on substitution of the expressions for f(t3) and f(t4) given by (21.8) and (21.9), respectively, one
obtains
That is, using extrapolation, the derivative f ′(t2) at the current time point involves known function
values: f(t2), f(t1), and f(t0) at the current and previous time points, or more generally,
(13 f (t ) − 14 f (t − h) + f (t − 2h))
f ʹ (t ) ≈ (21.12)
12h
Block Operations 551
Hence, this form of the derivative should be used in place of the five-point derivative (21.7) during
the simulation for t ≥ t2.
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n WARNING: error in computing the numerical
derivative!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Invalid input signal!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Input signal should be a scalar function, or a
vector of scalar functions.\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ That is: signal = f(t), or signal = [f1(t),
f2(t), …, fm(t)].\n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
valid = −1; // valid = −1 implies dimensionality error
return valid; // Exit function since input not a
scalar fn of a scalar var, or a vector of scalar fns of
scalar vars.
}
else
{
m_iNrows = nrows_in; // record the no. of rows for memory
deletion
}
// SET TIME COUNT REF AND NO. OF TIMES THE FN IS CALLED AT THIS TIME
POINT
if(m_i_t_cnt_ref == t_cnt)
{
m_iNcallsPerTimeCnt++; // no. of calls per time-based iteration
(t_cnt)
}
else
{
m_i_t_cnt_ref = t_cnt;
m_iNcallsPerTimeCnt = 1;
}
// -- THREE(3)-POINT DERIVATIVE
if( (m_iDerivativeMethod == 0) && (t_cnt == 0) ) // At time t_cnt = 0
{
// No derivative can be determined
for(i=0; i<nrows_in; i++)
{
for(j=0; j<ncols_in; j++)
{
m_dMatrixOut[i][j] = 0.0;
}
}
// -- FIVE(5)-POINT DERIVATIVE
if( (m_iDerivativeMethod == 1) && (t_cnt < 2) ) // At less than
2 calls to the fn
{
// No derivative can be determined
for(i=0; i<nrows_in; i++)
{
for(j=0; j<ncols_in; j++)
{
m_dMatrixOut[i][j] = 0.0;
}
}
// MEMORY DELETE
delete [] f_at_t; // delete the incoming signal vector f(t)
return valid;
}
The Derivative block’s OperateOnData() function initially reads the single input signal
“matrix_in” from the input connection and determines its size: “nrows_in” by “ncols_in”. This signal
is in fact f(t), a row or column vector (at this stage of the development) containing scalar signals fi(t),
for i ∈ [1, n], where n is the number of signal components “nsignals”. The input matrix, “matrix_in”,
is later assigned to the variable “f_at_t” representing f(t). If the input matrix is of the right structure,
i.e., if it is a row or a column vector (a scalar signal can be considered to be a one-element vector), then
the member variable “m_iNrows”, denoting the number of rows of the output matrix, is initialized;
this size is required for memory deallocation purposes in ResetBlockData() (to be introduced).
After the data is checked that it conforms to a vector structure, a test is made to determine the
number of times the OperateOnData() function is called for the current time point, “t_cnt”. If
the reference-like member variable, “m_i_t_cnt_ref”, is equal to the current time point, “t_cnt”,
then the variable “m_iNcallsPerTimeCnt” is incremented; if not, then the time-count reference
is updated and the variable recording the number of calls is reset to “1.” This test is necessary
since the Derivative block has memory, in that it records functional values at previous time points,
i.e., f(t − h) and f(t − 2h); the output f′(t) is a function of these previous values. If the block were
called more than once (in between time-steps), as it will be in Chapter 23, and the input signal,
f(t), changed, then the internal memory of the block, i.e., the values f(t − h) and f(t − 2h), would
not be those at previous time points, but rather at previous intermediary function-call occasions.
556 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Hence, if the function OperateOnData() is called more than once per time-step, then the output to
be generated, f′(t) (recorded in “m_dMatrixOut”), is that already determined by the initial call to
the function and the function returns.
However, if the call to OperateOnData() is the initial call at the current time point “t_cnt”,
then memory is allocated for the local variable “f_at_t” ( f(t)) and for the class member variables
“m_f_at_t_minus_h” ( f(t − h)), “m_f_at_t_minus_2h” f(t − 2h)), and “m_dMatrixOut”, which
will ultimately hold f′(t).
The three-point derivative section of the function initially records a zero value for f′(t0)
since a value for the derivative is only available for t ≥ t1 as described in the discussion earlier.
At t = t0, f(t0) is the incoming signal vector as expected, but f(t − h) is initialized to f(t0), i.e.,
f(t − h) ← f(t0) (where ← denotes programmatic assignment), in preparation for the next time-
step, when t = t1, at which stage f(t − h) = f(t1 − h) = f(t0), the current signal. For t ≥ t1, the
CalculateThreePointDerivative() function is called passing in the values f i(t − h), f i(t),
and Δt to yield f i ′(t), given by expression (21.6), and these components form f′(t), which is written
to the output matrix, “m_dMatrixOut”, a vector of output signals in matrix form. Again, in prepa-
ration for the next time-step, f(t − h) is updated with f(t), i.e., f(t − h) ← f(t). See Figure 21.9 that
shows the internal updating of the block’s data structures at the first few time-steps (this concerns
the five-point derivative calculation, but the same concept applies for the three-point derivative).
The five-point derivative section functions in a manner similar to the three-point derivative
section, in that, at t = t0, f(t − 2h) is initialized to be the incoming signal, i.e., f(t − 2h) ← f(t0), and
at t = t1, f(t − h) is similarly initialized, i.e., f(t − h) ← f(t1), such that, at t = t2, the first derivative
evaluation can occur through a call to CalculateFivePointDerivative() to yield the ith
component fi′(t) given by (21.12) of f′(t). Again, in preparation for the next time-step, f(t − 2h) and
f(t − h) are updated with f(t − h) and f(t) of the current time-step, respectively, i.e., f(t − 2h) ← f(t − h)
and f(t − h) ← f(t). In this regard, the Derivative block has time-based memory of the previous two
input signals (at the previous two distinct time points). Figure 21.9 shows the updating of the internal
data structures for the five-point derivative method.
Finally, after f′(t) is written to the output signal using “m_dMatrixOut”, memory allocated for
the local variable, “f_at_t”, representing f(t), is deallocated.
f (t0) f ΄(t0) = 0
At t0: d/dt
f (t ‒ 2h) f (t0)
f (t ‒ h) /
(a)
f (t1) f ΄(t1) = 0
At t1: d/dt
f (t ‒ 2h) = f (t0)
f (t ‒ h) f (t1)
(b)
f (t2) f ΄(t2)
At t2: d/dt
f (t ‒ 2h) f (t ‒ h) = f (t1)
(c) f (t ‒ h) f (t2)
FIGURE 21.9 Numerical five-point derivative operation: (a) At t0, both f(t − 2h) and f(t − h) are unknown;
hence, f′(t0) = 0. (b) At t1, f(t − 2h) = f(t0) but f(t − h) is unknown; hence, f′(t1) = 0. (c) At t2, f(t − 2h) = f(t0),
f(t − h) = f(t1), and hence, f′(t2) is known.
Block Operations 557
double CDerivativeBlock::CalculateThreePointDerivative(double
m_f_at_t_minus_h, double f_at_t, double delta_t)
{
// -- NUMERICAL 3-POINT DERIVATIVE
// The 3-pt derivative of a fn f(t) is
// f’(t) = (f(t+h) − f(t−h) )/2h
//
// However, f(t+h) = f(t + delta_t) is not available at time t, but
may be obtained by linear extrapolation.
//
// Linear extrapolation: f(tk) = f0 + ( (f1 − f0)/(t1 − t0) )*(tk − t0)
//
// So, letting tk = t + h, f0 = f(t−h) and f1 = f(t), the
extrapolated value f(tk) is
// f(t+h) = f(t−h) + (f(t) − f(t−h) )/(t − (t−h) )*(t+h −(t−h) )
// = f(t−h) + 2(f(t) − f(t−h) )
// = 2f(t) − f(t−h)
//
// Now upon substituting the expression for f(t+h) into the
definition of the derivative, one finds
// f’(t) = (f(t+h) − f(t−h) )/2h
// = (2f(t) − f(t−h) − f(t−h) )/2h
// = (f(t) − f(t−h) )/h
//
// Hence, if linear extrapolation is used to determine f(t+h) in
order to determine f’(t), then the formula
// for f’(t) reduces to a fn involving known values, f(t) and f(t−h),
at the current time point t.
// 3-POINT DERIVATIVE
f_dot_at_t = (f_at_t − m_f_at_t_minus_h)/delta_t;
return f_dot_at_t;
}
double CDerivativeBlock::CalculateFivePointDerivative(double
m_f_at_t_minus_2h, double m_f_at_t_minus_h, double f_at_t, double delta_t)
558 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
// -- NUMERICAL 5-POINT DERIVATIVE
// The 5-pt derivative of a fn f(t) is
// f’(t) = (−f(t+2h) + 8f(t+h) − 8f(t−h) + f(t−2h) )/12h
//
// However, f(t+h) and f(t+2h) are not available at time t, but may
be obtained by linear extrapolation.
//
// Linear extrapolation: f(tk) = f0 + ( (f1 − f0)/(t1 − t0) )*(tk − t0)
//
// Let f(t−2h) = f0, f(t−h) = f1, f(t) = f2, f(t+h) = f3, and
f(t+2h) = f4.
// Then, RTF f(t+h) = f3 and f(t+2h) = f4, in order to use the
expression for f’(t).
//
// f3 = f1 + ( (f2 − f1)/(t2 − t1) )*(t3 − t1)
// = 2f2 − f1
// and
// f4 = f2 + ( (f3 − f2)/(t3 − t2) )*(t4 − t2)
// = 2f3 − f2
//
// Now upon substituting the expressions for f(t+h) = f3 and f(t+2h)
= f4 into the definition of the derivative f’(t),
// one finds
// f’(t) = (−f(t+2h) + 8f(t+h) − 8f(t−h) + f(t−2h) )/12h
// = (−f4 + 8f3 − 8f1 + f0)/12h
// = (−(2f3 − f2) + 8(2f2 − f1) − 8f1 + f0)/12h
// = (13f2 − 14f1 + f0)/12h
// = (13f(t) − 14f(t−h) + f(t−2h) )/12h
//
// Hence, if linear extrapolation is used to determine f(t+h) and
f(t+2h) in order to determine f’(t), then the formula
// for f’(t) reduces to a fn involving known values, f(t), f(t−h) and
f(t−2h), at the current time point t.
// 5-POINT DERIVATIVE
f_dot_at_t = (13*f_at_t − 14*m_f_at_t_minus_h + m_f_at_t_minus_2h)/
(12*delta_t);
return f_dot_at_t;
}
5. Add a private variable, “m_dMatrixOut”, of type double**, to record the output signal
(stored in a matrix).
6. Add a private integer variable, “m_iNrows”, to record the number of rows of the signal
matrix, “m_dMatrixOut”; this is used for memory deletion purposes.
7. In addition, the double variable “t_step_size_h” that exists in both the CDerivativeBlock
and CIntegratorBlock classes can now be removed, as it is not used in either.
8. Finally, initialize these member variables as shown (in bold) in the CDerivativeBlock con-
structor; the rest of the constructor remains unchanged as denoted by the ellipsis, “…”.
CDerivativeBlock::CDerivativeBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Set memory based vars.
m_iNcallsPerTimeCnt = 0; // no. of times the fn is called per t_cnt
(time point)
m_iNrows = 0; // no. of rows of output matrix signal
m_i_t_cnt_ref = 0; // ref time cnt
m_f_at_t_minus_2h = NULL; // f(t−2h)
m_f_at_t_minus_h = NULL; // f(t−h)
m_dMatrixOut = NULL; // matrix output signal
}
void CBlock::ResetBlockData(void)
{
// This fn has been made virtual and is to be overridden by all
derived CBlock classes.
return;
}
Now add a public virtual member function to the CDerivativeBlock class with the prototype,
virtual void CDerivativeBlock::ResetBlockData(void), and edit it as shown to
deallocate memory and reset member variables.
void CDerivativeBlock::ResetBlockData()
{
int i;
560 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// MEMORY DELETE
if(m_dMatrixOut != NULL)
{
for(i=0; i<m_iNrows; i++)
{
delete [] m_dMatrixOut[i];// delete the row arrays
}
delete [] m_dMatrixOut; // delete the array (column vector)
holding the row arrays
m_dMatrixOut = NULL;
}
if(m_f_at_t_minus_h != NULL)
{
delete [] m_f_at_t_minus_h; // delete member var array f(t−h)
holding all signal’s f(t−h) values
m_f_at_t_minus_h = NULL;
}
if(m_f_at_t_minus_2h != NULL)
{
delete [] m_f_at_t_minus_2h; // delete member var array f(t−2h)
holding all signal’s f(t−2h) values
m_f_at_t_minus_2h = NULL;
}
}
CDerivativeBlock::∼CDerivativeBlock(void)
{
// Reset block data
ResetBlockData(); // reset: m_f_at_t_minus_h, m_f_at_t_minus_2h,
m_dMatrixOut
}
void CSystemModel::ResetMemoryBasedBlockData()
{
CString blk_name; // block name
list<CBlock*>::iterator it_blk; // block iterator
// Reset block data of those blocks that have memory, i.e. remember
previous values
for(it_blk = m_lstBlock.begin(); it_blk != m_lstBlock.end(); it_blk++)
{
blk_name = (*it_blk)->GetBlockName();
void CSystemModel::SignalPropagationDirect()
{
…
// -- Loop for n_TimeSteps + 1, from t_0 = 0, UP TO AND INCLUDING the
last time point t_n
for(t_cnt = 0; t_cnt <= n_TimeSteps; t_cnt++)
{
…
}// end (time)
// -- RESET ALL BLOCKS THAT HAVE MEMORY, I.E. RETAIN PAST VALUES
ResetMemoryBasedBlockData();
⎧ 2.0, t ∈[0, 3) s
⎪
f (t ) = ⎨t − 1, t ∈[3, 8] s (21.13)
⎪ 7.0, t ∈ (8, 10] s
⎩
562 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
(a)
5
f (t), f΄(t)
0
0.55
1.65
2.75
3.85
4.95
6.05
7.15
8.25
9.35
1.1
2.2
3.3
4.4
5.5
6.6
7.7
8.8
9.9
0
–1
(b) t (s)
(c)
FIGURE 21.10 Simple Derivative block–based model: (a) a Linear Function block generating a linear signal,
(b) the linear function f(t) and its derivative f′(t) (c) a Signal Generator block generating a sinusoidal function, and
Block Operations 563
0.5
f (t), f΄(t)
0
0.36
0.72
1.08
1.44
2.16
2.52
2.88
3.24
3.96
4.32
4.68
5.04
5.76
6.12
0
1.8
3.6
5.4
–0.5
–1
–1.5
–2
–2.5
(d) t (s)
FIGURE 21.10 (continued) (d) the sinusoidal function f(t) and its derivative f′(t).
and both the function f(t) and its derivative f′(t) are shown in Figure 21.10b. The Signal Generator block
is used to generate a function f(t) = Asin(ωt + ϕ), where A = 1, ω = 2, ϕ = 0, and t ∈[0.0, 2π], and its deriv-
ative f′(t) = ωAcos(ωt + ϕ) is evaluated numerically by the Derivative block, as shown in Figure 21.10d.
21.7.1 Integration
Mathematical models of real-world systems are often written in the form of first-order ordinary dif-
ferential equations (ODEs) through the use of order reduction by variable substitution. For example,
consider Newton’s second law of motion concerning a body of mass m acted upon by a spring–
damper–actuator force, resulting in the equation
F = k ( x − x0 ) + cx + fa ( x, x , t )
= mx (21.14)
where
F is the total force
k is the spring stiffness
c is the damping coefficient
.
fa(x, x, t) is the actuator force
x0 is the undeformed length of the spring
x is the body position
.
x is the velocity
..
x is the acceleration
t is the independent time variable
564 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
To reduce the second-order linear ODE (21.14) to a system of first-order equations, a variable sub-
stitution is made by defining a state vector
⎡ y1 ⎤ ⎡ x ⎤
y=⎢ ⎥=⎢ ⎥ (21.15)
⎣ y2 ⎦ ⎣ x ⎦
and then upon differentiation, the following first-order state equations may be written
⎡ y1 ⎤ ⎡ x ⎤ ⎡ y2 ⎤ ⎡ y2 ⎤
y = ⎢ ⎥ = ⎢ ⎥ = ⎢ ⎥ = ⎢ −1 ⎥ (21.16)
⎣ y 2 ⎦ ⎣ x⎦ ⎣ F /m ⎦ ⎣ m (k ( y1 − y1,0 ) + cy2 + fa ( y1, y2 , t ))⎦
y = f (t, y) (21.17)
results.
An analytic solution may not be readily available for the state space equations (21.17), and hence
numerical methods must be used to compute a suitable solution, i.e., the state vector y representing
the system being modeled. The following derivation of Euler’s method used for numerical integra-
tion follows closely the work of Shabana [2].
The state space equations
dy
y (t ) = = f (t , y) (21.18)
dt
with the initial condition, y(t = t0) = y0, may be integrated to obtain
y1 t1
∫
y0
dy =
∫ f (t, y)dt
t0
(21.19)
t1
⇒ y1 − y0 =
∫ f (t, y)dt
t0
(21.20)
t1
where the time-step size h = t1 − t0 = Δt, and upon substitution into (21.20), one obtains
y1 = y0 + h f (t0 , y0 ) (21.22)
Block Operations 565
This procedure may be repeated for consecutive values of tn, n = 0, 1, 2, … to arrive at the general
expression of Euler’s method for the nth iteration
yn +1 = yn + h f (t n , yn ) (21.23)
dn = y(t n +1 ) − yn +1 (21.24)
which is the difference between the actual and computed values of the function at tn+1. The second
term on the right-hand side of (21.24) is in fact
since the previous value y(tn) is assumed to be exact. Hence, for Euler’s method,
dn = y(t n +1 ) − yn +1
.
Now, if a Taylor series expansion of y is performed about tn, given the existence of y, ÿ, ∂f / ∂y and
∂f / ∂t on (tn,tn+1), then
d n = O (h 2 ) (21.28)
as h → 0.
The global truncation error en for a vector system, or en for a scalar equation, at tn, is the differ-
ence between the actual y(tn) and computed yn solutions disregarding roundoff error, i.e.,
en = y(t n ) − yn (21.29)
566 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The accuracy of a numerical method is usually discussed in terms of its order that is expressed using
the local truncation error dn; if
d n = O(h p +1 ) (21.30)
as h → 0, then the numerical method is of order p. On comparison of (21.28) and (21.30), one will
notice that Euler’s method is of first order (p = 1).
h
yn +1 = yn + (k1 + 2k2 + 2k3 + k4 ) (21.31)
6
where
k1 = f (t n , yn )
⎛ 1 1 ⎞
k2 = f ⎜ t n + h, yn + hk1 ⎟
⎝ 2 2 ⎠
⎛ 1 1 ⎞
k3 = f ⎜ t n + h, yn + hk2 ⎟
⎝ 2 2 ⎠
k4 = f (t n + h, yn + hk3 )
This method is only of fourth order if the fifth derivatives of y exist on the time interval in ques-
tion, if not, then the method will be of lower order [4]. Clear examples of the first-order single-step
Euler’s scheme and the fourth-order single-step Runge–Kutta scheme may be found in Ref. [2].
FIGURE 21.11 Simple model of direct integration (without a feedback loop) of a linear function involving a
Linear Function, Integrator, and an Output block.
from the Linear Function block, which subsequently enters the Integrator block, for this example, is
f(t) = t and forms the right-hand side of the scalar state equation
y = f (t , y) (21.32)
(Vector systems may be constructed by multiplying scalar functions by the appropriate unit vector
and summing them to form a first-order state vector (21.17).)
The scalar form of Euler’s method
yn +1 = yn + hf (t n , yn ) (21.33a)
or equivalently
yn = yn −1 + hf (t n −1, yn −1 ) (21.33b)
can be used to obtain approximate state values yn where the corresponding exact values are
y(t n ) = t n2 / 2 , for n = 0, 1, …, N, where N is the final time point of the simulation. Table 21.1 shows
TABLE 21.1
.
Numerical Integration of y = f(t, y) = t Using
Euler’s Method
t n2
n tn y n = y n−1 + hf (t n−1, y n−1) y(t n ) = en = y(tn) − yn
2
0 0 y0 = 0 y(0) = 0 0
1 0.1 y1 = 0 y(0.1) = 0.005 0.005
2 0.2 y2 = 0.01 y(0.2) = 0.02 0.01
3 0.3 y3 = 0.03 y(0.3) = 0.045 0.015
4 0.4 y4 = 0.06 y(0.4) = 0.08 0.02
5 0.5 y5 = 0.10 y(0.5) = 0.125 0.025
⋮ ⋮ ⋮ ⋮ ⋮
20 2.0 y20 = 1.9 y(2.0) = 2.0 0.10
yn are the approximate values at iteration n, where n ∈[0, 20]; y(t n ) are
the corresponding exact values; en = y(tn) − yn are the global trunca-
tion error values; y0 = y(0) = 0; h = 0.1 s.
568 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
f(t)
Euler integral
2
Exact integral
f(t), Euler and exact integrals
1.5
0.5
0
0 0.2 0.4 0.6 0.8 1 1.2 1.4 1.6 1.8 2
t (s)
FIGURE 21.12 Linear function f(t) = t and its exact y(tn) and numerical yn integrals showing an increasing
global truncation error en.
the results of the numerical integration: the iteration number n ∈ [0, N], tn is the time point that
advances in constant time-steps h = Δt = tn − tn−1, and en = y(tn) − yn is the global truncation error.
.
Figure 21.12 shows the graphs of the functions y = f(t, y) = t, the approximate numerical integral
values yn, and the exact values y(tn) vs. time for t ∈ [0, 2.0] with h = 0.1s, where the data correspond
to that in the preceding table. The function f(t, y) is linear, hence the integral is quadratic, and the
global truncation error increases with time. For this example, after 20 iterations, e20 = h.
{
length_y = nrows;
}
else
{
length_y = ncols;
}
// SET TIME COUNT REF AND NO. OF TIMES THE FN IS CALLED AT THIS TIME
POINT
if(m_i_t_cnt_ref == t_cnt)
{
m_iNcallsPerTimeCnt++;
}
else
{
m_i_t_cnt_ref = t_cnt;
m_iNcallsPerTimeCnt = 1;
}
// -- MEMORY NEW
y_at_t = new double[length_y];
if(t_cnt > 0)
{
// -- PERFORM CHOSEN NUMERICAL INTEGRATION
if(sIntegrationMethod == “Euler (1st Order)”)
{
EulerMethod(y_at_t, m_y_at_t_minus_h, m_y_dot_at_t_minus_h,
delta_t, length_y);
}
else if(sIntegrationMethod == “Runge-Kutta (4th Order)”)
{
// Default Euler method used in absence of Runge-Kutta method.
EulerMethod(y_at_t, m_y_at_t_minus_h, m_y_dot_at_t_minus_h,
delta_t, length_y);
}
}
//sMsgTemp.Format(“\n CIntegratorBlock::OperateOnData()\n”);
//sMsg+=sMsgTemp;
//sMsgTemp.Format(“ length_y = %d, y_at_t[0] = %lf,
m_y_at_t_minus_h[0] = %lf, m_y_dot_at_t_minus_h[0] = %lf\n”,
length_y, y_at_t[0], m_y_at_t_minus_h[0], m_y_dot_at_t_minus_h[0]);
//sMsg+=sMsgTemp;
//AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
}
else
{
for(i=0; i<length_y; i++)
{
m_y_dot_at_t_minus_h[i] = matrix_in[0][i];
}
}
// -- MEMORY DELETE
delete [] y_at_t; // delete the y_dot array
return valid;
}
The signal input and output of a block are considered to be generated at the current time value
tn n ∈ [0, N]; e.g., for a Derivative block, if the input signal is f(tn), then the output signal is f ′(tn).
However, for the Integrator block, the expression to evaluate the numerical integral using, e.g.,
Euler’s method (21.23), implies that the output signal yn+1, at tn+1, is at a time one time-step greater
than the input signal, yn, at tn. In order that all model blocks behave in a consistent manner, Euler’s
method is still used, but in the following form:
yn = yn−1 + h f (t n −1, yn −1 )
= yn −1 + hy n −1 (21.34)
i.e., the current value of yn to be sent as an output signal at tn is a function of the previous values yn−1 and
.
yn−1; these must then be statically stored (“remembered”) and updated appropriately. The developer will
notice that the explicit time variable tn is not actually used in the OperateOnData() function, since the
.
input signal yn (= f(tn, yn)) is already in numerical rather than analytic form and hence can be used directly.
Block Operations 573
Initially, the dimensions of the input signal are checked to make sure that it is in vector form, and
then its length is determined and checked against the member variable, “m_iLength”, the length
of the initial condition vector. If the vector is of the correct structure, then the member variable,
“m_iNrowsOut”, used to record the number of rows of the output matrix signal is initialized; it is
used for memory deallocation purposes in ResetBlockData() (to be introduced).
Thereafter a test is made to determine the number of times the OperateOnData() function is
called for the current time point “t_cnt”. This is the same check that was made for the Derivative block
and is required since the Integrator block has memory, in that it records values at previous time points,
.
i.e., y(t − h) and y(t − h); the output y(t) is a function of these previous values. If the block were called
.
more than once (in between time-steps) and the input signal, y(t), changed, then the internal memory of
.
the block, i.e., the values y(t − h) and y(t − h), would not be those at previous time points, but rather at
previous intermediary function-call occasions. Hence, if the function OperateOnData() is called
more than once per time-step, then the output to be generated, y(t) (recorded in “m_dMatrixOut”), is
that already determined by the initial call to the function, and the function returns.
Then memory is allocated for yt ≡ yn, and initially at t = 0 for “m_dMatrixOut”, yt−h ≡ yn−1 and
. .
yt−h ≡ yn−1, where the latter two variables hold (“remember”) information generated at the previous
time point t − h ≡ tn−1.
Now for the first iteration, at t 0 = 0, y(t 0) = y0, i.e., the output signal is the initial condition
.
vector. The Euler integration recurrence method is skipped; yt−h is set to be the current incom-
. .
ing signal, i.e., yt−h ← y0 (here the symbol ← denotes programmatic assignment), and then yt−h is
assigned yt = y0, i.e., yt−h ← y0, in preparation for the next call of the OperateOnData() func-
tion. Then, the output data are written to the output signal, i.e., at t 0 = 0, y(t 0) = y0. So the purpose
of the first call to OperateOnData() is simply to prepare variables for numerical computation
at the next iteration and to generate the initial condition vector y0 as an output signal at t 0, as
shown in Figure 21.13a.
For subsequent iterations, the numerical integration algorithm (here Euler’s method) is called
.
to determine yt, given yt−h , y t−h , and h. Then, after the output signal yt has been determined,
yt0 yt0
At t0: ∫dt
yt‒h yt0
(a) yt‒h yt0
yt1 yt1
At t1: ∫dt
yti yti
At t2: ∫dt
FIGURE 21.13 Numerical integration operation: (a) at t0, the initial condition vector yt 0 is output and local
variables updated; (b) at t1, output yt1 is determined using the previously stored variables according to Euler’s
method and the local variables updated again for the next iteration; and (c) in general, at ti, output yti is deter-
mined using previously stored values and the local variables are updated.
574 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
. . . .
yt−h is updated with the incoming signal yt of the current iteration (t), i.e., yt−h ← yt, and yt−h is
updated with the current output signal yt, i.e., yt−h ← yt, in preparation for the next call to the
.
function, whereupon the previously recorded yt−h and yt−h values will be used to generate the
.
next output signal, i.e., yt = yt−h + hyt−h , as shown in Figure 21.13b for t1 and in general for t i in
Figure 21.13c.
.
That is, the signal yt generated at time t makes use of the signals yt−h and yt−h, i.e., the stored signal
output and incoming input signal at the previous time-step, respectively. Hence, the current output
. .
yt is not dependent on the current input yt, but yt is required for storage purposes at the current time-
step to be used at the next time-step.
Finally, the output signal, “m_dMatrixOut”, is assigned yt and written to the CConnection
object’s CSignal data member, and local memory is deleted.
return 0;
}
Euler’s method determines the state value yt given the state value and its derivative at the previous
.
time-step, yt−h and yt−h (= f(t − h, yt−h)), respectively, and the time-step size h = Δt, i.e.,
yt = yt − h + hy t − h (21.35)
for t ∈ [0, N]. Only one step is being made and only yt is being evaluated.
At this stage, only Euler’s method has been provided; methods of higher order, e.g., the fourth-
order Runge–Kutta method, will be written and added to the project in future.
Edit the function as shown in the following to simply return the value of the CString variable,
“m_strIntegration Method”, of the CSystemModel class. The developer will notice that the numeri-
cal integration method–related variables are stored in the CSystemModel class rather than the
CIntegratorBlock class; the latter holds the initial condition vector.
CIntegratorBlock::CIntegratorBlock(CSystemModel *pParentSystemModel,
CPoint blk_posn, EBlockShape e_blk_shape):
CBlock(pParentSystemModel, blk_posn, e_blk_shape)
{
…
// Set memory based vars.
m_iNcallsPerTimeCnt = 0; // no. of times the fn is called per
t_cnt (time point)
m_iNrowsOut = 0; // no. of rows of output matrix signal
m_i_t_cnt_ref = 0; // ref time cnt
m_dMatrixOut = NULL; // output matrix signal
m_y_at_t_minus_h = NULL; // y(t-h)
m_y_dot_at_t_minus_h = NULL; // y_dot(t-h)
}
// MEMORY DELETE
if(m_dMatrixOut != NULL)
{
for(i=0; i<m_iNrowsOut; i++)
{
delete [] m_dMatrixOut[i]; // delete the row arrays
}
delete [] m_dMatrixOut; // delete the array
(column vector) holding the row arrays
m_dMatrixOut = NULL;
}
if(m_y_at_t_minus_h != NULL)
{
delete [] m_y_at_t_minus_h; // delete member var array
f(t-h) holding all signal’s f(t-h) values
m_y_at_t_minus_h = NULL;
}
if(m_y_dot_at_t_minus_h != NULL)
{
delete [] m_y_dot_at_t_minus_h; // delete member var array
f(t-2h) holding all signal’s f(t-2h) values
m_y_dot_at_t_minus_h = NULL;
}
}
FIGURE 21.14 OutputBlock dialog window showing numerical state values yt evaluated using Euler’s
method for y = f (t, y) = t, t ∈[0, 2.0] s, y0 = 0, and h = 0.1 s (cf. Table 21.1).
FIGURE 21.15 Two Constant blocks connected to a Divide block that is connected to an Output block; the con-
stant value associated with the upper Constant block is divided by that associated with the lower Constant block.
578 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// -- ITERATE OVER ALL PORTS AND CHECK INPUT SIGNAL DATA VALIDITY
for(it_port = vec_of_input_ports.begin(); it_port
!= vec_of_input_ports.end(); it_port++)
{
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
if( (*it_con)->GetRefToPort() == *it_port) // if the input port
of this connection is the current port
{
valid = (*it_con)->GetSignal()->GetSignalValidity();
if(valid == 0)
{
return valid;
}
}
}// end it_con
}// end it_port
// MEMORY NEW
temp_matrix = new double *[nrows]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows; i++)
Block Operations 579
{
temp_matrix[i] = new double[ncols]; // allocate an array of
doubles
}
// MEMORY DELETE
if(temp_matrix != NULL)
{
for(i=0; i<nrows; i++)
{
delete [] temp_matrix[i]; // delete the row arrays
}
delete [] temp_matrix; // delete the array (column
vector) holding the row arrays
}
if(valid != 1)
{
break; // Exit for it_port since DivideMultiplyOperation()
is in error.
}
port_cnt++;
}// end it_port
if(valid == 1)
{
valid = WriteDataToOutputSignal(temp_matrix, nrows, ncols);
}
// -- MEMORY DELETE
// DELETE temp_matrix, EQUIVALENTLY Result, SINCE temp_matrix WAS
ASSIGNED Result AT THE LAST STEP (nrows = nrows_R),
// AND Result’s MEMORY WAS ALLOCATED WITHIN
DivideMultiplyOperation().
if(temp_matrix != NULL)
{
// MEMORY DELETE
for(i=0; i<nrows; i++)
{
delete [] temp_matrix[i]; // delete the row arrays
}
delete [] temp_matrix; // delete the array (column vector)
holding the row array
}
return valid;
}
The OperateOnData() function of the Divide block first iterates over all its input ports and
checks whether the signal data of the incoming connection objects are available (valid), if not, the
function returns, if so, execution continues to perform pairwise operations on input data.
Consider a Divide block with two ports, p1 and p2, whose signs are multiply (×) and divide (/),
respectively, ordered from the top to the bottom of the left (input) side, admitting two connections
whose signal data are P1 and P2, respectively (cf. Figure 21.15). On the first iteration of the pairwise
operation loop, the “temp_matrix” is set equal to unity, the appropriate port name is obtained,
in this case “multiply” (×), and the DivideMultiplyOperation() function (introduced in
Section 21.8.2) is called to perform the pairwise operation to produce the result (“Result”)
R = 1P1 (21.36)
which is then assigned to the temporary matrix (“temp_matrix”) in preparation for the next pairwise
operation. On the second iteration, the result of the first iteration (“temp_matrix”) is then the left argu-
ment of the ensuing operation, in this case divide (/), involving the second input signal argument P2, i.e.,
1P1
R= = P1 P2−1 (21.37)
P2
where the repeated left-to-right operations correspond to a top-down order of port signs on the
block itself.
In the to-be-added DivideMultiplyOperation() function, the left and right arguments
forming the operation (op.), in A op. B, are denoted A and B, respectively. For the previous example,
on the first iteration through the vector of input ports, A = 1 and B = P1, and on the second iteration,
A = 1P1 and B = P2, where the operations are multiply and divide, respectively.
After each call to the DivideMultiplyOperation() function, the temporary matrix
“temp_matrix” has its memory deleted, and since memory is allocated for the result, “Result” (R),
from within the DivideMultiplyOperation() function itself, the assignment of the (double)
pointer, “temp_matrix = Result”, is feasible, and its dimensions, “nrows_R” and “ncols_R”, returned
through the function’s parameter list may be used in the deletion of “temp_matrix” on each follow-
ing iteration, and finally before the function returns.
Block Operations 581
The “valid” variable passed back from the DivideMultiplyOperation() function can have the
values “−1”, “0”, or “1”, indicating an operational error, a signal data read/write error, or an operational
success, respectively. If the divide/multiply operation is in error, then the operation is terminated and the
value passed back to the calling environment, i.e., to SignalPropagationDirect(). However, if
the operation is a success, then the final result is written to the output connection’s CSignal data member.
Finally, edit the code as shown in the following to filter through all the possible combinations of the
input-argument dimensions, to perform either a multiplication or division operation, in an elemental
or matrix-wise manner. Table 21.2 provides a summary of the dimension-filtering process and the
TABLE 21.2
Input-Argument Dimension-Filtering Process Used in the
DivideMultiplyOperation() Function to Perform
Multiplication and Division Operations
Case Argument 1: a, a, A Argument 2: b, b, B Multiplication Division
1 a b ab a/b
2 a b(1 × n) ab a/bi
3 a b(n × 1) ab a/bi
4 a(1 × n) b ba a/b
5 a(n × 1) b ab a/b
6 a B aB a/Bij, aB−1
7 A b bA A/b
8 a(1 × n) b(1 × n) ai bi ai/bi
9 a(n × 1) b(n × 1) ai bi ai/bi
10 a(1 × n) b(n × 1) ai bi, ab ai/bi
11 a(n × 1) b(1 × n) ai bi, ab ai/bi
12 A(n × n) B(n × n) Aij Bij, AB Aij/Bij, AB−1
13 A(n × m) B(m × m) AB AB−1
14 A(n × m) B(m × p) AB NA
types of multiplication and division of the input arguments; notation without subscripts denotes
standard matrix operation and that with subscripts indicates elemental operation.
// Get signal data and assign pointer to matrix B, but this is the
actual signal data not a copy.
B = ptr_to_con->GetSignal()->GetSignalData(&nrows_B, &ncols_B);
a = A[0][0];
b = B[0][0];
if(sign == “*”)
{
R[0][0] = a*b;
}
else if(sign == “/”)
{
if(b != 0)
{
R[0][0] = a/b;
}
else
{
valid = −2; // divide by zero error
}
}
}
// -- A IS A SCALAR AND B IS A VECTOR OR VICE VERSA ----------------
else if( (nrows_A == 1) && (ncols_A == 1) && (nrows_B == 1) &&
(ncols_B > 1) ) // A = scalar, B = row vec
{
584 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// MEMORY NEW
nrows_R = nrows_B;
ncols_R = ncols_B;
R = new double *[nrows_R]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows_R; i++)
{
R[i] = new double[ncols_R]; // allocate an array of doubles
}
a = A[0][0];
a = A[0][0];
b = B[0][0];
// Elemental and Matrix operations are the same for: vector op.
scalar (in that order)
if(sign == “*”)
{
MatrixMultiply(nrows_B, ncols_B, ncols_A, B, A, R);
// (1x1)*(1xn) = (1xn), B is a scalar
}
else if(sign == “/”)
{
for(i=0; i<ncols_R; i++)
{
if(b != 0)
{
R[0][i] = A[0][i]/b;
}
else
{
valid = −2; // divide by zero error
break;
}
}
}
}
else if( (nrows_A > 1) && (ncols_A == 1) && (nrows_B == 1) &&
(ncols_B == 1) ) // A = col vec, B = scalar
{
// MEMORY NEW
nrows_R = nrows_A;
ncols_R = ncols_A;
R = new double *[nrows_R]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows_R; i++)
{
R[i] = new double[ncols_R]; // allocate an array of doubles
}
b = B[0][0];
// Elemental and Matrix operations are the same for: vector op.
scalar (in that order)
if(sign == “*”)
{
MatrixMultiply(nrows_A, ncols_A, ncols_B, A, B, R);
// (nx1)*(1x1) = (nx1), B is a scalar
}
else if(sign == “/”)
{
for(i=0; i<nrows_R; i++)
{
if(b != 0)
{
R[i][0] = A[i][0]/b;
}
else
{
valid = −2; // divide by zero error
Block Operations 587
break;
}
}
}
}
// -- A IS A SCALAR AND B IS A MATRIX OR VICE VERSA -----------------
else if( (nrows_A == 1) && (ncols_A == 1) && (nrows_B > 1) && (ncols_B
> 1) ) // A = scalar, B = matrix
{
// MEMORY NEW
nrows_R = nrows_B;
ncols_R = ncols_B;
R = new double *[nrows_R]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows_R; i++)
{
R[i] = new double[ncols_R]; // allocate an array of doubles
}
a = A[0][0];
{
if(sign == “*”)
{
for(i=0; i<nrows_R; i++)
{
for(j=0; j<ncols_R; j++)
{
R[i][j] = a*B[i][j];
}
}
}
else if(sign == “/”)
{
if(nrows_B == ncols_B) // if B is a square matrix
{
// MEMORY NEW
// Inverse matrix B_inv = B∧−1
B_inv = new double *[nrows_B]; // allocate an
array of ptrs-to-double
for(i=0; i<nrows_B; i++)
{
B_inv[i] = new double[ncols_B]; // allocate an
array of doubles
}
{
R[i][j] = a*B_inv[i][j]; // a/B = a*B_inv
}
}
// MEMORY DELETE
for(i=0; i<nrows_B; i++)
{
delete [] B_inv[i]; // delete the row arrays
}
delete [] B_inv; // delete the array (column
vector) holding the row array
// Elemental and matrix operations are the same for the order A
op. B, where A is a matrix and B a scalar.
if(sign == “*”)
{
for(i=0; i<nrows_R; i++)
{
for(j=0; j<ncols_R; j++)
{
R[i][j] = b*A[i][j];
}
}
}
else if(sign == “/”)
{
if(b != 0)
590 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
for(i=0; i<nrows_R; i++)
{
for(j=0; j<ncols_R; j++)
{
R[i][j] = A[i][j]/b;
}
}
}
else
{
valid = −2; // divide by zero error
}
}
}
{
valid = −3; // dimensionality error
}
}
else if( (nrows_A > 1) && (ncols_A == 1) && (nrows_B == nrows_A) &&
(ncols_B == ncols_A) ) // A and B are col vecs of the same length
{
// MEMORY NEW
nrows_R = nrows_A;
ncols_R = ncols_A;
R = new double *[nrows_R]; // allocate an array of ptrs-to-double
for(i=0; i<nrows_R; i++)
{
R[i] = new double[ncols_R]; // allocate an array of doubles
}
if(m_iMultType == 0) // Elemental op.
{
if(sign == “*”)
{
for(i=0; i<nrows_R; i++)
{
R[i][0] = A[i][0]*B[i][0];
}
}
else if(sign == “/”)
{
for(i=0; i<nrows_R; i++)
{
if(B[i][0] != 0)
{
R[i][0] = A[i][0]/B[i][0];
}
else
{
valid = −2; // divide by zero error
break;
}
}
}
}
else if(m_iMultType == 1) // Matrix op.
{
valid = −3; // dimensionality error
}
}
else if( (nrows_A == 1) && (ncols_A > 1) && (nrows_B == ncols_A) &&
(ncols_B == nrows_A) ) // A = row vec, B = col vec, of same length
{
if(m_iMultType == 0) // Elemental op.
{
// MEMORY NEW
nrows_R = nrows_A;
ncols_R = ncols_A;
R = new double *[nrows_R]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows_R; i++)
592 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
R[i] = new double[ncols_R]; // allocate an array of
doubles
}
if(sign == “*”)
{
for(i=0; i<ncols_R; i++)
{
R[0][i] = A[0][i]*B[i][0];
}
}
else if(sign == “/”)
{
for(i=0; i<ncols_R; i++)
{
if(B[i][0] != 0)
{
R[0][i] = A[0][i]/B[i][0];
}
else
{
valid = −2; // divide by zero error
break;
}
}
}
}
else if(m_iMultType == 1) // Matrix op.
{
if(sign == “*”)
{
// MEMORY NEW
nrows_R = nrows_A;
ncols_R = ncols_B;
R = new double *[nrows_R]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows_R; i++)
{
R[i] = new double[ncols_R]; // allocate an array of
doubles
}
{
// MEMORY NEW
nrows_R = nrows_A;
ncols_R = ncols_A;
R = new double *[nrows_R]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows_R; i++)
{
R[i] = new double[ncols_R]; // allocate an array of doubles
}
if(sign == “*”)
{
for(i=0; i<nrows_R; i++)
{
R[i][0] = A[i][0]*B[0][i];
}
}
else if(sign == “/”)
{
for(i=0; i<nrows_R; i++)
{
if(B[0][i] != 0)
{
R[i][0] = A[i][0]/B[0][i];
}
else
{
valid = −2; // divide by zero error
break;
}
}
}
}
else if(m_iMultType == 1) // Matrix op.
{
if(sign == “*”)
{
// MEMORY NEW
nrows_R = nrows_A;
ncols_R = ncols_B;
R = new double *[nrows_R]; // allocate an array of
ptrs-to-double
for(i=0; i<nrows_R; i++)
{
R[i] = new double[ncols_R]; // allocate an array of
doubles
}
MatrixMultiply(nrows_A, ncols_A, ncols_B, A, B, R);
// (nx1)*(1xn) = (nxn)
}
else if(sign == “/”)
{
valid = −3; // dimensionality error, B non-square
}
}
}
594 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
else
{
valid = −2; // divide by zero error
break;
}
}
if(valid == −2)
{
break;
}
}
}
}
else if(m_iMultType == 1) // Matrix op.
{
if(sign == “*”)
{
MatrixMultiply(nrows_A, ncols_A, ncols_B, A, B, R);
}
else if(sign == “/”)
{
// MEMORY NEW
B_inv = new double *[nrows_B]; // allocate an
array of ptrs-to-double
for(i=0; i<nrows_B; i++)
{
B_inv[i] = new double[ncols_B]; // allocate an array
of doubles
}
// MEMORY DELETE
for(i=0; i<nrows_B; i++)
{
delete [] B_inv[i]; // delete the row arrays
}
delete [] B_inv; // delete the array (column vector)
holding the row array
// MEMORY DELETE
for(i=0; i<nrows_B; i++)
{
delete [] B_inv[i]; // delete the row arrays
}
delete [] B_inv; // delete the array
(column vector) holding the row array
// -- ERROR MESSAGES
if(valid == −1)
{
sMsgTemp.Format(“\n WARNING: error in usage of Divide-Multiply
block!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Check that the matrix to be inverted is not
singular for division operation.\n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
}
else if(valid == −2)
{
sMsgTemp.Format(“\n WARNING: error in usage of Divide-Multiply
block!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Check that the denominator is not zero for
division operation.\n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
}
else if(valid == −3)
{
sMsgTemp.Format(“\n WARNING: error in usage of Divide-Multiply
block!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n Check data dimensionality of all inputs
arguments.\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Check the type of multiplication/division
selected: Elemental or Matrix.\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“ Check the order of operations (top-down order
of ports).\n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
}
Block Operations 599
// Global Functions
CDiagramEngDoc* GetDocumentGlobalFn(void);
double **ConvertStringToDouble(CString string, int &nrows, int &ncols);
char *StripInputString(char *input_str);
int DetermineDataDimsByStrpbrk(char *in_str, int &nrows, int &ncols, int
&max_row_length);
int DetermineDataDimsByStrtok(char *in_str, int &nrows, int &ncols, int
&max_row_length);
int GaussJordanEliminationFullPivoting(double **A, int n, double **B,
int m);
double **ConvertStringMatrixToDoubleMatrix(char **str_matrix, int nrows,
int ncols);
void MatrixMultiply(int nrowsA, int ncolsA, int ncolsB, double **A,
double **B, double **C);
int PrintMatrix(double **M, int nrows, int ncols);
int PrintMatrix(int **M, int nrows, int ncols);
int PrintVector(double *v, int ncols);
int PrintVector(int *v, int ncols);
where
A.x_1 = b_1,
A.x_2 = b_2,
A.x_3 = b_3,
and
A.Y = I
=> Y = A∧−1
B:nxm = input matrix containing the m r.h.s. vectors (of length n).
This is overwritten with the corres. set of soln. vectors. That is,
x_1 = A∧−1.b_1,
x_2 = A∧−1.b_2,
x_3 = A∧−1.b_3.
*/
// Declaration
int i;
int icol; // col index
int irow; // row index
int j;
int k;
int u;
int v;
int valid = 1; // validity of result: (−1) if singular,
(1) if no error
int *index_c = NULL; // col index used for pivoting
int *index_r = NULL; // row index used for pivoting
int *ipiv = NULL; // pivot index array
double big;
double dum;
double pivinv;
double eps = 1.0e−6; // ALTERATION (BF): epsilon value to check for
a “zero” on the diagonal
// MEMORY NEW
// Integer arrays used to manage the pivoting
index_c = new int[n];
index_r = new int[n];
ipiv = new int[n];
{
big = fabs(A[j][k]); // update big with the
double-type abs value of A[j][k]
irow = j;
icol = k;
}
}
}// end for k
}// end if ipiv[j]
}// end for j
++(ipiv[icol]);
if(irow != icol)
{
for(u=0; u<n; u++)
{
Swap(A[irow][u], A[icol][u]);
}
// Now the pivot row can be divided by the pivot element, located
at irow and icol.
pivinv = 1.0/A[icol][icol];
A[icol][icol] = 1.0;
Block Operations 603
// Now the rows are reduced, except for the pivot one.
for(v=0; v<n; v++)
{
if(v != icol)
{
dum = A[v][icol];
A[v][icol] = 0.0;
// MEMORY DELETE
delete [] index_c;
delete [] index_r;
delete [] ipiv;
return valid;
}
604 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
where
A is a n×n leading matrix
xi are the vectors of unknowns, for i ∈[1, m] of a total of m simultaneous systems, of the form
Axi = bi (21.39)
⇒ xi = A−1bi (21.40)
AY = I (21.41)
⇒ Y = A−1 I (21.42)
is used where Y holds the inverse, A−1, of the leading matrix, A, at the end of system computation.
The function prototype, void GaussJordanEliminationFullPivoting(double **A,
int n, double **B, int m), involves two matrices A and B that are passed into the func-
tion. On entry, A is the leading matrix and B an n×m matrix composed of the m right-hand side
vectors bi, and on return, A holds its own inverse A−1 (21.42) and B holds the m solution vectors xi
(21.40). If only the inverse of a matrix is desired and no solution xi sought, then a “dummy” right-
hand side vector can be used, as it is in the DivideMultiplyOperation() function when calling
GaussJordanEliminationFullPivoting().
Appendix E contains a self-contained Win32 Console Application, titled MatrixInversion, that
explores matrix inversion using the GaussJordanEliminationFullPivoting() function.
The interested reader may like to experiment with this application first before implementing the
GaussJordanEliminationFullPivoting() function in the DiagramEng application.
// Global Functions
CDiagramEngDoc* GetDocumentGlobalFn(void);
double **ConvertStringToDouble(CString string, int &nrows, int &ncols);
char *StripInputString(char *input_str);
int DetermineDataDimsByStrpbrk(char *in_str, int &nrows, int &ncols, int
&max_row_length);
int DetermineDataDimsByStrtok(char *in_str, int &nrows, int &ncols, int
&max_row_length);
int GaussJordanEliminationFullPivoting(double **A, int n, double **B,
int m);
Block Operations 605
Add the implementation of the Swap() function to the “DiagramEngDoc.cpp” source file under-
neath the GaussJordanEliminationFullPivoting() function and edit the code as shown
in the following.
// Swap a and b
temp = a;
a = b;
b = temp;
}
The Swap() function simply swaps the values stored in the variables a and b, where both argu-
ments are references for those passed to the function. Alternatively, the addresses of the arguments
may be passed and pointers used to access what is stored at the argument addresses.
21.10 SUMMARY
The OperateOnData() functions for the Linear Function and Signal Generator (Source) blocks;
the Divide, Gain, and Sum (Mathematical Operations) blocks; and the Derivative and Integrator
(Continuous) blocks are added to the DiagramEng project. Parameters are set using the block dialog
parameter input windows, and block operation is verified by inspecting the numerical data of the
Output block. The Linear Function block generates a linear function value; the Signal Generator
block generates a sine, random, or square wave signal; the Divide block filters input based on sig-
nal data dimensionality and performs elemental or matrix-wise division and multiplication opera-
tions; the Gain block performs elemental, gain-multiplied-by-input, or input-multiplied-by-gain
606 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
operations; the Sum block adds or subtracts dimensionally consistent data; the Derivative block
implements a three-point or five-point derivative f ′(t) expression using linear extrapolation; and
finally, the Integrator block implements the single-step Euler method to determine state values y,
.
given first-order ODE-based input y = f(t, y).
REFERENCES
1. Scheid, F., Schaum’s Outline of Theory and Problems of Numerical Analysis, 2nd edn., Schaum’s Outline
Series, McGraw-Hill, New York, 1988.
2. Shabana, A. A., Computational Dynamics, 2nd edn., John Wiley & Sons, New York, 2001.
3. Fisher, M. E., Introductory Numerical Methods with the NAG Software Library, The University of
Western Australia, Perth, WA, 1989.
4. Fox, B., Jennings, L. S., and Zomaya, A. Y., Constrained Dynamics Computations: Models and Case
Studies, World Scientific Series in Robotics & Intelligent Systems—Vol. 16, World Scientific, London,
U.K., 2000.
5. Press, W. H., Teukolsky, S. A., Vetterling, W. T., and Flannery, B. P., Numerical Recipes in C: The Art of
Scientific Computing, 2nd edn., Cambridge University Press, Cambridge, U.K., 2002.
22 Preparation for
Feedback-Based Signal
Propagation
22.1 INTRODUCTION
Direct signal propagation in the absence of a feedback loop was introduced in Chapter 19 and was
implemented through the execution of the CSystemModel function SignalPropagationDirect(),
which calls the CBlock function OperateOnData() for each block that generates output. This method
of signal propagation was continued in Chapter 21, where further OperateOnData() functions were
provided to result in simple signal output for the Constant, Linear Function, and Signal Generator
(Source) blocks; the Divide, Gain, and Sum (Mathematical Operations) blocks; and the Derivative and
Integrator (Continuous) blocks, where the Output (Sink) block displays the propagated signals as graphs
in a View window.
However, the direct signal propagation approach is complicated by the presence of a feedback
or algebraic loop, where as discussed earlier, the input of a block depends, usually indirectly, on its
own output. In this case, a set of equations representing the block operations are to be constructed
and computed simultaneously, either as a linear or a nonlinear system, depending on the type of
block operations involved.
22.2 LINEAR SYSTEMS
Linear systems were introduced in Chapter 19 and are those to which the principle of superposition
applies [1] and, in particular, satisfy the additivity property and scaling and homogeneity prop-
erty [2]. Consider the simple linear system with Linear Function, Sum, Gain, and Output blocks
shown in Figure 22.1: block functional operations are denoted fi(t), operating at the current time
point t ∈ [t0, tf ] of the simulation, where t0 and tf are the initial and final time points, respectively;
signal values are denoted xi; and block numbers i ∈ [0, n −1] are shown beneath the blocks for a
total of n blocks.
The block equations for this model may be written as
x0 = f0 (t )
x1 = f1 ( x0 , x3 ) = x0 + x3
(22.1)
x2 = f2 ( x1 ) = k2 x1
x3 = f3 ( x2 ) = k3 x2
607
608 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
x0 x1 x2
f0(t) f2(t) Out
+
+
0 1 2 4
x3
f3(t)
FIGURE 22.1 Simple linear system with Linear Function, Sum, Gain, and Output blocks: fi(t) and xi denote
the ith block operation and output signal, respectively, where block numbers appear beneath the blocks.
where k 2 and k3 are the gain constants of blocks two and three, respectively, and the vector
of output signals x = [x 0, x1, x 2, x 3]T: the Output block does not generate an output signal,
i.e., x 4 does not exist and, hence, it does not appear in the block equations. The equivalent
matrix–vector system is
⎡1 0 0 0 ⎤ ⎡ x0 ⎤ ⎡ f0 (tt )⎤
⎢ −1 1 0 −1⎥⎥ ⎢⎢ x1 ⎥⎥ ⎢⎢ 0 ⎥⎥
⎢ = (22.2)
⎢0 − k2 1 0 ⎥ ⎢ x2 ⎥ ⎢ 0 ⎥
⎢ ⎥⎢ ⎥ ⎢ ⎥
⎣0 0 − k3 1 ⎦ ⎣ x3 ⎦ ⎣ 0 ⎦
i.e.,
Ax = b (22.3)
⇒ x = A−1b (22.4)
where
A is the leading matrix
b is the right-hand-side vector containing the Linear Function source signal f0(t)
However, computation of this linear system, in particular, the existence of an inverse A−1, is
dependent on the numerical choice of the gain constants. In general, a model involving linear
operations can be cast in the form of a linear system and computed with, e.g., the Gauss–Jordan
elimination function with full pivoting, GaussJordanEliminationFullPivoting(),
introduced for the Divide block operation in the previous chapter, for the block-output vector x.
Note that a block diagram, e.g., Figure 22.1, represents a system of equations at any particular time
instant and this system would then be computed for each time point t ∈ [t 0, tf ] of the simulation.
22.3 NONLINEAR SYSTEMS
Nonlinear systems are those that do not satisfy the principle of superposition, in particular the
properties of additivity and homogeneity [2]. The aforementioned model involves block operations
fi(t) for source-signal-generating blocks and fi(x(t)) for blocks with both input and output signals that
are linear in the generalized output signal vector x(t). However, if this is not guaranteed to be the
case, then a linear system of the form Ax = b, shown earlier, cannot be constructed, and, hence, a
nonlinear solution is required. In this case, the system of equations to be constructed is as follows:
x0 = f0 (t )
Preparation for Feedback-Based Signal Propagation 609
x1 = f1 ( x0 , x3 )
x2 = f2 ( x1 ) (22.5)
x3 = f3 ( x2 )
i.e.,
⎡ x0 ⎤ ⎡ f0 (t ) ⎤
⎢ x ⎥ ⎢ f ( x) ⎥
⎢ 1⎥ − ⎢ 1 ⎥ = 0 (22.6)
⎢ x2 ⎥ ⎢ f2 ( x ) ⎥
⎢ ⎥ ⎢ ⎥
⎣ x3 ⎦ ⎣ f3 ( x) ⎦
or more generally
where
x(t) = [x0(t), x1(t), …, xs−1(t)]T is the generalized output signal vector
f(x(t)) = [f0(x(t)), f1(x(t)), …, fs−1(x(t))]T is a vector of block-operation functions fi(x(t)) (here, for
simplicity, scalar functions) for i ∈ [0, s − 1] that is in general a function of the output signal
vector x(t)
s is the number of blocks that produce output signals
The system vector F(x(t)) is the difference between the signal output vector x(t) and the block-
operation function vector f(x(t)), which should be zero. The roots or zeros of this system are the
vectors xr(t) that satisfy (22.7) and form the output signal solution vector of this system at each time
point of the simulation.
f ( xr ) = 0 (22.8)
Newton’s method, also called the Newton–Raphson method, as described by Fisher [3], is the process of
making approximations xk+1, which are the intersections, with the x axis, of the tangent line to the curve
y = f(x) at the point (xk, f(xk)), for k = 0, 1, …. The equation of the tangent line at (xk, f(xk)) is
y = f ( xk ) + ( x − xk ) f ʹ( xk ) (22.9)
and since the point (xk+1, f(xk+1)) lies on the x axis, the earlier equation may be written as
0 = f ( x k ) + ( x k +1 − x k ) f ʹ ( x k ) (22.10)
610 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
to yield an expression in terms of the function and its derivative for xk+1, i.e., Newton’s method,
f ( xk )
x k +1 = x k − (22.11)
f ʹ( xk )
for k = 0, 1, 2, …, given that f′(xk) ≠ 0 [3]. The convergence of Newton’s method is dependent on
the choice of the initial root x0: if x0 is too far away from the actual root xr, then the method may
not converge, or if the tangent lines to the curve result in an oscillation between consecutive values
of xk, then the method may enter an infinite cycle and not progress to the root.
Newton’s method, as Fisher [3] indicates, may also be generalized to n dimensions. A linear
approximation to F(x), e.g., using a Taylor series expansion about xk, is
F ( x) ≅ F ( xk ) + JF ( xk )( x − xk ) (22.12)
where JF(x) is the Jacobian matrix of F(x) at x, consisting of the first partial derivatives ∂Fi/∂xj that
are assumed to exist, where i, j ∈ [1, n]. In a similar manner to the scalar case, if at xk+1, F(xk+1) = 0,
then Newton’s method in n dimensions is
JF ( xk )( xk +1 − xk ) = − F ( xk ) (22.13)
This is a linear system of the form Aq = b, where A = JF(xk), q = (xk+1 − xk), and b = −F(xk), and
consists of n equations in n unknowns and may be solved for xk+1 = q + xk, since the initial value, a
guess xk∣k=0, is known and q = A−1b.
⎡ x0 ⎤ ⎡ f0 (t ) ⎤ ⎡ x0 ⎤ ⎡ mt + c ⎤
⎢ x ⎥ ⎢ f ( x) ⎥ ⎢ x ⎥ ⎢ x + x ⎥
⎢ 1⎥ − ⎢ 1 ⎥ = ⎢ 1⎥ − ⎢ 0 3⎥
= x − f ( x) = F( x) = 0 (22.14)
⎢ x2 ⎥ ⎢ f2 ( x)⎥ ⎢ x2 ⎥ ⎢ k2 x1 ⎥
⎢ ⎥ ⎢ ⎥ ⎢ ⎥ ⎢ ⎥
⎣ x3 ⎦ ⎣ f3 ( x) ⎦ ⎣ x3 ⎦ ⎣ k3 x2 ⎦
where the function f0(t) is linear in time t with gradient m and f0(0) = c (the vector x is strictly a
function of time, but for ease of comparison with the development mentioned earlier, t is omitted for
Equations 22.14 through 22.16) and the Jacobian of the function F(x) at x = xk is
⎡1 0 0 0⎤
⎢ −1 1 0 −1⎥⎥
JF ( xk ) = ⎢
⎢0 − k2 1 0⎥
⎢ ⎥
⎣0 0 − k3 1⎦
Preparation for Feedback-Based Signal Propagation 611
The system
JF ( xk ) xk +1 = JF ( xk ) xk − F ( xk )
= JF ( xk ) xk − ( xk − f ( xk )) (22.15)
may be written as
which is the same as (22.2), and, hence, regardless of the choice of xk, the solution xk+1 of (22.14) is
obtained in one Newton iteration. Note that here, the notation xj,k represents the jth component of
the vector x at the kth iteration, for j ∈ [0, s −1].
So for models that involve both linear and nonlinear functions, the most general approach to their
computation is to structure them in the form
and to compute them using a nonlinear iterative scheme such as Newton’s method or a similar glob-
ally convergent method.
22.4 MODEL ASSUMPTIONS
The system of equations represented by (22.17) is one that involves s-output-signal-producing func-
tions fi(x(t)) and s and unknowns xi(t) for i ∈ [0, s − 1] and is typically written in vector form. This
suggests that the vector of unknowns x(t) is composed of either component scalars or vectors, and
the corresponding function f(x(t)) is composed of component scalar or vector functions, respec-
tively. If the block operations are scalar or vector functions producing scalar or vector output sig-
nals, then a procedure for computing the vector-based equation (22.17) can be used. However, if this
is not the case, then an alternative method should be pursued.
respectively, where x(t), u(t), and y(t) are the state vector (n × 1), control/input vector (r × 1), and
output vector (m × 1), respectively, and A(t), B(t), C(t), and D(t) are the plant/state matrix (n × n), con-
trol/input matrix (n × r), output matrix (m × n), and direct transmission matrix (m × r), respectively
[1,4]. If ẋ(t) and y(t) do not involve time explicitly, then Equation 22.18 involves matrices A, B, C,
and D that are not functions of time and is then a time-invariant system [1]. The corresponding block
diagram representation of the state and output equation (22.18) is shown in Figure 22.2.
612 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
A(t)
2
+ y(t)
u(t) B(t) + ∫ dt C(t) + Out
x(t) x(t) +
0 1 3 4 6 7 8
D(t)
FIGURE 22.2 Block diagram representation of the state and output equations (22.18) where all block
operations are vector functions and output signals are (column) vectors. (From Ogata, K., Modern Control
Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.)
The reader will notice that all input and output signals of the diagram entering or emanating
from blocks are column vectors: u(t) (r × 1), ẋ(t) (n × 1), x(t) (n × 1), and y(t) (m × 1). The gain opera-
tions, through matrices A(t), B(t), C(t), and D(t), are all applied to their input vectors as shown in
(22.18) and, hence, result in vector output. In this form, the system represented by (22.17) can be
constructed as follows:
⎡ q0 (t )⎤ ⎡ u(t ) ⎤
⎢ q (t ) ⎥ ⎢ B(t )u(t ) ⎥
⎢ 1 ⎥ ⎢ ⎥
⎢ q2 (t )⎥ ⎢ A(t ) x(t ) ⎥
⎢ ⎥ ⎢ ⎥
q3 (t ) ⎥ ⎢ x (t )
F ( q(t )) = q(t ) − f ( q(t )) = ⎢ − ⎥=0 (22.19)
⎢ q4 (t )⎥ ⎢ x(t ) ⎥
⎢ ⎥ ⎢ ⎥
⎢ q5 (t ) ⎥ ⎢ D(t )u(t )⎥
⎢ q (t )⎥ ⎢ C (t ) x(t ) ⎥
⎢ 6 ⎥ ⎢ ⎥
⎢⎣ q7 (t )⎥⎦ ⎢⎣ y(t ) ⎥⎦
where x(t) in (22.17) is replaced by the generalized output signal vector q(t) to avoid confusion with
the state vector x(t) used in (22.18) and is composed of components qi(t), where the index i is that of
a block that produces an output signal (here i ∈ [0, 7], since block eight does not produce an output
signal and, hence, is not part of the system of equations). This approach allows the system (22.18),
cast as a set of 4n + 3m + r simultaneous equations (22.19), to be computed for the output signal
vector q(t) using Newton’s method.
However, as far as the state equations are concerned, alternative methods exist for their computa-
tion. The time-invariant (A, B ≠ f(t)) nonhomogeneous state equation is
and Ogata [1] shows that the solution to this may be written as
x(t ) = e A( t − t0 ) t
∫
x(t0 ) + e A( t − τ ) Bu(τ)d τ
t0
(22.21)
Preparation for Feedback-Based Signal Propagation 613
for an initial time t0. The interested reader will appreciate the clarity with which Ogata presents his
material, in particular, the consideration of the scalar case prior to the derivation of the aforemen-
tioned result (22.21). The scalar homogeneous equation considered is
x = ax + bu (22.22)
t
at
x (t ) = e x (0 ) + e at
∫e
0
− aτ
bu(τ)d τ (22.23)
Once x(t) is determined, the computation of the output equation (22.18b) may then proceed, since
now both the state vector x(t) and input vector u(t) are known. In which case, casting the system in
the form (22.19) and computing this using Newton’s method would be unnecessary.
The reason why the Newton-method-based approach of iterative computation is introduced here
is that a system of equations (represented by a block diagram) in general may not conform to a
particular structure as is present in the linear nonhomogeneous state equation and output equation.
Hence, a general method is required to compute the output signals q(t) as a result of block operations
f(q(t)) that may not necessarily be time invariant or even linear: the most general method would be
one applicable to a non-memoryless, nonlinear, and time-variant dynamical system.
void CSystemModel::OrderBlockList()
{
int k;
int chk_list = 0; // flag to check blocks in block list
CString blk_name; // block name
CString sMsg; // string to be displayed
CString sMsgTemp; // temp string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg
list <CBlock *>::iterator it_blk; // block list iterator
list <CBlock *>::reverse_iterator it_blk_r; // reverse iterator
list <CBlock *>::iterator it_tmp; // temporary iterator
614 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Check blocks
if(chk_list == 1)
{
sMsgTemp.Format(“\n CSystemModel::OrderBlockList(). \n”);
sMsg += sMsgTemp;
for(it_blk = m_lstBlock.begin(); it_blk != m_lstBlock.end();
it_blk++)
{
blk_name = (*it_blk)->GetBlockName();
sMsgTemp.Format(“%s \n”, blk_name);
sMsg += sMsgTemp;
}
AfxMessageBox(sMsg, nType, nIDhelp);
}
// Place the Output blocks at the back of the list (retaining their
original placement order)
Preparation for Feedback-Based Signal Propagation 615
Three main for loops are executed in the OrderBlockList() function. The first loop iter-
ates through the block list and determines the number of Source and Output blocks and updates
the class member variables appropriately. The second loop iterates over the number of Source
blocks and searches the block list in reverse using a reverse iterator, once for each Source block,
to locate the block and then place it at the beginning (begin()) of the list, using the overloaded
version of the splice() function that takes three arguments: the destination position (the front
of the list, begin()), the list (m_lstBlock), and the source position (it_blk) [5,6]. The function
base() is called on the reverse iterator, since the splice() function takes an iterator argument
rather than a reverse-iterator argument, and base() returns the base iterator, i.e., the element
in the list next to the reverse iterator. However, the element is one position-offset less than it
should be in the reverse direction, and, hence, “tmp--” is required for alignment purposes. The
third loop iterates (forward) over the number of Output blocks and searches the block list, once
for each Output block, to locate the block and then place it at the end (end()) of the list. Hence,
after this process is complete, the Source and Output blocks are grouped at the front and back
of the list, respectively, maintaining their order, and this may be verified using the list-checking
conditional statements.
Reordering of the block list is only necessary prior to model execution or signal propagation
when feedback loops exist, as will be shown later. However, for consistency reasons, i.e., having
an ordered block list for both the direct signal propagation and feedback-loop-based signal propa-
gation mechanisms, this reordering should take place at the model validation stage, i.e., within
616 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
int CSystemModel::ValidateModel()
{
int i;
…
// -- REORDER BLOCK LIST: Source, I/O, Output
OrderBlockList();
// -- VALIDATE SYSTEM MODEL
error1 = ValidateModelBlocks();
error2 = ValidateModelBlockPorts();
error3 = ValidateModelConnections();
…
}
1. Add two private member variables to the CSystemModel class to record the different types
of tours: (1) list<int*> m_lstSourceOutputTour and (2) list<int*> m_lstSourceLoopTour.
2. Edit the SaveTourVector() function as shown in the following to add the integer array,
representing the block-to-block tour, to the appropriate list.
{
int i;
int n_repeats = 0; // number of repeated entries
int *tour_vec_copy = NULL; // copy of the incoming tour_vec
CString sMsg; // string to be displayed
CString sMsgTemp; // temp msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
Preparation for Feedback-Based Signal Propagation 617
// MEMORY NEW
tour_vec_copy = new int[ncols];
PrintVector(tour_vec, ncols);
return;
}
The developer will notice that a copy of the incoming “tour_vec” integer array is made, since in the
calling environment, memory for “tour_vec” is allocated and deleted and, hence, can’t be placed in
the member variable lists to be stored persistently.
Now that the block-to-block tours are recorded as an array of integers and these arrays stored
in the appropriate tour list, these lists need to have their contents deleted and then be subsequently
cleared upon each new rebuilding of the block diagram model through ValidateModel(). Hence,
the following steps are to be performed:
1. Add a public member function to the CSystemModel class with the prototype
void CSystemModel::DeleteSourceOutputLoopLists().
2. Edit the function as shown in the following to iterate through the lists, delete the integer
arrays, and finally clear the entire list.
618 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CSystemModel::DeleteSourceOutputLoopLists()
{
list <int *>::iterator it_int;
// Delete contents of m_lstSourceOutputTour
for(it_int = m_lstSourceOutputTour.begin(); it_int
!= m_lstSourceOutputTour.end(); it_int++)
{
delete [] *it_int;
}
m_lstSourceOutputTour.clear();
// Delete contents of m_lstSourceLoopTour
for(it_int = m_lstSourceLoopTour.begin(); it_int
!= m_lstSourceLoopTour.end(); it_int++)
{
delete [] *it_int;
}
m_lstSourceLoopTour.clear();
return;
}
3. Make a call to this function from within the ValidateModel() function, prior to the
call to DetermineModelFeedbackLoops(), and in the CSystemModel() destruc-
tor, as shown in bold in the following.
int CSystemModel::ValidateModel()
{
int i;
…
// -- INFORM USER OF END OF BUILD MODEL STAGE
if( (error1 == 1) || (error2 == 1) || (error3 == 1) )
{
…
}
else
{
sMsgTemp.Format(“\n CSystemModel::ValidateModel() \n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“\n No build errors. \n”);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp);
// Delete any saved source-to-output or source-to-loop tour block
lists
DeleteSourceOutputLoopLists();
// Determine if the model has feedback loops, i.e. source-to-loop
tours
m_iNSourceLoopTours = 0;
m_iNSourceOutputTours = 0;
DetermineModelFeedbackLoops();
if(m_iNSourceLoopTours > 0)
{
sMsg.Format(“\n Note! The number of source-to-loop tours in
the model is: %d. \n”, m_iNSourceLoopTours);
Preparation for Feedback-Based Signal Propagation 619
{
cnt = 0;
for(it_int = m_lstSourceLoopTour.begin(); it_int
!= m_lstSourceLoopTour.end(); it_int++)
{
if(cnt == i)
{
// Dereference the iterator to return the integer
array held in the tour list
return (*it_int);
}
cnt++;
}
}
}
// Return a null pointer denoting an error regarding tour_type.
return NULL;
}
The GetTourVector() function returns an array of integers denoting the block numbers that were
traversed when forming either the Source-to-Output-block tour or the Source-to-loop-repeated-block
tour, where the tours themselves are stored in their corresponding lists: “m_lstSourceOutputTour” and
“m_lstSourceLoopTour”, respectively. The “tour_type” argument denotes the type of tour (“0” and
“1” denote a Source-to-Output-block and a Source-to-loop-repeated-block tour, respectively) and the
offset integer “i” denotes the ith tour stored in the particular list. The iterator is dereferenced to access
the integer array stored in the list. If a tour cannot be correctly found, then a NULL pointer is returned.
// Check blocks
if(chk_list == 1)
{
sMsgTemp.Format(“\n CSystemModel::ConvertIntegerTourToBlock
List(). \n”);
sMsg += sMsgTemp;
for(it_blk = tour_blk_list.begin(); it_blk != tour_blk_list.end();
it_blk++)
{
blk_name = (*it_blk)->GetBlockName();
sMsgTemp.Format(“%s \n”, blk_name);
sMsg += sMsgTemp;
}
AfxMessageBox(sMsg, nType, nIDhelp);
}
return;
}
622 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
valid = (*it_con)->GetSignal()->GetSignalValidity();
if(valid == 0) // if invalid, return valid = 0
{
return valid;
}
}
return valid; // if valid, return valid = 1
}
int CSystemModel::PreliminarySignalPropagation()
{
int i;
int j;
int valid = 0; // valid: (0) invalid, (1) valid
int t_cnt = 0; // time count
int tour_type; // (0) source-to-output,
(1) source-to-loop
int *tour_vec = NULL; // tour_vec ptr-to-int to receive
the tour_vec stored in a list
double delta_t = m_dTimeStepSize; // time step size delta_t = h.
double t = 0.00; // current time point.
double t_init = m_dTimeStart; // time initial
CStringsMsg; // string to be displayed
CString sMsgTemp; // temp msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CBlock*> tour_blk_list; // list of ptrs-to-block within a
block tour
list<CBlock*>::iterator it_blk; // blk list iterator
list<CBlock*>::iterator prev_blk; // blk list iterator of the
previous block in the list
624 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
//sMsgTemp.Format(“\n PreliminarySignalPropagation()\n”);
//sMsg += sMsgTemp;
// ITERATE THROUGH ALL SOURCE-TO-OUTPUT-BLOCK TOURS
tour_type = 0;
for(i=0; i<m_iNSourceOutputTours; i++)
{
// Get the tour vector and convert it into a block list
tour_vec = GetTourVector(tour_type, i);
ConvertIntegerTourToBlockList(tour_vec, tour_blk_list);
it_blk = tour_blk_list.begin(); // init. iterator to be at
the start of the list
// ITERATE THROUGH THE LIST A NUMBER OF TIMES = size - 1.
for(j = 1; j < tour_blk_list.size(); j++)
{
valid = (*it_blk)->CheckBlockOutputSignalDataValidity();
//sMsgTemp.Format(“%d, %d, %s, valid = %d\n”, tour_type, i,
(*it_blk)->GetBlockName(), valid);
//sMsg += sMsgTemp;
if(!valid) // if there does not exist output signal data
{
if( (*it_blk)->GetBlockName() == “sum_block”) // skip sum
blocks but transfer signals directly
{
prev_blk = it_blk;
prev_blk--;
TransferSignalDataToAnotherConnection(*prev_blk,
*it_blk);
valid = 1;
}
else if( (*it_blk)->GetBlockName() == “divide_block”)
// skip divide blocks since output signal data already
provided by user
{
// Output signal data should already have been
entered by the user.
valid = 0;
sMsgTemp.Format(“\n PreliminarySignalPropagation()\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n WARNING! Divide block has
unspecified output signal!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“Please manually enter initial block
output signal data!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“Aborting signal propagation!\n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
return valid; // abort fn
}
else // Perform OperateOnData() to generate the output
signal data
{
t = t_init + t_cnt*delta_t;
valid = (*it_blk)->OperateOnData(t_cnt, t, delta_t);
Preparation for Feedback-Based Signal Propagation 625
}// end j
}// end i
{
// Output signal data should already have been
entered by the user.
valid = 0;
sMsgTemp.Format(“\n PreliminarySignalPropagation()
\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n WARNING! Divide block has
unspecified output signal!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“Please manually enter initial block
output signal data!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“Aborting signal propagation!\n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
return valid; // abort fn
}
else // Perform OperateOnData() to generate the output
signal data
{
t = t_init + t_cnt*delta_t;
valid = (*it_blk)->OperateOnData(t_cnt, t, delta_t);
if(valid <= 0) // OPERATIONAL ERROR: ABORT SIGNAL
PROPAGATION
{
sMsgTemp.Format(“\n PreliminarySignalPropagation()
\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n WARNING! Unsuccessful (non-
Output) block operation!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“Aborting signal propagation!\n”);
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
valid = 0;
return valid; // abort fn
}
}
}// end valid
}// end j
}// end i
// Check preliminary signal propagation
//AfxMessageBox(sMsg, nType, nIDhelp);
return valid;
}
Two for loops are used: the first iterates through the list of Source-to-Output-block tours
and the second iterates over the list of Source-to-loop-repeated-block tours, stored respectively,
in “m_lstSourceOutputTour” and “m_lstSourceLoopTour”. The integer tour array “tour_vec” is
obtained via a call to GetTourVector(), and this is converted to a block list through a call to
ConvertIntegerTourToBlockList().
For each Source-to-Output-block tour or Source-to-loop-repeated-block tour, a for loop is exe-
cuted to iterate n − 1 times, where n is the number of blocks in the list: this is done since a final
Output block does not generate an output signal and a final loop-repeated block would already have
been encountered earlier in the tour and its output signal data generated. Hence, the final block of
a tour is not operated upon since the output data are not required. The block list iterator is incre-
mented at the end of each for loop.
If the current block already has valid output-connection-based CSignal data, then no action
is required to generate a signal, otherwise, three conditional clauses are processed: (1) if
the current block is a Sum block, then output signal data from the previous block are simply
transferred to the output signal data of the Sum block, since the dimensionality of the data
would be unchanged; (2) if the block is a Divide block, then user-specified output signal data
should already have been explicitly entered, and, hence, an error message is generated; and
(3) for all other blocks, the OperateOnData() function is called and the validity of the opera-
tion assessed.
The developer will have noticed the call CheckBlockOutputSignalDataValidity() in the
function mentioned earlier. The purpose of the CheckBlockOutputSignalDataValidity()
function in the context of preliminary signal propagation (PreliminarySignalPropagation())
is to determine whether preliminary signal data already exist for an output-connection object of a
particular block. If so, then the block’s OperateOnData() function need not be executed and
signal generation/propagation can progress to the next block in the block list, and if not, then
OperateOnData() should be executed to generate output-connection-based CSignal data.
Hence, add a public member function to the CBlock class with prototype int CBlock::
CheckBlockOutputSignalDataValidity(void). Edit the function as shown to check
output-connection-based CSignal member validity.
int CBlock::CheckBlockOutputSignalDataValidity()
{
int valid = 0; // (0) invalid, (1) valid
list<CConnection*>::iterator it_con; // iterator for list of
CConnection ptrs.
vector<CPort*>::iterator it_port; // iterator for vector of CPort
ptrs.
vector<CPort*> vector_of_output_ports = GetVectorOfOutputPorts();
// vector of output ports
// Get connection list
list<CConnection*> &con_list = m_pParentSystemModel-
>GetConnectionList();
// -- CHECK OUTPUT CONNECTION SIGNAL DATA VALIDITY
// Iterate over output ports
for(it_port = vector_of_output_ports.begin(); it_port
!= vector_of_output_ports.end(); it_port++)
{
// Iterate over list of connections
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
// If connection attached to output port: either directly or
INDIRECTLY through a bend point
628 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
if(valid == 0)
{
return valid;
}
}
}// end it_con
}// end it_port
return valid;
}
Two for loops exist in the previous code to iterate over the block’s vector of output ports
“vector_of_output_ports” and the model’s connection list, stored locally in “con_list”, and the con-
ditional statement determines the correct output-connection object whose CSignal-based data valid-
ity is checked and its status (“valid”) returned.
The other function to be added to the project that is called from within the
PreliminarySignalPropagation() function provided earlier is TransferSignalData
ToAnotherConnection(), which transfers signal data directly from one connection object to
another, without the need to execute the intervening block’s OperateOnData() function to gen-
erate the connection-based output CSignal data.
Add a public member function to the CSystemModel class with the prototype
int CSystemModel::TransferSignalDataToAnotherConnection(CBlock *ptr_
blk1, CBlock *ptr_blk2). Edit the function as shown in the following to transfer data from
one block’s connection object to another.
{
matrix_data = (*it_con)->GetSignal()-
>GetSignalData(&nrows, &ncols); // get signal data
from output connection
break;
}
}
}
// Transfer the output matrix signal data of block 1 to the output
signal of block 2.
valid = ptr_blk2->WriteDataToOutputSignal(matrix_data, nrows, ncols);
return valid;
}
Two pointers-to-CBlock, “ptr_blk1” and “ptr_blk2”, are passed into the aforementioned function,
where the output-connection-based CSignal data of the first block, referred to by “ptr_blk1”, are trans-
ferred to the output-connection object of the second block, referred to by “ptr_blk2”. The iteration over
the list of block ports and the model’s list of connections is required to determine the output-connection
object of the first block from which the output signal data may be retrieved. Thereafter, the output sig-
nal data are written directly to the output-connection-based CSignal data of the second block.
Usually, the two blocks referred to by “ptr_blk1” and “ptr_blk2” would be found in sequence,
where the first is a non-Sum block and the second a Sum block, all of whose input signals may not
be known, hence not allowing the OperateOnData() function to work properly. Hence, an initial
“dummy” output signal can be copied across directly and forms the initial guess, xi,k∣k=0, for the ith
block, where i ∈ [0, s − 1].
TABLE 22.1
Output Signal Dialog Window Controls
Object Property Setting
Static Text ID ID_OUTPUT_SIG_DLG_TXT
Caption Enter initial output signal scalar or vector
Edit Box ID ID_OUTPUT_SIG_DLG_VALUE
Multiline Checked
Horizontal scroll Checked
Vertical scroll Checked
Want return Checked
Button ID ID_OUTPUT_SIG_DLG_BTN_OK
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 22.3 Output Signal Dialog window showing the controls as specified in Table 22.1.
Preparation for Feedback-Based Signal Propagation 631
TABLE 22.2
Dialog Window Controls, Variable Name, Category, and
Type for the IDD_INIT_OUTPUT_SIG_DLG Dialog Resource
Control Variable Name Category Type
ID_OUTPUT_SIG_DLG_VALUE m_strOutputSignal Value CString
TABLE 22.3
Objects, IDs, Class, and Event-Handler Functions for the COutputSignalDialog Class
COMMAND
Object ID Class Event-Handler
OK button ID_OUTPUT_SIGNAL_DLG_BTN_OK COutputSignalDialog OnOutputSignalDlgBtnOk()
Cancel button IDCANCEL (default) COutputSignalDialog OnCancel()
void COutputSignalDialog::OnOutputSignalDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
//AfxMessageBox(“\n COutputSignalDialog::OnOutputSignalDlgBtnOk()\n”,
MB_OK, 0);
// Update variable values with the Dlg Wnd control values
UpdateData(TRUE);
// Close the dialog wnd with OnOK()
OnOK();
// DiagramEng (end)
}
1. Initialize this variable in the CSignal class’ constructor as shown in bold in the following
to be empty, since the initial signal is invalid.
2. The ConvertStringToDouble() function should not be called to convert the CString
value to a double signal matrix, since the initial signal is invalid and of size zero, as indi-
cated by the number of rows and columns of the signal data matrix, “m_iNrows” and
“m_iNcols”, respectively.
632 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
CSignal::CSignal(void)
{
// Init.
m_iNrows = 0; // no initial output signal data
hence zero rows
m_iNcols = 0; // no initial output signal data
hence zero cols
m_iValidity = 0; // initial invalid signal
m_strSignalName = “signal_name”; // default place-holder name
m_strOutputSignal = “”; // no initial output signal data
hence empty string
m_dSignalMatrix = NULL; // no initial output signal data
hence NULL matrix
}
void CSignal::ResetSignalData()
{
int i;
// MEMORY DELETE
// Delete the arrays held at each of the entries of the column vector
if(m_dSignalMatrix != NULL)
{
for(i=0; i<m_iNrows; i++)
{
delete [] m_dSignalMatrix[i];
}
delete [] m_dSignalMatrix;
}
// -- RESET VARS
m_iNrows = 0;
m_iNcols = 0;
m_iValidity = 0;
m_strOutputSignal = “”; // rqd as a result of introducing a
COutputSignalDialog object into the project.
m_dSignalMatrix = NULL;
return;
}
TABLE 22.4
Context Menu Settings: IDs, Captions, and Prompts
with the New IDM_SET_OUTPUT_SIGNAL Entry
ID Caption Prompts: Status Bar and Tooltips
IDM_DELETE_ITEM &Delete Item Delete selected item\nDelete selected item
ID_EDIT_DELETE_GROUPED_ITEMS Delete &Grouped Items Delete items enclosed by
rectangle\nDelete grouped items
IDM_FINE_MOVE_ITEM &Fine Move Item Fine movement of item\nFine movement
of item
IDM_INSERT_BEND_POINT &Insert Bend Point Insert bend point\nInsert bend point
IDM_REVERSE_BLOCK_DIREC &Reverse Block Reverse block direction\nReverse block
direction
IDM_SET_OUTPUT_SIGNAL Set &Output Signal Set block-output connection-based
signal\nSet output signal
IDM_SET_ITEM_PROPERTIES &Set Properties Set item properties\nSet item properties
void CDiagramEngDoc::OnSetOutputSignal()
{
// TODO: Add your command handler code here
// DiagramEng (start)
double dist; // Euclidean dist bw. block posn and point posn.
double width; // block width
CPoint blk_posn; // block posn.
CPoint point; // local point var.
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CBlock*>::iterator it_blk; // iterator
// Print msg.
//sMsg.Format(“\n CDiagramEngDoc::OnSetOutputSignal(),
point (x,y) = (%d,%d)\n”, point.x, point.y);
//AfxMessageBox(sMsg, nType, nIDhelp);
634 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// DiagramEng (end)
}
The selection of the Set Output Signal Context menu entry results in the OnSetOutputSignal()
function being called. The purpose of this function is to determine the block whose output-con-
nection-based CSignal data are to be set. A for loop iterates through the block list, and the condi-
tional statements determine whether the mouse cursor position lies over a Divide block (at present,
this function should be called only for the Divide block, but this may change in future); if so, the
AssignBlockOutputSignal() function is called to specifically set the output connection’s
CSignal data.
1. Add a public virtual member function to the CBlock class with the prototype virtual
void CBlock::AssignBlockOutputSignal(void).
2. Edit the function as shown in the following to perform the five key data transfer steps
earlier.
3. Include the header file, “OutputSignalDialog.h”, towards the top of the “Block.cpp” source
file, beneath the statement, include “TransferFnBlockDialog.h”. This is required since a
COutputSignalDialog dialog object is created within AssignBlockOutputSignal().
Preparation for Feedback-Based Signal Propagation 635
void CBlock::AssignBlockOutputSignal()
{
int i;
int attached = 0; // flag denoting the attachment
of an output connection to a block output port
int input = 0; // input: (0) incomplete,
(1) complete
int ncols; // no. of cols of data matrix
int nrows; // no. of rows of data matrix
int valid; // used to set signal validity
double **matrix = NULL; // data matrix
CSignal *signal = NULL; // local ptr-to-signal
CString sMsg; // string msg. to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CConnection*>::iterator it_con; // iterator for list of
CConnection ptrs.
vector<CPort*>::iterator it_port; // iterator for vector of
CPort ptrs.
vector<CPort*> vector_of_output_ports = GetVectorOfOutputPorts();
// vector of output ports
return;
}
}
// SET REF-FROM-PORTS OF CONNECTIONS THAT EMANATE FROM BEND POINTS.
// These ref-from-ports are then RESET to NULL at the end of the fn
(since they don’t actually emanate from a PORT).
m_pParentSystemModel->SetRefFromPortOfConnectionFromBendPoint();
// Create a dlg obj. of class COutputSignalDialog : public CDialog
COutputSignalDialog oDlg;
// GET OUTPUT SIGNAL
// Iterate over output ports
for(it_port = vector_of_output_ports.begin(); it_port
!= vector_of_output_ports.end(); it_port++)
{
// Iterate over list of connections
for(it_con = con_list.begin(); it_con != con_list.end();
it_con++)
{
// If connection attached to output port: either directly or
INDIRECTLY through a bend point
if( (*it_con)->GetRefFromPort() == (*it_port) )
{
// GET THE ptr-to-CSignal OBJECT OF THE OUTPUT CONNECTION.
// Note: there could be more than one CConnection object
with its CSignal data connected
// to the current block’s output port. But here the first
CSignal object is retrieved,
// since all CSignal data on CConnection objects
associated with the current output port
// will have identical data. Below, an update of all
CConnection objects whose reference-from
// port is the current output port will have their
CSignal data updated individually.
signal = (*it_con)->GetSignal();
break;
}
}
}
// Set the COutputSignalDialog var using the CSignal var
oDlg.m_strOutputSignal = signal->GetOutputSignalString();
// WHILE NOT CORRECT INPUT
while(input == 0)
{
// DISPLAY DIALOG TO USER USING DoModal() FN
// Return val of DoModal() fn of ancestor class CDialog is
checked to determine which btn was clicked.
if(oDlg.DoModal() == IDOK)
{
// Assign COutputSignalDialog variable values to CSignal
variable values.
signal->SetOutputSignalString(oDlg.m_strOutputSignal);
// Convert the input string into a double matrix and assign
it below.
Preparation for Feedback-Based Signal Propagation 637
input = 0;
}
else // Non-NULL input
{
// UPDATE ALL CONNECTION OBJECTS WHOSE REFERENCE-FROM
PORT IS THAT OF THE CURRENT BLOCK’S OUTPUT PORT
valid = WriteDataToOutputSignal(matrix, nrows, ncols);
// Matrix is non-NULL implying valid user input.
input = 1;
// MEMORY DELETE
if(matrix != NULL)
{
for(i=0; i<nrows; i++)
{
delete [] matrix[i];
}
delete [] matrix;
matrix = NULL;
}
}
Initially, the model’s connection list is retrieved, and the vector of output ports and list of connec-
tions are iterated over to determine if all output ports (at this stage, there is only one output port
per block) have connections attached. A CConnection object contains a CSignal object, and the
absence of the former precludes the latter from being assigned or retrieved, and, hence, the function
would return.
The developer will recall that more than one CConnection object may share the same “reference-
from” port of a block: a primary connection may be directly attached to the output port, and a
secondary connection may be connected to a primary connection through a connection bend
point. Hence, the “reference-from” ports (“m_pRefFromPort”) of connections that emanate from
bend points are set to their parent connection’s “from” port (“from_port”), such that when assign-
ing an output signal of a particular block, all direct (primary) and indirect (secondary) connec-
tion objects have consistent signal data. At the end of the function, these secondary connection
“reference-from” ports are then assigned to NULL to leave the program state unchanged, since
secondary connection objects (attached to bend points) are not physically and directly attached to
output ports.
A COutputSignalDialog object “oDlg” is then instantiated: but before the CSignal object’s
“m_strOutputSignal” CString variable can be assigned to the CString variable (of the same name)
of the COutputSignalDialog dialog class, the correct signal object must be retrieved. Hence,
nested for loops and a conditional statement determine the output-connection object and its asso-
ciated signal data. Then the output signal CString member variable is obtained through a call to
GetOutputSignalString() and assigned to the CString dialog object.
The while loop then iterates until valid user input is entered. The DoModal() function dis-
plays the dialog window to the user, and the returned value is checked to determine which button
was clicked, using IDOK or IDCANCEL. If OK, the edit box string value is used to initialize the
CSignal’s CString member variable using the accessor function SetOutputSignalString().
The string variable is then converted into a two-dimensional double array through a call to
ConvertStringToDouble(). The conditional section then processes three forms of input:
(1) if the user enters an empty string, “”, then the CSignal data of the connection object are
reset; (2) if the matrix is NULL, then a prompt is issued to reenter signal data; and (3) if
the resultant matrix is non-NULL, then it is used to set the output signal data via a call to
WriteDataToOutputSignal(). The order of the three conditional statements is important
here, since the empty string, “”, results in a NULL matrix, where the desired intent is to reset the
signal data.
The developer will have noticed the introduction of the two accessor functions
GetOutputSignalString() and SetOutputSignalString() used to get and set the
“m_strOutputSignal” CString member variable, respectively. Hence, the public member functions
with the following declarations and definitions need to be added to the CSignal class:
1. CString CSignal::GetOutputSignalString(void)
2. void CSignal::SetOutputSignalString(CString string)
Preparation for Feedback-Based Signal Propagation 639
A B
c=1 k=1 Out
x0 x1
0 1 2
FIGURE 22.4 Simple Constant-Gain-Output model used to test signal assignment with direct signal propa-
gation in the absence of a feedback loop: block numbers appear beneath the blocks.
CString CSignal::GetOutputSignalString(void)
{
return m_strOutputSignal;
}
void CSignal::SetOutputSignalString(CString string)
{
m_strOutputSignal = string;
}
The developer may now test the signal-assignment functionality on a model that does not involve a
feedback loop. For example, consider Figure 22.4, involving a Constant block, generating a constant
signal c = 1, a Gain block with gain constant k = 1, and an Output block used to display the resultant
signals, for a simulation with t0 = 0, tf = 10, and δt = 1.0. The two connections joining the blocks are
labeled A and B, and their corresponding signal values are x0(t) and x1(t), respectively.
Different results will be obtained depending on the order of placement of the connections A and B
on the palette. If the user first places connection B and then connection A on the palette and inter-
actively assigns, e.g., the output signal value, x0(t0) = 5, upon running the simulation, the first output
value y(t0) = 5. This is due to the SignalPropagationDirect() function being a connection-
centric method, where the block associated with the output connection has its OperateOnData()
function executed. So if the first connection is B, then the Gain block is executed prior to the
Constant block. In this example, the output y(t0) = x1(t0) = kx0(t0) = 1 × 5 = 5. However, if the user
first places connection A on the palette followed by connection B, then the first block to have its
OperateOnData() function called is that associated with connection A, i.e., the Constant block,
in which case, x0(t0) = 1 and the output y(t0) = x1(t0) = kx0(t0) = 1 × 1 = 1. In both cases, for t > t0,
y(t) = 1, since x0(t) would be the Constant-block-generated value c = 1 from the previous time-step
and the initial user-entered signal value x0(t0) would no longer be relevant (as expected).
This behavior is consistent and correct as far as the SignalPropagationDirect() method
is concerned, since it is a connection-centric block execution procedure. For signal propagation
involving feedback loops, the purpose of preliminary signal values is to allow the user to specify
the output of a loop-repeated block, e.g., a Divide block, in the absence of knowing the exact values
of its input arguments (as discussed later).
⎡ F0 (x(t )) ⎤ ⎡ x0 (t ) ⎤ ⎡ f0 (x(t )) ⎤ ⎡0 ⎤
⎢ F (x(t )) ⎥ ⎢ x (t ) ⎥ ⎢ f (x(t )) ⎥ ⎢0 ⎥
1 ⎥ = ⎢ 1 ⎥−⎢ 1
F (x(t )) = ⎢ ⎥=⎢ ⎥ (22.24a)
⎢ ⎥ ⎢ ⎥ ⎢ ⎥ ⎢ ⎥
⎢ ⎥ ⎢ ⎥ ⎢ ⎥ ⎢ ⎥
⎣ Fs −1 (x(t ))⎦ ⎣ xs −1 (t )⎦ ⎣ fs −1 (x(t ))⎦ ⎣0 ⎦
640 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
i.e.,
which involves the block-output signal vectors xi(t) and block-operation functions, fi(x(t)), for
i ∈ [0, s − 1], where s is the number of output-signal-generating blocks in the model diagram.
Now the project code is to be extended to complete the previously introduced placeholder function
SignalPropagationSystemOfEquations() and to call various functions introduced ear-
lier to form the system (22.24) and then compute it using an iterative Newton method to determine
the root or zero vector xr(t), i.e., the output signal vector solution that satisfies (22.24) for the model
at the particular time-step t ∈ [t0, tf ] of the simulation.
x7
f7(x)
x5
7
x0 x1 x2 x3 x4
f0(t) f1(x) f2(x) f3(x) f4(x) f5(x) Out
0 1 2 3 4 5 9
x6 f6(x) Out
6 8
FIGURE 22.5 General representation of a model containing two feedback loops with block numbers (i),
functional operations ( fi(x(t))), and output signals (xi(t)) as shown (the variable t is omitted for brevity).
Preparation for Feedback-Based Signal Propagation 641
void CDiagramEngDoc::OnSimStart()
{
…
// Validate model
error_flag = m_SystemModel.ValidateModel();
// Propagate signals
if( (error_flag == 0) && (m_SystemModel.GetNFeedbackLoops() == 0) )
{
m_SystemModel.SignalPropagationDirect();
}
else if( (error_flag == 0) && (m_SystemModel.GetNFeedbackLoops() > 0) )
{
m_SystemModel.SignalPropagationSystemOfEquations();
}
…
}
void CSystemModel::SignalPropagationSystemOfEquations()
{
int n_TimeSteps; // total no. of time steps for
system computation/simulation
int ncols; // total no. of cols of system
equations vector x and fn vec f(x) (1 col)
int nrows; // total no. of rows of system
equations vector x and fn vec f(x)
int t_cnt; // time counter
int valid = 0; // validity of function operation
and of x_vec (x) or f_vec (f(x) ) vector construction
clock_t tv1; // init time for execution time
calc.
clock_t tv2; // final time for execution time
calc.
double delta_t = m_dTimeStepSize;// time step size of system model
simulation
double t; // i-th time point t_i = t
double t_execution; // execution time
double t_init = m_dTimeStart; // time init of system model
simulation
double *x_vec = NULL; // system equations vector x
CString blk_name; // block name
CStringsMsg; // string to be displayed
CString sMsgTemp; // temp msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CBlock*>::iterator it_blk; // iterator for list of CBlock ptrs.
list<CConnection*>::iterator it_con; // iterator for list of
CConnection ptrs.
// MEMORY ALLOCATION
x_vec = new double[nrows]; // memory alloc for system equations
vector x = x_vec
// MEMORY DELETE
delete [] x_vec;
x_vec = NULL;
return;
}
22.7.1.2 Data Dimensions
The second step is the determination of the dimensions of the signal data via a call to the func-
tion DetermineSystemEquationsDataDimensions() (to be introduced) to check if the
data conform to the column vector specification introduced earlier when discussing the state equa-
tions. If the data are not of vector form, then the connection-held CSignal data are reset using
ResetSignalData(): this is done to ensure that PreliminarySignalPropagation() is
called to recompute signal data dimensions upon rebuilding, after any changes to the model by the
user. The number of rows of all the signal vectors xi(t), for i ∈ [0, s − 1], is summed to yield the
total number of system equations (“nrows”); this is then used to allocate memory for the three main
system equation vectors, x(t), f(x(t)), and F(x(t)), present in (22.24).
646 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
22.7.1.5 Newton Solver
The system signal vector x(t) is passed to the NewtonSolver() function, wherein the Newton-
method-based solver is called to obtain the system function vector F(x(t)) = x(t) − f(x(t)), composed
of the system signal vector x(t) and function operation vector f(x(t)). After the Newton iteration is
complete, the root xr(t) of (22.24) is subsequently assigned to x(t).
22.7.1.8 Resetting of States
Before the function returns, the blocks with memory need to have allocated memory deallocated and
some of their variables reset: this is performed via the call to ResetMemoryBasedBlockData()
(introduced in the previous chapter). In addition, signal data need to be reset via a call to
ResetSignalData(), since if a new simulation is started, the old (final) signal data should not be
used as the initial signal values, but rather, new preliminary signals need to be generated based upon
the block dialog window parameter values. Furthermore, the “reference-from” ports of potentially
secondary connections emanating from a primary connection’s bend point are set back to NULL
(since they are not physically connected to a port) to leave the program state as it was prior to entry
into the function. Finally, memory allocated for the vector x(t) is deallocated.
1.DetermineSystemEquationsDataDimensions()
2.DetermineSystemEquationsSignalVector()
3.DetermineSystemEquationsFunctionOperationVector()
4.NetwonSolver()
5.AssignSystemEquationsSignalVector()
// Get the system equations block list, i.e. all non-output blocks.
DetermineSystemEquationsBlockList(equ_blk_list);
// Initialize dimensions
nrows_total = 0;
ncols_total = 0;
// Iterate over the equations-based block list
for(it_blk = equ_blk_list.begin(); it_blk != equ_blk_list.end();
it_blk++)
{
// Iterate over the vector of output ports
vector<CPort*> vector_of_output_ports = (*it_blk)-
>GetVectorOfOutputPorts(); // vector of output ports
for(it_port = vector_of_output_ports.begin(); it_port
!= vector_of_output_ports.end(); it_port++)
{
// Iterate over the list of connections
for(it_con = m_lstConnection.begin(); it_con
!= m_lstConnection.end(); it_con++)
{
// If connection attached to output port: either directly
or INDIRECTLY through a bend point
if( (*it_con)->GetRefFromPort() == (*it_port) )
{
// Get signal data dimensions to make sure signal
output is a column vector
(*it_con)->GetSignal()->GetSignalDataDimensions
(&nrows, &ncols);
if( (nrows >= 1) && (ncols == 1) )
{
nrows_total += nrows;
ncols_total += ncols;
// There is only one signal vector for an
output-port-based-connection object.
// Although other connection objects share the
same ref-from-port, the signal (vector or
scalar)
// associated with these connections is the same.
Hence, break from the “for it_con” loop,
// as soon as the first output-port connection is
found and its signal data dimensions obtained.
valid = 1;
break; // break from “for it_con”
}
else
{
valid = 0;
sMsgTemp.Format(“\n DetermineSystemEquationsData
Dimensions()\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“\n WARNING! All signals should
be column vectors!\n”);
sMsg+=sMsgTemp;
sMsgTemp.Format(“Aborting function!\n”);
Preparation for Feedback-Based Signal Propagation 649
sMsg+=sMsgTemp;
AfxMessageBox(sMsg, nType, nIDhelp); // msg. box
return valid;
}
}
}// end it_con
if(valid == 1)
{
break; // break from “for it_port”
}
}// end it_port
}// end it_blk
return valid;
}
The system equations (22.24), whose dimensionality is to be determined, concern only blocks
that generate output signal data xi(t) for i ∈ [0, s − 1]. Hence, a revised equation-based block list,
“equ_blk_list”, for the purpose of equation construction, is required that contains only non-Output
blocks: this list is obtained by the call to DetermineSystemEquationsBlockList()
(introduced in the following). Thereafter, the list is iterated over and the output-connection-based
CSignal data dimensions are checked and the total system vector dimensions, “nrows_total” and
“ncols_total”, are determined.
Add a public member function to the CSystemModel class with the prototype
void CSystem Model::Deter mineSystem Eq uationsBlockList(list<CBl
ock*> &eq u_blk_list). Edit the code as shown to extract non-Output blocks from the origi-
nal block list “m_lstBlock”.
void CSystemModel::DetermineSystemEquationsBlockList(list<CBlock*>
&equ_blk_list)
{
int chk_list = 0; // flag to control msg.
CString blk_name; // block name
CStringsMsg; // string to be displayed
CString sMsgTemp; // temp string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg
list<CBlock*>::iterator it_blk; // iterator for list of CBlock ptrs.
// Check blocks
if(chk_list == 1)
{
sMsgTemp.Format(“\n CSystemModel::DetermineSystemEquationsBlock
List(). \n”);
650 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
sMsg += sMsgTemp;
for(it_blk = equ_blk_list.begin(); it_blk != equ_blk_list.end();
it_blk++)
{
blk_name = (*it_blk)->GetBlockName();
sMsgTemp.Format(“%s \n”, blk_name);
sMsg += sMsgTemp;
}
AfxMessageBox(sMsg, nType, nIDhelp);
}
}
}
}// end it_con
if(valid == 1)
{
break; // break from “for it_port”
}
}// end it_port
}// end it_blk
return valid;
}
Initially, the equation-based block list, “equ_blk_list”, is obtained, then three for loops exist that
iterate over the equation-based block list, the vector of output ports, and the list of system model
connections to get the data dimensions of the output-connection-based CSignal data before extract-
ing the actual signal data xi(t) and adding it to the system signal vector x(t). A check of the signal
validity and dimensionality is made to ensure that the signal data exist and are in fact a column
vector. If an error in processing the data occurs, “valid” is set to “0” and the function returns.
The developer should be aware that only one signal data matrix exists for an output connection,
although more than one connection may be associated with a single output port: e.g., a secondary connec-
tion attached to a primary connection’s bend point shares the “reference-from” port of the primary con-
nection. Hence, as soon as the primary or secondary connection’s CSignal data are retrieved, the control
flow breaks from the nested loops and continues to the next block in the list to retrieve the next signal xi(t).
int CSystemModel::DetermineSystemEquationsFunctionOperationVector(int
t_cnt, double t, double delta_t, double *x_vec, double *f_vec)
{
int i;
int cnt = 0; // counter used to build up f_vec
int ncols = 0; // number of cols of data signal
int nrows = 0; // number of rows of data signal
int valid = 0; // validity of block operation
double **data_matrix = NULL; // signal data matrix
CStringsMsg; // string to be displayed
CString sMsgTemp; // temp msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CBlock*> equ_blk_list; // a list of ptrs to the Blocks
used to form the simultaneous equations
list<CBlock*>::iterator it_blk; // iterator for list of CBlock
ptrs.
list<CConnection*>::iterator it_con; // iterator for list of
CConnection ptrs.
vector<CPort*>::iterator it_port; // iterator for vector of CPort
ptrs.
Preparation for Feedback-Based Signal Propagation 653
{
// If connection attached to output port: either directly
or INDIRECTLY through a bend point
if( (*it_con)->GetRefFromPort() == (*it_port) )
{
// Check signal validity and get signal data
valid = (*it_con)->GetSignal()->GetSignalValidity();
if(valid == 0)
{
return valid;
}
else
{
// Get signal data dimensions to make sure signal
output is a column vector
(*it_con)->GetSignal()->GetSignalDataDimensions
(&nrows, &ncols);
if( (nrows >= 1) && (ncols == 1) )
{
// The signal data is that generated by
OperateOnData() which called
WriteDataToOutputSignal()
data_matrix = (*it_con)->GetSignal()-
>GetSignalData(&nrows, &ncols);
// Build up the total functional operation
vector
for(i=0; i<nrows; i++)
{
f_vec[cnt] = data_matrix[i][0];
cnt++;
}
}
else
{
valid = 0;
return valid;
}
// Only one block operation concerning the output
connection
break; // break from “for it_con”
}
}
}// end it_con
if(valid == 1)
{
break; // break from “for it_port”
}
}// end it_port
}// end it_blk
return valid;
}
Preparation for Feedback-Based Signal Propagation 655
The functional operation vector is obtained by allowing each block to execute its
OperateOnData() function given the input signal vector x(t). The OperateOnData() func-
tion calls the WriteDataToOutputSignal() function to write the result of the individual block
operation, fi(x(t)), to the output connection’s CSignal data member “m_pSignal”. Thereafter, this
result is obtained from the block’s output connection via a call to GetSignalData() and
emplaced in the system function vector f(x(t)).
The developer will notice the AssignSystemEquationsSignalVector() call being made
for each iteration of the block list. This is done since the functional operation vector f(x(t)) is based
on the input signal system vector x(t). If the AssignSystemEquationsSignalVector()
signal-assignment function were not called, then the individual block operations would change the
connection-based CSignal data, which would subsequently be operated upon by other blocks (in
the for loop), and the functional operations would then be made upon perturbed signal data rather
than the original incoming vector x(t). Hence, the input arguments to the blocks and upon which the
block operates need to be reset with the original incoming vector x(t).
(*it_con)->GetSignal()->ResetSignalData();
// erase existing signal
valid = (*it_blk)->WriteDataToOutputSignal
(data_matrix, nrows, ncols); // write new
signal data
// MEMORY DELETE
if(data_matrix != NULL) // only delete memory if
it was in fact allocated
{
for(i=0; i<nrows; i++)
{
delete [] data_matrix[i]; // delete the
row arrays
}
delete [] data_matrix; // delete the array
(column vector) holding the row arrays
data_matrix = NULL;
}
if(valid == 0)
{
return valid;
}
}
else
{
valid = 0;
return valid;
}
// Only one signal data matrix exists for the output
connection although more than one connection
// may emanate from the same output port due to
ref-port updating, in which case the signal data
// is duplicated for all relevant connections as
performed in WriteDataToOutputSignal().
break; // break from “for it_con”
}
}// end it_con
if(valid == 1)
{
break; // break from “for it_port”
}
}// end it_port
}// end it_blk
return valid;
}
Initially, the equation-based block list that contains all blocks with output signals is obtained, and
then for each block, the vector of output ports and the list of connections are iterated over to find the
appropriate block-output-connection object. Then, a signal data dimensionality check is made, fol-
lowed by a memory allocation of a matrix data structure in preparation for the ensuing assignment
of the relevant rows of the vector x(t) to the component connection-based CSignal vector xi(t). A call
to ResetSignalData() is made to delete the existing connection-based CSignal data member
prior to writing the new data to the output signal: this may not delete the data of possible secondary
658 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
connections that share the same data, but in any case, memory is deleted in SetSignalData()
to prevent a memory leak. The call, WriteDataToOutputSignal(), is used to make sure that
all connection objects whose “reference-from” ports are that of the current block’s output port have
their signal data matrix (“m_dSignalMatrix”) updated appropriately (a call to SetSignalData()
is made). Thereafter, the local data matrix object has its memory deallocated.
22.8 STOPPING A SIMULATION
The Newton-method-based solver, when implemented, will be able to compute systems of linear and
nonlinear equations represented by a block diagram, modeling the system being studied. However,
a mechanism is required to stop any simulation prematurely if desired by the user. The following
implementation steps are required in order that the user be able to terminate a simulation essentially
instantaneously:
TABLE 22.5
Function-Call Tree Concerning a Model with at Least One Feedback Loop Beginning
from OnSimStart()
Level 1 Level 2 Level 3 Level 4 Level 5
OnSimStart()
SignalPropagationSystemOfEquations()
GetConnectionList()
SetRefFromPortOfConnectionFromBendPoint()
CheckSystemSignalValidity()
GetSignalValidity()
PreliminarySignalPropagation()
GetTourVector()
ConvertIntegerTourToBlockList()
CheckBlockOutputSignalDataValidity()
GetSignalValidity()
GetBlockName()
TransferSignalDataToAnotherConnection()
GetSignalData()
WriteDataToOutputSignal()
OperateOnData()
DetermineSystemEquationsDataDimensions()
DetermineSystemEquationsBlockList()
GetSignalDataDimensions()
DetermineNTimeSteps()
- - - for(t_cnt) - - -
DetermineSystemEquationsSignalVector()
DetermineSystemEquationsBlockList()
GetSignalValidity()
GetSignalDataDimensions()
GetSignalData()
ResetSignalData()
NewtonSolver()
AssignSystemEquationsSignalVector()
DetermineSystemEquationsBlockList()
GetSignalDataDimensions()
ResetSignalData()
WriteDataToOutputSignal()
OperateOnData()
- - - end for(t_cnt) - - -
ResetMemoryBasedBlockData()
ResetSignalData()
GetRefFromPoint()
SetRefFromPort()
Important functions are shown, some of which are highlighted, and the code between the “for(t_cnt)” and “end
for(t_cnt)” statements is contained in the time-based iterative loop.
660 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CSystemModel::SignalPropagationDirect()
{
int n_TimeSteps; // total no. of time steps for numerical
integration
int out_blk_state = 0; // flag denoting output-block operation
state
int std_blk_state = 0; // flag denoting standard-block operation
state
int t_cnt; // time counter
bool bStop = FALSE; // stop simulation
clock_t tv1; // init time for execution time calc.
clock_t tv2; // final time for execution time calc.
…
// -- MAIN ITERATION TIME LOOP
n_TimeSteps = DetermineNTimeSteps(); // no. of time steps
// -- EXECUTION TIME START
tv1 = clock();
// -- Loop for n_TimeSteps + 1, from t_0 = 0, UP TO AND INCLUDING the
last time point t_n
for(t_cnt = 0; t_cnt <= n_TimeSteps; t_cnt++)
{
t = t_init + t_cnt*delta_t; // t cnt, i.e. i-th time point t_i,
or simply t.
…
// -- ITERATE OVER CONNECTION LIST TO DETERMINE CONNECTION’S
SIGNAL DATA
…
// -- EXECUTE ALL OUTPUT BLOCKS
…
// -- RESET ALL CONNECTION-HELD SIGNAL DATA
…
// RE-OBTAIN A COPY OF THE CONNECTION LIST AT EACH TIME STEP
…
// If a std block operation failed or if there was a read/write
error for the output block, then break.
if( (std_blk_state == -1) || (out_blk_state == 0) )
{
break; // break from for loop
}
// Check if stop button pressed
bStop = CheckStopButtonPressed();
if(bStop)
Preparation for Feedback-Based Signal Propagation 661
{
break;
}
}// end (time)
…
return;
}
void CSystemModel::SignalPropagationSystemOfEquations()
{
int n_TimeSteps; // total no. of time steps for system
computation/simulation
int ncols; // total no. of cols of system equations
vector x and fn vec f(x) (1 col)
int nrows; // total no. of rows of system equations
vector x and fn vec f(x)
int t_cnt; // time counter
int valid = 0; // validity of function operation and of
x_vec (x) or f_vec (f(x) ) vector construction
bool bStop = FALSE; // stop simulation
clock_t tv1; // init time for execution time calc.
clock_t tv2; // final time for execution time calc.
…
// -- MAIN ITERATION TIME LOOP
n_TimeSteps = DetermineNTimeSteps(); // no. of time steps
// -- EXECUTION TIME START
tv1 = clock();
// -- Loop for n_TimeSteps + 1, from t_0 = 0, UP TO AND INCLUDING the
last time point t_n
for(t_cnt = 0; t_cnt <= n_TimeSteps; t_cnt++)
{
t = t_init + t_cnt*delta_t; // t cnt, i.e. i-th time point t_i,
or simply t.
…
// DETERMINE SYSTEM EQUATION VECTOR x_vec (x)
…
// PERFORM NEWTON ITERATION TO DETERMINE THE VECTOR x_vec,
i.e. x, THAT SATISFIES F(x) = x - f(x) = 0
…
// ASSIGN THE RESULTANT SIGNAL VECTOR x_vec TO THE BLOCK OUTPUT
CONNECTION-BASED SIGNAL DATA DIRECTLY
662 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
…
// -- EXECUTE ALL OUTPUT BLOCKS
…
// If a signal data writing operation failed or if there was a
read/write error for the output block, then break.
if(valid == 0)
{
break; // break from “for t_cnt”
}
// Check if stop button pressed
bStop = CheckStopButtonPressed();
if(bStop)
{
break;
}
}// end (time)
…
return;
}
The CheckStopButtonPressed() method is called at the very end of the time-based loop as
it is in the direct signal propagation method and breaks from the loop if the user selects either the
Stop menu entry or toolbar button.
AfxGetApp()->PumpMessage();
// Filter WM_COMMAND event msg’s only
if(msg.message == WM_COMMAND)
{
WORD wNotifyCode = HIWORD(msg.wParam); // notification code:
menu or toolbar = 0
WORD wID = LOWORD(msg.wParam); // item, control, or
accelerator identifier ID_SIM_STOP
//sMsg.Format(“message = %d, wNotifyCode = %d, wID = %d,
ID_SIM_STOP = %d\n”, msg.message, wNotifyCode, wID,
ID_SIM_STOP);
//AfxMessageBox(sMsg, MB_OK, 0);
// If menu entry or toolbar button with ID, ID_SIM_STOP
selected
if(wID == ID_SIM_STOP)
{
//sMsg.Format(“Stopping simulation … \n”);
//AfxMessageBox(sMsg, MB_OK, 0);
bStop = TRUE;
}
}
}
return bStop;
}
Three key actions take place in the CheckStopButtonPressed() function: (1) checking of a
thread message queue for a message using PeekMessage(), (2) forcing the processing of a mes-
sage using PumpMessage(), and (3) filtering the message to determine whether the menu entry or
toolbar button, both sharing the same ID, was selected.
The PeekMessage() function checks a thread message queue for a message and places the
message (if any) in the specified structure [6]. The function takes five arguments: (1) a pointer to a
message structure that receives message information (LPMSG), (2) a handle to the window whose
messages are to be examined (HWND), (3) the value of the first message in the range of messages
to be examined (UINT), (4) the value of the last message in the range of messages to be examined
(UINT), and (5) a message removal flag (UINT) denoting how messages are to be handled [6].
The interested reader should consult Refs. [6,8,9] for further information concerning the
PeekMessage() function and its arguments: all three references have been used here to explain
function details. In the aforementioned code, the HWND argument is NULL, implying that both
window and thread messages will be processed. The message filter arguments denote the range of
message values being filtered: if both are set to zero, PeekMessage() returns all available mes-
sages and no range filtering is performed. The message removal flag is set to PM_NOREMOVE to
indicate that messages are not to be removed from the queue after processing by the function. The
return value of PeekMessage() is either nonzero or zero, denoting whether a message is available
or unavailable for processing, respectively. Hence, while there are messages to be processed, the
body of the loop is executed.
The first action that takes place within the loop is a call to PumpMessage(): this function con-
tains the thread’s message loop and calling it forces the messages to be processed [8]. For example,
an event-handler function may be called to process a message. If this is not called, no processing
takes place and the code halts.
664 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The conditional section of the loop concerns the filtering of the message to determine whether
the menu entry or toolbar button was selected. The first conditional clause determines whether the
message is a command event message, denoted by WM_COMMAND: a command message is
sent when the user selects a command item from a menu, when a control sends a notification mes-
sage to its parent window, or when an accelerator keystroke is translated [6]. Here, the Simulation
menu entry, Stop, will generate this message, but the Stop button on the Common Operations
toolbar does not. This is treated in the following by the Stop button’s event-handler function. The
WM_COMMAND parameters are as follows: (1) a notification code (HIWORD(wParam)), (2) an
item identifier (LOWORD(wParam)), and (3) a handle of the control ((HWND) lParam) (the inter-
ested reader should consult Ref. [6] for further details). The second conditional clause filters out the
ID corresponding to the menu entry and toolbar button, i.e., ID_SIM_STOP. Finally, the function
returns the Boolean value “TRUE”, denoting whether the simulation was stopped.
void CDiagramEngDoc::OnSimStop()
{
// TODO: Add your command handler code here
// DiagramEng (start)
HWND hWnd = NULL; // handle to window whose window
procedure is to receive the msg.
LPARAM lParam = NULL; // windows procedure based parameter
with msg. specific info.
UINT msg = WM_COMMAND; // message
WPARAM wParam = MAKEWPARAM(ID_SIM_STOP, BN_CLICKED); // make the para
using ID and BN_CLICKED event
// DiagramEng (end)
}
The PostMessage() function places (posts) a message in the message queue associated with
the thread that created the specified window and returns without waiting for the thread to process
the message [8]. The parameters to the function are the following: (1) a handle to the window
whose window procedure is to receive the message (HWND), (2) the message to be posted (UINT),
Preparation for Feedback-Based Signal Propagation 665
–0.200000
–0.400000
–0.600000
–0.800000
–1.000000
0.000000 1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000 9.000000 10.000000
t (s)
FIGURE 22.6 Simulation involving a Signal Generator block used to generate a sinusoidal signal for a 10 s
interval that is prematurely terminated using the Stop button or menu entry.
22.9 SUMMARY
The preparation for signal propagation in the context of a feedback loop involves the addition of
member methods and variables to extend the existing project structure to be able to form, in general,
a nonlinear system of equations, F(x(t)) = x(t) − f(x(t)) = 0, where x(t) is the system signal vector,
f(x(t)) is the system function operation vector, and F(x(t)) is the generalized system vector. The root
or solution, xr(t), of this system is the vector of system signals at the current time-step, t ∈ [t0, tf ],
forming the model output that may be displayed as a graph.
A discussion of linear and nonlinear systems is first made, including their computation using
Newton’s method. Model assumptions are then introduced and involve all block-generated output-
connection-based signals, being column vectors. Preliminary signal propagation is used to generate
initial signals for block-output connections by either performing an OperateOnData() opera-
tion or a signal data transfer (TransferSignalDataToAnotherConnection()) operation
to obviate the need for block execution. Functionality is then added to allow user-defined setting of
initial output signals using a Context menu and dialog window.
666 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The building of the system of equations involves the construction of the system signal
vector, x(t) (DetermineSystemEquationsSignalVector()), which is passed to the
NewtonSolver() function that in turn calls a function to build the system function operation
vector, f(x(t)) (DetermineSystemEquationsFunctionOperationVector()), to com-
plete the generalized system vector, F(x(t)). The workings of the NewtonSolver() method and
related functions to perform the Newton-method-based computation of the system equations are the
subject of Chapter 23.
Finally, a method to terminate a simulation prematurely using the Stop (Common Operations
toolbar) button or (Simulation/Stop) menu entry was added to the project and involved call-
ing a CheckStopButtonPressed() function, within which the message-related func-
tions PeekMessage() and PumpMessage() are called, and also sending a message using
PostMessage() from within the OnSimStop() event-handler function.
REFERENCES
1. Ogata, K., Modern Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.
2. Oppenheim, A. V., Willsky, A. S., and Nawab, S. H., Signals and Systems, 2nd edn., Prentice Hall, Upper
Saddle River, NJ, 1997.
3. Fisher, M. E., Introductory Numerical Methods with the NAG Software Library, The University of
Western Australia, Perth, WA, 1989.
4. Nelson, R. C., Flight Stability and Automatic Control, 2nd edn., McGraw-Hill, Singapore, 1998.
5. C++ Reference, http://www.cppreference.com/wiki/
6. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
7. Press, W. H., Teukolsky, S. A., Vetterling, W. T., and Flannery, B. P., Numerical Recipes in C: The Art of
Scientific Computing, 2nd edn., Cambridge University Press, Cambridge, U.K., 2002.
8. “CWinThread::PumpMessage()”, Microsoft Developer Network Library, http://msdn.microsoft.com/
en-US/library/t1tkd768(v=VS.80).aspx, Microsoft Corporation, 2009, (accessed between 2008 and 2010).
9. “Using Messages and Message Queues”, Microsoft Developer Network Library, http://msdn.microsoft.com/
en-us/library/ms644928(v=VS.85).aspx#examining_queue, Microsoft Corporation, 2009, (accessed between
2008 and 2010).
23 Feedback-Based Signal
Propagation
23.1 INTRODUCTION
The previous chapter concerned the addition of code in preparation for signal propagation in
a feedback loop using Newton’s method. This work is now extended, where all the Newton-
method-based code is added, to allow the computation of a system of nonlinear equations of
the form
for t ∈ [t0, tf ], where t0 and tf are the initial and final times of the simulation, respectively, and x(t),
f(x(t)), and F(x(t)) are the system signal vector, system block operational function vector, and gener-
alized system vector, respectively. Newton’s method is used to compute the solution vector, or root
xr(t), that satisfies F(x(t)) = 0, and this may be decomposed into the component connection-based
signals xi(t), for i ∈ [0, s − 1], of the block diagram, where s is the total number of block output sig-
nals in the model (where there is only one output signal per block).
The main topics discussed herein involve (1) an initial brief exploration of the original Newton-
method-based code of Press et al. [1], (2) a C++-oriented transformation of the Newton-method-
based code, (3) the addition of the C++-oriented Newton-method-based code into the DiagramEng
project to allow the iterative solution of (23.1) to obtain signal values for diagrams involving feed-
back loops, (4) a discussion of convergence of the Newton method, and (5) testing of the Newton-
method-based code, when computing a model diagram involving at least one feedback loop, using
six case studies.
667
668 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 23.1
Function Call Tree of the Newton-Method-Based Win32 Console Application
Code Used to Test the Material Presented in Press et al.
Level 1 Level 2 Level 3 Level 4 Level 5 Level 6
main()
MyMathProblem()
newt()
fmin()
funcv()
fdjac()
funcv()
ludcmp()
lubksb()
lnsrch()
fmin()
funcv()
PrintVector()
Source: Press, W.H. et al., Numerical Recipes in C: The Art of Scientific Computing, 2nd edn., Cambridge
University Press, Cambridge, U.K., 2002.
Console Application is presented in Table 23.1, and the initial function prototypes with brief
descriptions of the functions are provided in Table 23.2.
Software developers familiar with the code of Press et al. (see the aforementioned function defi-
nitions in Ref. [1]) will notice that two global variables exist: “int nn” denotes the number of sys-
tem equations and “float *fvec” is used for “communication purposes” between the functions that
require values of F(x), in particular, newt(), fmin(), and fdjac(). Steps may be taken to remove
the global variables and pass these from one function to another, using extra function arguments.
The steps to make the global “nn” variable local are as follows:
However, the global variable “fvec” still remained since the process of making it local would
result in too many changes throughout the program making it difficult to read and understand.
Hence, a different approach was required: an object-oriented-based transformation was selected,
where “fvec” could be made a private member variable of a class (CNewtonMethod) and hence no
longer be global.
Feedback-Based Signal Propagation 669
TABLE 23.2
Original Function Prototypes and Brief Descriptions Used in the Newton-Method-
Based Win32 Console Application (the Majority of These Prototypes and Descriptions
May Be Found in Press et al.)
Function Prototypes and Descriptions
int main(int argc, char **argv);
// Calls MyMathProblem() to initiate the program.
int MyMathProblem(void);
// Sets up the initial vector x(t0) and calls newt().
void fdjac(int n, float x[], float fvec[], float **df, void (*vecfunc)(int,
float [], float []));
// Computes the forward finite difference approximation to the Jacobian.
float fmin(float x[]);
// Computes F(x) · F(x)/2, a sum of squares of the function F(x).
void funcv(int n, float x[], float f[]);
// Provides the user-supplied definition of the function F(x).
void lnsrch(int n, float xold[], float fold, float g[], float p[], float x[],
float *f, float stpmax, int *check, float (*func)(float []));
// A line search mechanism that finds a new point along a direction away from
an old point for a sufficient decrease in the function “func”.
void lubksb(float **a, int n, int *indx, float b[]);
// Solves a system of linear equations using the LU decomposition of a matrix
performing forward and backward substitution.
void ludcmp(float **a, int n, int *indx, float *d);
// Performs a LU decomposition of a matrix.
void newt(float x[], int n, int *check, void(*vecfunc)(int, float[],
float[]));
// Computes a root xr to a function F(x) = 0.
int PrintVector(float *v, int ncols);
// Prints a vector of floating point numbers.
Source: Press, W.H. et al., Numerical Recipes in C: The Art of Scientific Computing, 2nd edn., Cambridge University
Press, Cambridge, U.K., 2002.
23.3 OBJECT-ORIENTED TRANSFORMATION
OF THE NEWTON-METHOD-BASED SOLVER
A class-based approach was taken to remove the aforementioned global variable “fvec” and to trans-
form the Newton-method-based code to be object oriented in preparation for its inclusion in the
DiagramEng project. Function pointers that were used in the structured programming approach were
removed and explicit function names used, as indicated in the code presented later, since their names
are known prior to runtime (this makes reading the code somewhat easier). Memory allocation and
deallocation, using the operators new and delete, and array indexing from “0” to “n − 1”, rather
than from “1” to “n”, has been used throughout to conform to typical C/C++ programming style.
In addition, the defined (#define) constants in the original code were replaced with constant integers
and doubles where appropriate, and floating point arguments were changed to double type argu-
ments throughout (this does improve the accuracy of the code and makes it less problematic to use).
670 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Finally, the use of the system-provided assert() function, requiring the inclusion of the header file,
“assert.h”, was made in various locations to check that denominators are not equal to zero, thereby
avoiding a possible divide-by-zero error. An additional manual version of this is also used to allow
assertions in both the Debug and Release versions of the executable code.
// Newton.h
#ifndef NEWTON_H
#define NEWTON_H
// Defined Constructs
#define AssertManual(expr) do{ if(!(expr)) {PrintAssertMsg
(__FILE__, __LINE__); __asm{int 3}; } }while(0)
// Constant Values
const int PROBLEM_TYPE = 4; // type of problem: 0, 1, 2, 3, 4
// Constant Values Replacing Defined Vars in Numerical Recipes
const int MAXITS = 200; // max no. of iterations used in newt()
const double ALF = 1.0e-4; // used to ensure suff. decr in fn
value, used in lnsrch()
const double EPS = 1.0e-4; // epsilon
const double TINY = 1.0e-20; // small value used in ludcmp()
const double TOLF = 1.0e-4; // sets the cgce criterion on fn vals,
used in newt()
const double TOLMIN = 1.0e-6; // sets the criterion for deciding
whether spurious cgce to a min of fmin has occurred, used in newt()
const double TOLX = 1.0e-7; // cgce criterion on delta_x, used in
lnsrch() and newt()
const double STPMX = 100.0; // scaled max step length allowed in
line searched, used in newt()
// Merging of nrutil.h code into the Newton.h file
static double sqrarg;
#define SQR(a) ( (sqrarg = (a)) == 0.0 ? 0.0 : sqrarg*sqrarg)
static double maxarg1, maxarg2;
#define FMAX(a,b) (maxarg1 = (a), maxarg2 = (b), (maxarg1) >
(maxarg2) ? (maxarg1) : (maxarg2))
// Static vars
static int prec = 15;
// Main line fns
int main(int argc, char **argv);
int MyMathProblem(void);
Feedback-Based Signal Propagation 671
The header file involves “const int” and “const double” statements, global functions, and the
CNewtonMethod class declaration. The constant integer “PROBLEM_TYPE” denotes the type of
problem being investigated (explained later). The constants “MAXITS” to “STPMAX” are used
by newt()-related code, so too are the macros FMAX() and SQR(). The CNewtonMethod class
involves the Newton-method-based code of Press et al. [1], i.e., fdjac(), fmin(), funcv(),
lnsrch(), lubksb(), ludcmp(), and newt() (originally presented in Table 23.2). The func-
tion nrerror() is an error-handling method for newt()-related code. The memory manage-
ment functions, memory_dmatrix(), memory_dvector(), and memory_ivector(),
allocate memory for a double matrix, a double vector, and an integer vector, respectively, and
memory_delete_newt() deallocates memory for newt()-related data structures. The devel-
oper will see in the “Newton.cpp” code later that the memory_delete_newt() function
deletes not only the variables passed as arguments but also the class member variable, “fvec”.
672 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Finally, manually coded versions of assert() are provided in two forms: (1) as a function,
AssertManualFn() and (2) as a macro, AssertManual(), where both of these functions call
PrintAssertMsg() to print a message to the user via the screen and a log file. The assertion
macro and function were provided by Jesse Pepper of Think Bottom Up [3].
// Title: Newton.cpp
// Purpose: Performs computation of the system of nonlinear equations,
F(x) = x - f(x) = 0.
// That is, the solution, or root, or zero vector x that
satisfies F(x) = 0, is returned.
// Source: Numerical Recipes in C: The Art of Scientific Computing
(2nd Ed.), sections 9.7, p. 383–389.
// Source: Numerical Recipes in C++: The Art of Scientific Computing
(2nd Ed.), sections 2.3 and 9.7.
// Refs. See “Introductory Numerical Methods with the NAG Software
Library” by Fisher. In particular
// Chapter 4, Nonlinear Equations, p. 67–78.
//
// FN CALL TREE:
//
// main()
// MyMathProblem()
// newt()
// fmin()
// funcv()
// fdjac()
// funcv()
// ludcmp()
// lubksb()
// lnsrch()
// fmin()
// funcv()
// PrintVector()
// Include files
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <iomanip>
#include <math.h>
#include “Newton.h”
#include “assert.h” // used to make assertions
using namespace std;
// -- MAIN LINE FNS
int main(int argc, char **argv)
Feedback-Based Signal Propagation 673
{
cout << “\n main()\n”;
MyMathProblem();
cout << endl;
return 0;
}
// Init
switch(problem_type)
{
case 0:
n = 1; // size of system
x_vec = new double[n]; // memory alloc
// x_vec init
x_vec[0] = 2; // initial guess
break;
case 1:
n = 2; // size of system
x_vec = new double[n]; // memory alloc
// x_vec init
x_vec[0] = 5; // initial guess
x_vec[1] = 5; // initial guess
break;
case 2:
n = 4; // size of system
x_vec = new double[n]; // memory alloc
// x_vec init
x_vec[0] = 5; // initial guess
x_vec[1] = 6; // initial guess
x_vec[2] = 7; // initial guess
x_vec[3] = 8; // initial guess
break;
case 3:
n = 2; // size of system
x_vec = new double[n]; // memory alloc
// x_vec init
x_vec[0] = 10; // initial guess
x_vec[1] = 11; // initial guess
break;
case 4:
n = 2; // size of system
x_vec = new double[n]; // memory alloc
// x_vec init
x_vec[0] = 1; // initial guess
674 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
else
{
fprintf(ofp, “Assertion failed, in file = %s, on line = %d.\n”,
file_name, line); // write msg. to file
fclose(ofp); // close file
}
}
// Constructor
CNewtonMethod::CNewtonMethod()
{
cout << “\n CNewtonMethod()::CNewtonMethod()\n”;
// Init. class member pointer-to-double fvec
fvec = NULL;
}
// Destructor
CNewtonMethod::∼CNewtonMethod()
{
cout << “\n CNewtonMethod::∼CNewtonMethod()\n”;
// MEMORY ALLOC
indx = memory_ivector(n);
g = memory_dvector(n);
p = memory_dvector(n);
xold = memory_dvector(n);
fvec = memory_dvector(n); // global var used (globally) in newt()
and fmin(), and passed as an arg to fdjac() and funcv().
fjac = memory_dmatrix(n,n);
{
test = fabs(fvec[i]);
}
}
if(test < 0.01*TOLF)
{
*check = 0;
memory_delete_newt(indx, g, p, xold, fjac, n);
return;
}
for(sum=0.0, i=0; i<n; i++)
{
sum += SQR(x[i]); // calculate stpmax for line searches
}
stpmax = STPMX*FMAX(sqrt(sum), (double)n);
// Start of iteration loop
for(its = 0; its < MAXITS; its++)
{
// fdjac(n, x, fvec, fjac, vecfunc);
fdjac(n, x, fvec, fjac);
for(i=0; i<n; i++)
{
for(sum=0.0, j=0; j<n; j++)
{
sum += fjac[j][i]*fvec[j];
}
g[i] = sum;
}
for(i=0; i<n; i++)
{
xold[i] = x[i]; // store x
}
fold = f; // store f
for(i=0; i<n; i++)
{
p[i] = -fvec[i]; // rhs for linear equs
}
ludcmp(fjac, n, indx, &d); // solve linear equs by LU decomp
lubksb(fjac, n, indx, p);
// lnsrch returns new x and f. It also calculates fvec at the new
x when it calls fmin.
// lnsrch(n, xold, fold, g, p, x, &f, stpmax, check, fmin);
lnsrch(n, xold, fold, g, p, x, &f, stpmax, check);
test = 0.0; // test for cgce on fn vals
for(i=0; i<n; i++)
{
if(fabs(fvec[i]) > test)
{
test = fabs(fvec[i]);
}
}
678 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
return 0.5*sum;
}
double alam2;
double alamin;
double b;
double disc;
double f2;
double rhs1;
double rhs2;
double slope;
double sum;
double temp;
double test;
double tmplam;
*check = 0;
for(sum=0.0, i=0; i<n; i++)
{
sum += p[i]*p[i];
}
{
for(i=0; i<n; i++)
{
x[i] = xold[i] + alam*p[i];
}
// *f = (*func)(x, n);
*f = fmin(x, n);
if(ii != 0)
{
for(j=ii-1; j<i; j++)
{
sum -= a[i][j]*b[j];
}
}
else if(sum != 0.0)
{
ii = i + 1;
}
b[i] = sum;
}
Feedback-Based Signal Propagation 683
big = 0.0;
for(i=j; i<n; i++)
{
sum = a[i][j];
for(k=0; k<j; k++)
{
sum -= a[i][k]*a[k][j];
}
a[i][j] = sum;
if( (dum = vv[i]*fabs(sum)) >= big)
{
big = dum;
imax = i;
}
}
if(j != imax)
{
for(k=0; k<n; k++)
{
dum = a[imax][k];
a[imax][k] = a[j][k];
a[j][k] = dum;
}
*d = -(*d);
vv[imax] = vv[j];
}
indx[j] = imax;
if(a[j][j] == 0.0)
{
a[j][j] = TINY; // original NR code
//a[j][j] = (double)TINY; // BF altered.
}
if(j != (n-1))
{
dum = 1.0/(a[j][j]);
for(i=j+1; i<n; i++)
{
a[i][j] *= dum;
}
}
}// end for j
// Memory delete
if(vv != NULL)
{
delete [] vv;
vv = NULL;
}
}
// Print Vector
cout << endl;
for(i=0; i<ncols; i++)
{
cout << “” << setprecision(prec) << v[i];
}
cout << “n\n”;
return 0;
}
double* CNewtonMethod::memory_dvector(int n)
{
double *v = NULL;
// Memory Alloc
v = new double[n];
// Check memory
if(!v)
{
nrerror(“memory_dvector(), memory allocation failure.”);
}
return v;
}
int* CNewtonMethod::memory_ivector(int n)
{
int *v = NULL;
// Memory Alloc
v = new int[n];
// Check memory
if(!v)
{
nrerror(“int_vector(), memory allocation failure.”);
}
return v;
}
{
delete [] fvec;
fvec = NULL;
}
// Memory Delete Matrix
if(fjac != NULL)
{
for(i=0; i<nrows; i++)
{
delete [] fjac[i];
}
delete [] fjac;
fjac = NULL;
}
return;
}
void CNewtonMethod::nrerror(char *error_text)
{
/* Numerical Recipes standard error handler */
fprintf(stderr, “Numerical Recipes run-time error. …\n”);
fprintf(stderr, “%s\n”, error_text);
fprintf(stderr, “Now exiting to system. …\n”);
exit(1);
}
The developer will notice in the memory_delete_newt() function mentioned earlier that if the class
member variable, “fvec”, were to be passed into the function from the newt() calling environment, then
it would assume a local declaration in the function header, and the assignment “fvec = NULL” would
not be persistent outside the function in the class scope! The memory would be deleted, but the NULL
assignment would not hold, and this would cause potential problems if the memory were attempted to
be deleted more than once, e.g., in the CNewtonMethod class destructor! Hence, “fvec” should not be
passed into memory_delete_newt(), but rather simply be deleted and reassigned to NULL: it is a
class member variable and memory_delete_newt() has access to it as a member function.
23.3.3.1 Case 1
The first simple case (PROBLEM_TYPE = 0) is
F ( x) = x 2 − 1 (23.2)
and a solution or root, xr, is desired to the equation F(x) = 0. Depending on an initial guess for x0,
two different solutions are possible: for x0 < 0, xr = −1 and for x0 > 0, xr = 1. The developer should
be aware that when the vector “x” was of type float, at exactly x0 = 0, the solver had difficulty in
determining a solution and reported the detection of a singular matrix in the routine ludcmp() and
terminated execution. However, after changing the type of “x” to double, for the initial guess x0 = 0,
the solution xr = 1 was found.
688 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
23.3.3.2 Case 2
The second case (PROBLEM_TYPE = 1) involves the vector function of a vector variable
⎡ F0 ( x)⎤ ⎡ x0 + 2 ⎤
F( x) = ⎢ ⎥=⎢ 2 ⎥ (23.3)
⎣ F1 ( x) ⎦ ⎣ x1 − 1⎦
The solution, xr, to the equation F(x) = 0, differs for different initial values x0, e.g., for x0 = [5, 5]T,
xr = [−2, 1]T, and for x0 = [−5, −5]T, xr = [−2, −1]T.
23.3.3.3 Case 3
The third case (PROBLEM_TYPE = 2) involves the linear function represented by Equations 22.1
through 22.7 and Figure 22.1, i.e.,
⎡ F0 ( x)⎤ ⎡ x0 − f0 ⎤ ⎡ x0 − 1 ⎤
⎢ F ( x) ⎥ ⎢ x − x − x ⎥ ⎢ x − x − x ⎥
1 ⎥=⎢ 1 0 3⎥ 1 0 3⎥
F( x) = ⎢ =⎢ (23.4)
⎢ F2 ( x)⎥ ⎢ x2 − k2 x1 ⎥ ⎢ x2 − 2 x1 ⎥
⎢ ⎥ ⎢ ⎥ ⎢ ⎥
⎣ F3 ( x) ⎦ ⎣ x3 − k3 x2 ⎦ ⎣x3 − 3 x2 ⎦
The reader will notice that a constant function f0 = 1 and gain constants k2 = 2 and k3 = 3 are used.
The solution or root of the equation F(x) = 0 is xr = [1, −0.2, −0.4, −1.2]T.
23.3.3.4 Case 4
The fourth case (PROBLEM_TYPE = 3) involves the nonlinear vector function
The solution to the equation F(x) = 0, for choices of x0 in a (sufficiently large) neighborhood of the
solution, is xr = [2, 4]T.
23.3.3.5 Case 5
The fifth case (PROBLEM_TYPE = 4) involves the nonlinear vector function provided by Fisher [4]
(p. 77), i.e.,
⎡ F0 ( x)⎤ ⎡ x0cos( x1 + 1) ⎤
F( x) = ⎢ ⎥ = ⎢ x0 − 2 ⎥ (23.6)
⎣ F1 ( x) ⎦ ⎣e sin( x1 / 2) − 1⎦
where the solution to the equation F(x) = 0 is, to three decimal places, xr = [2.156, 4.230]T.
simulation. The general steps involved to augment the DiagramEng project with the Newton-
method-centric code are as listed, while specific details are provided in the sections that follow:
// MEMORY DELETE
if(x != NULL)
{
delete [] x;
x = NULL;
}
return valid;
}
The call to newt() is made to obtain the vector x(t), i.e., the root xr(t) that satisfies (23.1).
Initially a CNewtonMethod object, “probObj”, is instantiated, passing the time-based argu-
ments, “t_cnt”, “t”, and “delta_t”, as parameters: this is done since newt() indirectly calls
funcv(), in which the generalized system function vector F(x(t)) = x(t) − f(x(t)) is formed, and
this is composed of the block operational function vector f(x(t)), which itself is determined by
invoking the block OperateOnData(int t, double t, double delta_t) methods,
taking the three time-based arguments.
Thereafter memory for a local vector “x” is allocated, and the contents of the system vec-
tor, “x_vec”, representing x(t) in (23.1), are then written to “x” in preparation for the call
to newt(). At t = t0, the incoming vector, x(t0), is that determined by the function call to
DetermineSystemEquationsSignalVector() made in the calling environment:
SignalPropagationSystemOfEquations(). After the call to newt(), the solution
vector or root xr(t) of (23.1), represented by “x”, is written to x(t), i.e., “x_vec”, and this is then
passed back to the calling environment (through the parameter list) and assigned to the com-
ponent signal vectors using AssignSystemEquationsSignalVector() (within
SignalPropagationSystemOfEquations()).
An aside: the developer should be aware that the vector “x” and the size of the system of equa-
tions, “nrows”, are passed to newt(), wherein vectors have memory allocated from offset “0” to
“nrows −1”. In the original newt()-based code of Press et al. [1], array indices “1” to “n” are used,
but in Ref. [2] and in the object-oriented transformation to C++-like code, array indices “0” to
“n − 1” are used.
1. Edit the header file to have a similar structure to “Newton.h” introduced earlier, to result in
the “NewtonMethod.h” header file shown in the following. Note that the manual assertion
functions are added to the project later to the “DiagramEngDoc.cpp/.h” files and hence are
omitted from the “NewtonMethod.cpp/.h” files.
2. Add three new private member variables to the CNewtonMethod class, as shown in bold
in the following, to retain time-based information, i.e., an integer, “m_t_cnt”, a double,
“m_t” and a double, “m_delta_t”. These are used to communicate the time-based param-
eter values from the CSystemModel-centric code to the CNewtonMethod-centric code
(as described earlier).
3. Add a new public constructor function prototype to the CNewtonMethod class,
i.e., CNewtonMethod::CNewtonMethod(int t_cnt, double t, double
delta_t) taking three time-based arguments, as shown in bold in the following.
4. Add a new public function prototype to the CNewtonMethod class, i.e., int
CNewtonMethod::CheckJacobian(double **fjac, int nrows, int
ncols), as shown in bold in the following. This diagnostic function will be added to the
project toward the end of the section.
{
public:
// Constr/Destr
CNewtonMethod();
CNewtonMethod(int t_cnt, double t, double delta_t);
virtual ∼CNewtonMethod();
// Checking fns
int CheckJacobian(double **fjac, int nrows, int ncols);
// Printing fns
int PrintVector(double *v, int ncols);
private:
int m_t_cnt; // time count
double m_t; // time value
double m_delta_t; // time step size delta_t
double *fvec;
};
#endif // !defined(AFX_NEWTONMETHOD_H__DC19AFC4_3263_4690_8914_
B7F21D652DBD__INCLUDED_)
1. Add (by copying and pasting) the implementations of the functions fdjac(), fmin(),
lnsrch(), lubksb(), ludcmp(), newt(), the memory management functions, and the
destructor, as shown earlier in “Newton.cpp” of the Win32 Console Application project, to
the “NewtonMethod.cpp” source file of the DiagramEng project.
2. Include the “assert.h” header file at the top of the “NewtonMethod.cpp” source file, since
the system-provided assert() function is introduced for the newt()-related code for
diagnostic purposes; the manual implementations of assertions are introduced later.
3. Edit the new construction function as shown in the following to initialize the member
variables “m_t_cnt”, “m_t”, “m_delta_t”, and “fvec”.
{
x_vec[i] = x[i];
}
// MEMORY DELETE
if(F_vec != NULL)
{
delete [] F_vec;
F_vec = NULL;
}
Feedback-Based Signal Propagation 695
if(f_vec != NULL)
{
delete [] f_vec;
f_vec = NULL;
}
if(x_vec != NULL)
{
delete [] x_vec;
x_vec = NULL;
}
}
Initially memory is allocated, with the memory_dvector() function, for three vectors: the general-
ized system function vector, F(x(t)); the block operational function vector, f(x(t)); and the system signal
vector, x(t). The number of elements to be allocated (n) is the size of the system of equations (23.1).
The incoming vector “x” is then assigned to “x_vec”. The developer should be aware that the local
variables “F_vec”, “f_vec”, and “x_vec” are concerned with CSystemModel-centric code, whereas the
incoming arguments to funcv(), “x” and “f”, are related to CNewtonMethod-centric code.
The next step is to determine the functional operation vector f(x(t)), which in conjunction with x(t)
can complete the generalized system function vector (23.1). In general, i.e., for an arbitrary time point,
t, the functional operation vector f(x(t)) is composed of the output of the functional block operations
fi(x(t)), for i ∈ [0, s − 1], where the input arguments to the blocks and upon which the block operations
are performed are the components xi(t) of the system signal vector, x(t). The function vector, f(x(t)),
is returned by the DetermineSystemEquationsFunctionOperationVector() function,
which, being a member function of the CSystemModel class, is called upon the CSystemModel
object via the pointer-to-CDiagramEngDoc, “pDoc”.
Finally, the generalized system function vector F(x(t)) = x(t) − f(x(t)) is formed, represented by
“F_vec” and written to the “f” variable to be returned to the calling environment, i.e., to the func-
tions fmin() or fdjac() called from within the Newton-method-based solver newt() (see Table
23.1 for the function call tree). That is, given an input system signal vector x(t), the output general-
ized system vector F(x(t)) is returned.
5. The function fdjac() makes use of the fabs() function, hence add, #include
<math.h>, at the top of the “NewtonMethod.cpp” source file beneath the inclusion of the
“NewtonMethod.h” header file.
6. The funcv() function shown earlier makes use of a pointer-to-CDiagramEngDoc,
“pDoc”; hence, include “DiagramEngDoc.h” at the top of the “NewtonMethod.cpp”
source file.
7. The double version of the PrintVector() function of the CNewtonMethod class needs
to be altered to print the vector within an AfxMessageBox(), as shown in the follow-
ing. This is identical to the double version of the PrintVector() function defined in the
“DiagramEngDoc.cpp” file; it is added here to the CNewtonMethod class for completeness.
{
return 1;
}
sMsgTemp.Format(“\n PrintVector()\n”);
sMsg += sMsgTemp;
// Print Vector
sMsgTemp.Format(“\n”);
sMsg += sMsgTemp;
for(i=0; i<ncols; i++)
{
//sMsgTemp.Format(“%-10.3e”, v[i]); // left adjusted, total width
10, 3 decimal places, exponential notation
sMsgTemp.Format(“%lf”, v[i]); // standard double formatting
sMsg += sMsgTemp;
}
sMsgTemp.Format(“\n”);
sMsg += sMsgTemp;
// Display msg. box
AfxMessageBox(sMsg, nType, nIDhelp);
return 0;
}
Now the code may be built with no build errors. However, there is a problem with the nrerror()
function: a call to exit() is made whenever the newt()-based code experiences either memory
Feedback-Based Signal Propagation 697
TABLE 23.3
CNewtonMethod Functions That Call nrerror()
through Either Memory Allocation Failure or
Numerical Error
Functions Calling nrerror() Functions Calling nrerror()
for Memory Allocation Failure for Numerical Error
memory_dmatrix() lnsrch()
memory_dvector() ludcmp()
memory_ivector() newt()
allocation failure or numerical problems. The result of this is that the program, DiagramEng, is
terminated and the user does not have a chance to modify the model diagram in an attempt to
resolve any numerical anomalies. Hence, a mechanism should be in place to allow the Newton-
method-based code to deallocate any allocated memory and return an error value to the calling
environment (ultimately SignalPropagationSystemOfEquations()) to terminate model
execution without terminating the DiagramEng application.
nr_status = -2;
nrerror(“Roundoff problem in lnsrch().”, nr_status);
return nr_status;
}
…
for(;;)
{
…
if(alam < alamin)
{
for(i=1; i<=n; i++)
{
x[i] = xold[i];
}
*check = 1;
// DiagramEng-altered
return nr_status;
}
else if(*f <= fold + ALF*alam*slope) // Sufficient fn decrease.
{
// DiagramEng-altered
return nr_status;
}
…
}// end for
// DiagramEng-altered
return nr_status;
}
1. Modify the ludcmp() function prototype in the “NewtonMethod.h” header file as fol-
lows: int CNewtonMethod::ludcmp(double **a, int n, int *indx,
double *d).
2. Edit the ludcmp() function as shown in bold in the following to either return “nr_status”
with value “1” or call nrerror(), deallocate memory, and return “nr_status” with
value “−2”.
nr_status = -2;
nrerror(“Singular matrix in routine ludcmp().”, nr_status);
if(vv != NULL)
{
delete [] vv;
vv = NULL;
}
return nr_status;
}
vv[i] = 1.0/big;
}// end for i
for(j=0; j<n; j++)
{
…
}// end for j
// Memory delete
if(vv != NULL)
{
delete [] vv;
vv = NULL;
}
// DiagramEng-altered
return nr_status;
}
1. Modify the newt() function prototype in the “NewtonMethod.h” header file as follows:
int CNewtonMethod::newt(double *x, int n, int *check).
2. Edit the newt() function, as shown in bold in the following, to return the value of
“nr_status”: nrerror() is called if “nr_status” is equal to “−2”.
// -- DiagramEng-altered
nr_status = lnsrch(n, xold, fold, g, p, x, &f, stpmax, check);
if(nr_status == -2)
{
nrerror(“Numerical problem concerning lnsrch() detected in
newt().”, nr_status);
memory_delete_newt(indx, g, p, xold, fjac, n);
return nr_status;
}
…
if(test < TOLF)
{
*check = 0;
memory_delete_newt(indx, g, p, xold, fjac, n);
return nr_status;
}
// DiagramEng-altered
nr_status = -2;
nrerror(“MAXITS exceeded in newt().”, nr_status);
memory_delete_newt(indx, g, p, xold, fjac, n);
return nr_status;
}
The reader will have noticed a call to the diagnostic function CheckJacobian() (mentioned in
Section 23.4.2): this is used to check whether the Jacobian matrix, “fjac”, is singular, i.e., whether it
contains a row of zeros. This test is actually made at the start of ludcmp() and is used in newt()
to detect problems a little earlier.
1. Hence, edit the new public member function CheckJacobian() of the CNewtonMethod
class, as shown in the following.
2. Also add a new public member function to the CNewtonMethod class to print the
Jacobian matrix, with the prototype int CNewtonMethod::PrintMatrix(double
**M, int nrows, int ncols), and edit it as shown in the following.
These changes to the CNewtonMethod functions allow for better error checking and a more infor-
mative termination of computation.
double* CNewtonMethod::memory_dvector(int n)
{
double *v = NULL;
// Memory Alloc
v = new double[n];
// Check memory
if(!v)
704 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
//nrerror(“memory_dvector(), memory allocation failure.”);
nrerror(“memory_dvector(), memory allocation failure.”, -1);
}
return v;
}
23.4.4.6 Modify Relevant Functions to Process the Integer Returned from newt()
Now the returned “nr_status” integer from newt() needs to be passed back to the
SignalPropagationSystemOfEquations() function via the NewtonSolver() function.
Hence, perform the following alterations.
1. Assign the result of the call to newt() to the variable, “valid”, in NewtonSolver(), as
shown in the following; if the newt() function evaluates successfully, then “nr_status”
will be “1” and so too the variable “valid”.
int CSystemModel::NewtonSolver(int nrows, double *x_vec, int t_cnt,
double t, double delta_t)
{
int i;
int check;
int valid = 1;
…
valid = probObj.newt(x, nrows, &check);
if(valid != 1) // if valid != 1 then newt() is in error
{
valid = 0;
probObj.PrintVector(x, nrows);
}
// Prepare the solution vector x_vec from the vector x returned by
newt() if newt() not in error.
if(valid == 1)
{
for(i=0; i<nrows; i++)
{
x_vec[i] = x[i];
}
}
…
// MEMORY DELETE
if(x != NULL)
{
delete [] x;
x = NULL;
}
return valid;
}
Now if the user experiences a memory allocation problem or a numerical problem in the newt()-based
code, the code will either exit to the system (nr_status = −1) or abort the simulation (nr_status = −2),
respectively. The user can experiment with numerical error detection by constructing a simple linear
system with a feedback loop, as shown in Figure 23.1, and set the source function, f0(t) = 1 (e.g., using
a Linear Function block with f0(t) = t, where f(0) = 0), and the gain constant, k2 = 1, to make the sys-
tem singular, i.e., the leading matrix of the system, Ax = b, is singular (as discussed in Chapter 19).
The error reported reads as follows: “Numerical Recipes run-time error … Roundoff problem in
lnsrch().” This is followed by error messages from functions higher up the hierarchy, i.e., in
newt() and SignalPropagationSystemOfEquations().
x0 x1 x2
f0(t) k2 = 1 Out
0 1 2 3
FIGURE 23.1 Simple linear system with a feedback loop that is singular if f 0(t) = 1 and k2 = 1.
706 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
1. Add the AssertManual() macro at the top of the “DiagramEngDoc.h” header file, as
shown in bold in the following code excerpt.
2. Add the following prototype at the global function definition section of the “DiagramEngDoc.h”
header file, as shown in bold in the following: int AssertManualFn(bool expr,
char *file_name, int line).
3. Add the prototype of the printing function that works with the assertion functions, at the
global function definition section of the “DiagramEngDoc.h” header file, as shown in bold
in the following: void PrintAssertMsg(char *file_name, int line).
#if !defined(AFX_DIAGRAMENGDOC_H__9DA8C218_5A85_4AB4_B77B_A9F8FF828990__
INCLUDED_)
#define AFX_DIAGRAMENGDOC_H__9DA8C218_5A85_4AB4_B77B_A9F8FF828990__
INCLUDED_
…
// User defined constructs
enum EBlockType{eChirpSignal, eConstBlock, eDerivativeBlock,
eDivideBlock, eGainBlock, eIntegratorBlock, eLinearFnBlock,
eOutputBlock, eSignalGenBlock, eSubsysBlock,
eSubsysInBlock, eSubsysOutBlock, eSumBlock,
eTransferFnBlock};
// -- GLOBAL FUNCTIONS
CDiagramEngDoc* GetDocumentGlobalFn(void); // gets a ptr to the doc
int AssertManualFn(bool expr, char *file_name, int line);
double **ConvertStringMatrixToDoubleMatrix(char **str_matrix, int nrows,
int ncols);
double **ConvertStringToDouble(CString string, int &nrows, int &ncols);
int DetermineDataDimsByStrpbrk(char *in_str, int &nrows, int &ncols,
int &max_row_length);
int DetermineDataDimsByStrtok(char *in_str, int &nrows, int &ncols,
int &max_row_length);
int GaussJordanEliminationFullPivoting(double **A, int n, double **B,
int m);
void MatrixMultiply(int nrowsA, int ncolsA, int ncolsB, double **A,
double **B, double **C);
void PrintAssertMsg(char *file_name, int line);
Feedback-Based Signal Propagation 707
// DiagramEng (end)
/////////////////////////////////////////////////////////////////////////
//{{AFX_INSER.T_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
before the previous line.
#endif // !defined(AFX_DIAGRAMENGDOC_H__9DA8C218_5A85_4AB4_B77B_
A9F8FF828990__INCLUDED_)
Now the developer can place AssertManual() in the code, where the input argument is the
expression whose validity is being questioned, for both the Debug and Release build configurations
of the executable.
// SET TIME COUNT REF AND NO. OF TIMES THE FN IS CALLED AT THIS TIME
POINT
if(m_i_t_cnt_ref == t_cnt)
{
m_iNcallsPerTimeCnt++; // no. of calls per time-based iteration
(t_cnt)
}
else
Feedback-Based Signal Propagation 709
{
m_i_t_cnt_ref = t_cnt;
m_iNcallsPerTimeCnt = 1;
}
The Derivative block’s OperateOnData() function takes input, f(t), and generates the derivative
output, f′(t), using the internal (“remembered”) data, f(t − h) and f(t − 2h) (for the five-point derivative
calculation). Hence, any alteration or change in the input signal after the initial operation at the cur-
rent time, t, would alter the internal data structures f(t − h) and f(t − 2h) through algorithmic updating,
as shown in Figure 23.2: f(t − 2h) ← f(t − h) and f(t − h) ← f(t). Hence, the internal values would
not in fact be those at the time points indicated, i.e., at t − h and t − 2h, but rather at intermediary
values. For this reason, the output signal, “m_dMatrixOut”, determined on the first invocation of the
OperateOnData() function, is returned for any intermediary calls made by newt()-based code.
.
The Integrator block’s OperateOnData() function takes input, y(t) = f(y, t), and generates
.
the integral output y(t), using the internal data y(t − h) and y(t − h), as shown in Figure 23.3, i.e.,
.
y(t) = y(t − h) + hy(t − h). In a similar manner to the Derivative block, any change in the input
signal after the initial invocation of the OperateOnData() function at the current time, t,
.
would alter the internal data y(t − h) and y(t − h) through the algorithmic updating: y(t − h) ← y(t)
. .
and y(t − h) ← y(t). Hence, the internal values would not be those at the time specified, but at an
intermediary time. Again, for this reason, the output signal, “m_dMatrixOut”, determined on the
first invocation of the OperateOnData() function, is returned for any intermediary calls made
by newt()-based code.
Hence, memory-based blocks do not contribute to the convergence of the Newton-method-based
code, since the output at a certain time point is fixed, regardless of a newt()-specified change in the
block’s input. The memoryless blocks do contribute to convergence, since their output at the current
time point can change with a change in the input signal.
The mathematics student will recall that numerical integration schemes may be either explicit
or implicit. Explicit schemes are those where the state of the system at the next time point, yn+1,
f (t) f ΄(t)
d/dt
f (t – 2h) f (t – h)
f (t – h) f (t)
FIGURE 23.2 Algorithmic function of the Derivative block, for the five-point derivative calculation, updat-
ing the internal memory-based data structures.
y(t) y(t)
∫ dt
y(t – h) y(t)
· – h) ·
y(t)
y(t
FIGURE 23.3 Algorithmic function of the Integrator block, using Euler’s method, updating the internal
memory-based data structures.
710 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
is a function of the states, yn and yn−1, at the current and previous times points, respectively. For
example, the two-step midpoint method
yn +1 = yn −1 + 2hf (t n , yn ) (23.7)
yn +1 = yn + hf (t n , yn ) (23.8a)
yn = yn −1 + hf (t n −1, yn −1 ) (23.8b)
in the current project for reasons discussed earlier) are explicit schemes since yn+1 = f(tn, yn, yn−1) [6].
Implicit schemes are those where the state of the system at the next time point, yn+1, is a function of
the states yn and yn+1, at the current and future time points, respectively. For example, the one-step
trapezoidal method
h
yn +1 = yn + [ f (t n , yn ) + f (t n +1, yn +1 )] (23.9)
2
is an implicit scheme, since yn+1 = f(tn, tn+1, yn, yn+1) [6]. Hence, blocks with memory, i.e., those of
“explicit” form, cannot contribute to the convergence of the Newton-based method, since their out-
put is fixed at the current time point. However, blocks without memory, i.e., those of an “implicit”
form, can contribute to convergence, since their output is variable at the current time point.
x0 x1 x2
f0(t) + f2(t) Out
+
0 1 2 4
x3
f3(t)
FIGURE 23.4 Simple linear system with a feedback loop: fi(t) and xi denote the ith block operation and out-
put signal, respectively, where block numbers appear beneath the blocks.
⎡ F0 ( x(t ))⎤ ⎡ x0 (t )⎤ ⎡ f0 (t ) ⎤ ⎡ x0 (t ) − f0 (t ) ⎤
⎢ F ( x(t )) ⎥ ⎢ x (t ) ⎥ ⎢ f ( x(t )) ⎥ ⎢ x (t ) − x (t ) − x (t )⎥
⇒ ⎢ 1 ⎥ = ⎢ 1 ⎥−⎢ 1 ⎥=⎢ 1 0 3 ⎥=0 (23.10)
⎢ F2 ( x(t ))⎥ ⎢ x2 (t )⎥ ⎢ f2 ( x(t ))⎥ ⎢ x2 (t ) − k2 x1 (t ) ⎥
⎢ ⎥ ⎢ ⎥ ⎢ ⎥ ⎢ ⎥
⎣ F3 ( x(t )) ⎦ ⎣ x3 (t ) ⎦ ⎣ f3 ( x(t ))⎦ ⎣ x3 (t ) − k3 x2 (t ) ⎦
where
k2 and k3 are the gain constants of blocks two and three, respectively
the vector of output signals at the current time point, t, is x(t) = [x0(t), x1(t), x2(t), x3(t)]T
The solution xr(t) that satisfies (23.10) returned by the newt() function for f0(t) = 1 and gain con-
stants k2 = 2 and k3 = 3 is xr(t) = [1, −0.2, −0.4, −1.2]T, as shown earlier in “Case 3” of Section 23.3.3.
Throughout the simulation, i.e., for t ∈ [t0, tf ], the same solution vector xr(t) will be generated, since
the signal generated by block “0” is constant, i.e., f0(t) = 1.
The user may experiment with different Source blocks and generate the corresponding output.
For example, a Linear Function source signal block with initial value f(t0) = 0, f ′(t) = 1, δt = 1.0 s,
and initial and final times of signal activation, ta0 = 0 s and taf = 7 s, respectively, is used in place of
block “0” in Figure 23.4, with gain constants k2 = 2 and k3 = 3. The corresponding output signal
generated is that shown in Figure 23.5a. In addition, a Signal Generator block may be used in place
of block “0” in Figure 23.4, with f(t) = A sin(ωt + ϕ), where A = 1, ω = 1 rad/s, ϕ = 0 rad, t ∈ [0, 6.4],
and δt = 0.1 s. The corresponding output signal generated is that shown in Figure 23.5b.
where y, t, and u(t) are the dependent variable, independent variable, and source signal generation
function, respectively. If the source signal is chosen to be u(t) = 2t, then the analytic solution to
(23.11), determined using the “integrating factor” method and “integration by parts,” is
1
y(t ) = t − Ce −2 t − (23.12)
2
where C is a constant. If the initial condition y(0) = 0 is chosen, then C = −1/2 and y(t) → t as
t → ∞. However, for different source functions u(t), different solutions y(t) will be obtained. This
ODE can be modeled using a block diagram involving a feedback loop, as shown in Figure 23.6,
712 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
–1.400000
–1.680000
–1.960000
–2.240000
–2.520000
–2.800000
0.000000 1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000 9.000000 10.000000
(a) t (s)
0.319989
0.240010
0.160030
0.080050
0.000070
f (t)
–0.079910
–0.159890
–0.239870
–0.319850
–0.399829
0.000000 0.640000 1.280000 1.920000 2.560000 3.200000 3.840000 4.480000 5.120000 5.760000 6.400000
(b) t (s)
FIGURE 23.5 Output generated by computation of a linear system: (a) a Linear Function block is used
as the source input signal, and (b) a Signal Generator block (sinusoidal function) is used as the source
input signal.
. y(t)
u(t) y(t)
f0(t) + ∫ dt Out
x0 x1 x2
+
0 1 2 4
2y(t)
x3 k3 = –2
FIGURE 23.6 Block diagram model of the ODE y.(t) = −2y(t) + u(t), with block numbers written below the
blocks, and signals xi(t), for i ∈ [0, s − 1] written beneath the block output connections.
Feedback-Based Signal Propagation 713
where block numbers are written below the blocks and signals xi(t), for i ∈ [0, s − 1], are written
beneath the block output connections.
The system of the form of (23.1) may be written as follows:
⎡ x (t ) − u(t ) ⎤
⎡ F0 ( x(t ))⎤ ⎡ x0 (t )⎤ ⎡ f0 (t ) ⎤ ⎢ 0 ⎥
⎢ F ( x(t )) ⎥ ⎢ x (t ) ⎥ ⎢ f ( x(t )) ⎥ ⎢ x1 (t ) − x0 (t ) − x3 (t )⎥
⎢ 1 ⎥=⎢ 1 ⎥−⎢ 1 ⎥=⎢ ⎥=0 (23.13)
⎢ ⎥ ⎢ ⎥ ∫
⎢ F2 ( x(t ))⎥ ⎢ x2 (t )⎥ ⎢ f2 ( x(t ))⎥ ⎢ x2 (t ) − x1 (t )dt ⎥
⎥ ⎢
⎣ F3 ( x(t )) ⎦ ⎣ x3 (t ) ⎦ ⎣ f3 ( x(t )) ⎦ ⎢ x3 (t ) − k3 x2 (tt ) ⎥
⎣ ⎦
where the second equation x1(t) − x0(t) − x3(t) = 0 represents the ODE (23.11) and the other equations
complete the signal-function description. The solution vector xr(t) to (23.13) may then be obtained
from newt() for each time-step t ∈ [t0, tf ] of the simulation, where x2(t) represents the solution
y(t) given by (23.12). The developer may experiment with different signal generation functions u(t),
including constant, linear, and sinusoidal forms, and observe the resultant output of y(t). For exam-
ple, if the input signal u(t) = 2t and y(0) = 0, then as described earlier, y(t) → t as t → ∞, as shown
in Figure 23.7a and b.
However, if u(t) = c1, where c1 is a constant, then the ODE
y (t ) = −2 y(t ) + c1 (23.14)
c1
y( t ) = + (c1c3 − c2 )e −2 t (23.15)
2
where c2 and c3 are constants of integration. As t → ∞, y(t) → c1/2, as shown in Figure 23.8 where
here u(t) = c1 = 1.
4.050000
3.600000
3.150000
2.700000
2.250000
f (t)
1.800000
1.350000
0.900000
0.450000
0.000000
0.000000 0.500000 1.000000 1.500000 2.000000 2.500000 3.000000 3.500000 4.000000 4.500000 5.000000
(a) t (s)
FIGURE 23.7 (a) Computed solution y(t) (23.12) of ODE (23.11), with u(t) = 2t, y(0) = 0, and δt = 10 −1 s, for
t ∈ [0, 5.0].
(continued)
714 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
89.550000
79.600000
69.650000
59.700000
f (t)
49.750000
39.800000
29.850000
19.900000
9.950000
0.000000
0.000000 10.000000 20.000000 30.000000 40.000000 50.000000 60.000000 70.000000 80.000000 90.000000 100.000000
(b) t (s)
FIGURE 23.7 (continued) (b) Computed solution y(t) (23.12) of ODE (23.11), with u(t) = 2t, y(0) = 0, and
δt = 10 −3 s, for t ∈ [0, 100.0], showing that y(t) → t as t → ∞.
0.449968
0.399971
0.349975
0.299979
0.249982
f (t)
0.199986
0.149989
0.099993
0.049996
0.000000
0.000000 0.300000 0.600000 0.900000 1.200000 1.500000 1.800000 2.100000 2.400000 2.700000 3.000000
t (s)
FIGURE 23.8 Computed solution y(t) (23.15) of ODE (23.14), with u(t) = 1, y(0) = 0, and δt = 10 −1 s, for
t ∈ [0, 3.0], indicating that y(t) → c1/2 = 1/2 as t → ∞.
were introduced in the previous chapter, where x(t), u(t), and y(t) are the state, control, and output
vectors, respectively, and A(t), B(t), C(t), and D(t) are the state, control, output, and direct transmis-
sion matrices, respectively [8]. The corresponding block diagram representation of the state and
output equations (23.16) (repeated here for convenience) is shown in Figure 23.9.
Feedback-Based Signal Propagation 715
A(t)
2
+ y(t)
u(t) B(t) + . ∫ dt C(t) + Out
x(t) x(t) +
0 1 3 4 6 7 8
D(t)
FIGURE 23.9 Block diagram representation of the state and output equations (23.16). (From Ogata, K.,
Modern Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.)
where m, b, k, y(t), and u(t) are the mass, damping constant, spring constant, output mass displace-
ment from the equilibrium position, and external force input to the system, respectively. An order
reduction is used to reduce the second-order system to two first-order equations, where x1(t) = y(t)
.
and x2(t) = y(t), and results in the following system:
⎡ 0 1 ⎤
⎡ x1 (t ) ⎤ ⎢ ⎡ x1 (t ) ⎤ ⎡ 0 ⎤
⎢ x (t )⎥ = ⎢ k b ⎥⎥ ⎢ ⎥ + ⎢ −1 ⎥ u(t ) (23.18a)
⎣ 2 ⎦ − − ⎣ x2 (t ) ⎦ ⎣ m ⎦
⎣ m m⎦
⎡ x1 (t ) ⎤
y(t ) = [1 0] ⎢ ⎥ (23.18b)
⎣ x2 (t ) ⎦
where (23.18a) is the state equation and (23.18b) the output equation, with [8]
⎡ 0 1 ⎤
⎡ 0 ⎤
A= ⎢ k b ⎥⎥ , B = ⎢ −1 ⎥ , C = [1 0] and D=0
⎢− − ⎣m ⎦
⎣ m m⎦
The control engineer will notice, for this example, that the state, control, output, and direct trans-
mission matrices are not functions of time explicitly and hence the system (23.18) is a linear, time-
invariant system.
. .
The integration in the previous diagram concerns that of x(t) to yield x(t), and since x(t) = [y(t), y(t)]T,
. T
then the initial condition for the Integrator block is x(0) = [y(0), y(0)] ; here x(0) = [2, 0] , i.e., the mass is
T
initially at rest with displacement 2.0 m (the reader may like to review the mechanism of the Integrator
block from a previous chapter concerning the initial condition required to start the integration process).
The system equations corresponding to the state and output equations that are to be computed
by the Newton method were presented in the previous chapter (i.e., Equation 22.19), where the
716 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
vector q is used in place of x in (23.1) to avoid confusion with the present usage of the order-reducing
vector variable x(t). The system equations for the mass–spring–damper system are as follows:
⎡ q0 (t )⎤ ⎡ u(t ) ⎤
⎢ q (t ) ⎥ ⎢ Bu(t ) ⎥
⎢ 1 ⎥ ⎢ ⎥
⎢ q2 (t )⎥ ⎢ Ax(t )⎥
⎢ ⎥ ⎢ ⎥
q3 (t ) ⎥ ⎢ x (t ) ⎥
F ( q(t )) = q(t ) − f ( q(t )) = ⎢ − =0 (23.19)
⎢q4 (t )⎥ ⎢ x(t ) ⎥
⎢ ⎥ ⎢ ⎥
⎢ q5 (t ) ⎥ ⎢ Du(t )⎥
⎢ q (t )⎥ ⎢ Cx(t ) ⎥
⎢ 6 ⎥ ⎢ ⎥
⎢⎣ q7 (t )⎥⎦ ⎢⎣ y(t ) ⎥⎦
The engineer can draw Figure 23.9 using the DiagramEng application, as shown in Figure 23.10,
and enter various selections of mechanical properties, m, b, and k and forcing functions u(t), to
generate different displacement outputs y(t), to analyze the physical response behavior of the mass–
spring–damper system.
The general analytic solution yg(t) ≡ y(t) to (23.17) is the sum of the homogeneous solution yh(t)
and the particular solution yp(t), i.e.,
yg (t ) = yh (t ) + yp (t ) (23.20)
The homogeneous solution is obtained by setting the right-hand side of (23.17) to zero as follows:
b k
⇒ y(t ) + y (t ) + y(t ) = 0 (23.21b)
m m
b k
r2 + r+ =0 (23.22a)
m m
FIGURE 23.10 Block diagram model of the state and output equations (23.18) drawn with DiagramEng.
Feedback-Based Signal Propagation 717
with roots
r1,2 =
1
2m (
−b ± b2 − 4km ) (23.22b)
where
ω = β is the angular frequency
Ci, for i ∈ {1, …, 4}, are constants [9]
Values for the constants may be determined with knowledge of quantities b, k, m, u(t) and the initial
conditions x(0). The mathematician will notice here that since α < 0, yh(t) → 0 as t → ∞, with a
decaying oscillatory motion.
Both roots are real but negative, and, hence, yh(t) → 0 as t → ∞, without oscillation.
The particular solution of (23.17) may be found by the “Method of Undetermined Coefficients,”
and given initial conditions, the coefficients may be determined and a general solution found. For
example, if u(t) = 1, a particular solution yp(t) = At + B may be sought, and upon substitution in
(23.17), it is found that A = 0 and B = 1/k, to yield yp(t) = 1/k. If, e.g., u(t) = sin(t), and the particular
solution yp(t) = Asin(t) + B cos(t) is sought, then it may be shown that A = (k − m)/( (k − m)2 + b2) and
B = −b/((k − m)2 + b2). If m = 1 = k, then A = 0 and B = −1/b and yp(t) = (−1/b)cos(t).
Figure 23.11a through c illustrates the three different damping conditions where the homoge-
neous solutions (yh(t)) are those provided by (23.23) through (23.25) and yp(t) = 1/k (given u(t) = 1)
for the initial conditions x(0) = [2, 0]T: (1) Figure 23.11a shows underdamped oscillatory vibration,
718 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
1.421395
1.276744
f (t)
1.132092
0.987441
0.842790
0.698138
0.553487
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(a) t (s)
1.900001
1.800001
1.700001
1.600001
f (t)
1.500001
1.400000
1.300000
1.200000
1.100000
1.000000
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(b) t (s)
1.800113
1.700169
1.600225
f (t)
1.500281
1.400337
1.300393
1.200449
1.100505
1.000561
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(c) t (s)
FIGURE 23.11 (a) Underdamped (b2 − 4km < 0) oscillatory vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1,
.
m = 1 = k, b = 0.5, x(0) = [y(0), y(0)]T = [2, 0]T, and δt = 10 −3 s. (b) Critically damped (b2 − 4km = 0) nonos-
.
cillatory vibration, where yg(t) →1.0 as t → ∞, for u(t) = 1, m = 1 = k, b = 2, x(0) = [y(0), y(0)]T = [2, 0]T, and
δt = 10 −3 s. (c) Overdamped (b2 − 4km > 0) nonoscillatory vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1,
.
m = 1 = k, b = 3, x(0) = [y(0), y(0)]T = [2, 0]T, and δt = 10 −3 s.
Feedback-Based Signal Propagation 719
where yg(t) → 1.0 as t → ∞, for u(t) = 1, m = 1 = k, and b = 0.5; (2) Figure 23.11b shows critically
damped nonoscillatory vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1, m = 1 = k, and b = 2; and (3)
Figure 23.11c shows overdamped nonoscillatory vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1,
m = 1 = k, and b = 3.
y h (t ) = C1 (sin(ωt ) + C2 ) (23.27)
where ω = k /m . Sallas indicates that the nature of the vibration depends on the relationship
between the applied frequency fa = γ/2π and the natural frequency of the system fs = ω/2π; if γ ≠ ω,
the general solution
F0 /m
yg (t ) = C1 (sin(ωt ) + C2 ) + cos( γt ) (23.28)
ω2 − γ 2
and the motion is bounded and may be periodic or aperiodic depending on the rationality of ω/γ, if
γ = ω,
F0
yg (t ) = C1 (sin(ωt ) + C2 ) + t sin(ωt ) (23.29)
2 ωm
the undamped system is said to be in resonance with aperiodic oscillatory motion and increasing
amplitude of vibration [9].
( )
−1
δt < ω −1 ≡ β −1 = (1 / 2m) 4km − b2 (23.30)
720 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
1.606840
1.213680
0.820520
0.427360
f (t)
0.034200
–0.358960
–0.752120
–1.145280
–1.538440
–1.931599
0.000000 0.800000 1.600000 2.400000 3.200000 4.000000 4.800000 5.600000 6.400000 7.200000 8.000000
t (s)
FIGURE 23.12 Underdamped (b2 − 4km < 0) highly oscillatory vibration, for u(t) = 0.5 sin(t), m = 1, k = 103,
.
b = 1, x(0) = [y(0), y(0)]T = [2, 0]T, and δt = 10 −4 s.
If b, k, and m are unity, then ω−1 ≅ 1.15 and δt = 0.1 s may be chosen; however, due to the numeri-
cal error incurred by Euler’s method, a smaller δt should be selected. If b = 1 = m and k = 103,
then ω−1 ≅ 0.03 and δt = 0.005 s may be selected. If an oscillatory driving force is applied, then its
frequency should also be considered, since as t → ∞, yh(t) → 0, and yg(t) → yp(t). For the example
provided earlier (Figure 23.12), δt = 10 −4 s, and the computation of the system with δt > 10 −4 s pro-
duced spurious results.
V = VR + VL + VC (23.31a)
di q
⇒ iR + L + =0 (23.31b)
dt C
where
i = dq/dt is the current
q(t) is the charge
VR, VL , VC, and V are the voltages of the resistor, inductor, capacitor, and the circuit loop, respec-
tively [11]
If i is replaced with dq/dt in (23.31b), then the following second-order linear differential equation
may be obtained:
R 1
q(t ) + q (t ) + q (t ) = 0 (23.32)
L LC
Feedback-Based Signal Propagation 721
1.849660
1.699318
1.548977
1.398635
f (t)
1.248294
1.097952
0.947611
0.797269
0.646928
0.496586
0.000000 3.000000 6.000000 9.000000 12.000000 15.000000 18.000000 21.000000 24.000000 27.000000 30.000000
t (s)
FIGURE 23.13 Underdamped (R2 − 4LC < 0) oscillatory charge, where q(t) ∈ [0.5, 1.5] as t → ∞, for
.
u(t) = Asin(t) + 1, A = 0.5, L = 1 = C, R = 1, x(0) = [q(0), q(0)]T = [2, 0]T, and δt = 10 −3 s.
where the mass m, spring constant k, and damping coefficient b of the mechanical system are equiv-
alent to the inductance L, the inverse of the capacitance 1/C, and the resistance R, respectively, as
may be observed by comparison with (23.21). If q(t) = ert is substituted in (23.32), then the following
characteristic equation is obtained:
R 1
r2 + r+ =0 (23.33a)
L LC
with roots
1 ⎛ 4L ⎞
r1,2 = ⎜ − R ± R2 − (23.33b)
2L ⎝ C ⎟⎠
The same analytic procedure as earlier may be followed to yield the underdamped (R2 − 4L/C < 0),
critically damped (R2 − 4L/C = 0), and overdamped (R2 − 4L/C > 0) cases, whose graphs will be of
.
the same shape as those of the mechanical system if the initial conditions x(0) = [q(0), q(0)]T = [Q, 0]T
.
are chosen, where Q = 2(F) is the initial charge, i(0) = q(0) = 0(Ω) is the initial current, and an input
voltage u(t) = 1 V is selected. In fact, if an alternating voltage source is supplied in conjunction with
the unit voltage source, i.e., if u(t) = Asin(t) + 1, where A = 0.5, then the charge and hence current will
alternate about unity with amplitude A, as shown in Figure 23.13, since the homogeneous solution
component would tend to zero.
x2 (t ) = x0 (t ) − αx3 (t ) (23.34a)
722 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
x0(t) x2(t)
f0(t) + Out
0 2
6
β
x4(t)
x5(t)
α
x1(t) x3(t) 5
f1(t) + Out
3 7
1
FIGURE 23.14 Coupled linear system (23.34) involving two input signals x0(t) and x1(t) and two output sig-
nals x2(t) and x3(t), where α and β are gain constants.
x3 (t ) = x1 (t ) − βx2 (t ) (23.34b)
where
x0(t) and x1(t) are independent input signals
x2(t) and x3(t) are the system output signals
α and β are gains applied to the output signals, as shown by the block diagram representation of
Figure 23.14 (block numbers appear beneath the blocks)
⎡ F0 (t )⎤ ⎡ x0 (t )⎤ ⎡ f0 (t ) ⎤ ⎡ x0 (t ) − f0 (t ) ⎤
⎢ F (t ) ⎥ ⎢ x (t ) ⎥ ⎢ f (t ) ⎥ ⎢ x (t ) − f (t ) ⎥
⎢ 1 ⎥ ⎢ 1 ⎥ ⎢ 1 ⎥ ⎢ 1 1 ⎥
⎢ F2 (t )⎥ ⎢ x2 (t )⎥ ⎢ f2 ( x(t ))⎥ ⎢ x2 (t ) − x0 (t ) + αx5 (t )⎥
⎢ ⎥=⎢ ⎥−⎢ ⎥=⎢ ⎥=0 (23.35)
⎢ F3 (t ) ⎥ ⎢ x3 (t ) ⎥ ⎢ f3 ( x(t )) ⎥ ⎢ x3 (t ) − x1 (t ) + βx4 (t ) ⎥
⎢ F4 (t )⎥ ⎢ x4 (t )⎥ ⎢ f4 ( x(t ))⎥ ⎢ x4 (t ) + βx2 (t ) ⎥
⎢ ⎥ ⎢ ⎥ ⎢ ⎥ ⎢ ⎥
⎢⎣ F5 (t )⎥⎦ ⎢⎣ x5 (t ) ⎥⎦ ⎢⎣ f5 ( x(t )) ⎥⎦ ⎢⎣ x5 (t ) + αx3 (t ) ⎥⎦
and then computed using Newton’s method. The mathematician will notice that the simultaneous
coupled equations (23.34) may in fact be solved analytically in terms of the input signals, to yield
x0 (t ) − αx1 (t )
x2 (t ) = (23.36a)
1 − αβ
and
x1 (t ) − βx0 (t )
x3 (t ) = (23.36b)
1 − αβ
where αβ ≠ 1.
Experimentation with this system using the DiagramEng application will give the user insight
into the nature of coupled systems, in particular the effect of the source signals and gain constants
Feedback-Based Signal Propagation 723
0.159995
0.120005
0.080015
0.040025
0.000035
f (t)
–0.039955
–0.079945
–0.119935
–0.159925
–0.199915
0.000000 0.640000 1.280000 1.920000 2.560000 3.200000 3.840000 4.480000 5.120000 5.760000 6.400000
(a) t (s)
0.479774
0.359805
0.239835
0.119865
–0.000105
f (t)
–0.120075
–0.240044
–0.360014
–0.479984
–0.599954
0.000000 0.640000 1.280000 1.920000 2.560000 3.200000 3.840000 4.480000 5.120000 5.760000 6.400000
(b) t (s)
FIGURE 23.15 (a) Output x2(t) as a result of the coupling of source inputs x0(t) = sin(t) and x1(t) = sin(t).
(b) Output x3(t) as a result of the coupling of source inputs x0(t) = sin(t) and x1(t) = sin(t).
on the different outputs. For example, if x0(t) = sin(t), x1(t) = sin(t), α = 2, and β = −2, then the output
signals x2(t) and x3(t), defined by (23.36), are those shown in Figure 23.15a and b, respectively, and
are out of phase with differing magnitudes.
dy
= − γy + δxy (23.37b)
dt
724 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
where
t represents the independent time variable
α and γ are the rates of growth of the prey and predator, respectively
β and δ are the rates of competitive efficiency for the prey and predator species, respectively,
where α, β, γ, δ > 0
A block diagram representation of this system (23.37) and its DiagramEng implementation are
shown in Figures 23.16 and 23.17, respectively, where the input signals are the growth rates α and γ,
which may be initially chosen given a condition of no interaction (β, δ = 0) between the species
(block numbers appear beneath the blocks).
x(t) x(t)
α + ∫dt Out
0 4 6 10
2
+ y(t) y(t)
γ ∫dt Out
1 3 5 7 11
FIGURE 23.16 Block diagram representation of the Lotka–Volterra system of two coupled first-order non-
linear differential equations (23.37).
FIGURE 23.17 Block diagram representation of the Lotka–Volterra system made using DiagramEng.
Feedback-Based Signal Propagation 725
dx
= αx ⇒ x(t ) = C1eαt (23.38a)
dt
dy
= − γy ⇒ y(t ) = C2e − γt (23.38b)
dt
where
C1 and C2 are constants
x(t) and y(t) are exponentially increasing and decreasing functions of time, for the prey and
predator populations, respectively
The population of the prey in the absence of the predator increases and that of the predator decreases,
as shown by Figure 23.18a and b, respectively.
Mathematicians familiar with the study of nonlinear dynamical systems and chaos, see, e.g.,
the texts of Kibble and Berkshire [12] and Strogatz [13], will recognize that the fixed points occur
3.839e+005
3.412e+005
2.986e+005
2.559e+005
f (t)
2.133e+005
1.706e+005
1.280e+005
8.533e+004
4.267e+004
1.999e+001
0.000e+000 5.000e-001 1.000e+000 1.500e+000 2.000e+000 2.500e+000 3.000e+000 3.500e+000 4.000e+000 4.500e+000 5.000e+000
t (s)
9.007e+000
8.006e+000
7.006e+000
6.005e+000
f (t)
5.004e+000
4.003e+000
3.003e+000
2.002e+000
1.001e+000
4.418e-004
0.000e+000 5.000e-001 1.000e+000 1.500e+000 2.000e+000 2.500e+000 3.000e+000 3.500e+000 4.000e+000 4.500e+000 5.000e+000
t (s)
FIGURE 23.18 (a) Population of the prey, x(t) = C1eαt, in the absence of the predator: x(0) = C1 = 20 and
α = 2. (b) The population of the predator, y(t) = C2 eγt, in the absence of the prey: y(0) = C2 = 10 and γ = 2.
726 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
. .
when the populations are in equilibrium, i.e., when x(t) = 0 and y(t) = 0, resulting in two such points:
(x0, y0) = (0, 0) and (x1, y1) = (γ/δ, α/β). The stability of these points may be determined by observing
the eigenvalues of the Jacobian matrix of the system (23.37), i.e.,
⎡ ∂x ∂x ⎤
⎢ ∂x ∂y ⎥
J( x, y) = ⎢ ⎥ (23.39a)
⎢ ∂y ∂y ⎥
⎢ ∂x ∂y ⎥⎦
⎣
⎡α − βy −βx ⎤
=⎢ (23.39b)
⎣δy δx − γ ⎥⎦
For (x0, y0) = (0, 0), the eigenvalues of J(x, y) are λ1 = α and λ2 = −γ, with corresponding eigenvectors
v1 = [1, 0]T (the unstable manifold, x axis) and v2 = [0, 1]T (the stable manifold, y axis), respectively,
and, hence, the critical point is a saddle point and the system is unstable, implying that the extinction
of both species is unlikely.
For (x1, y1) = (γ/δ, α/β), the eigenvalues of J(x, y) are λ1,2 = ±i αγ , i.e., they are purely
imaginary (Re(λ1,2) = 0) indicating the presence of a center (in the positive quadrant) rather
than a spiral, and Kibble states that as a result, there are cyclic variations in x(t) and y(t) which
are not in phase [12] (the population of the predators grows while that of the prey declines and
vice versa).
Here, system (23.1) may be formed resulting in a similar coupled structure to (23.34) and com-
puted using Newton’s method. The generalized output signal vector x(t) of (23.1) is replaced by q(t)
to avoid confusion with the existing population function x(t) in (23.37) and is composed of compo-
nents qi(t) for i ∈ [0, s − 1]. The system is thus
⎡ q0 (t )⎤ ⎡ α ⎤
⎢ q (t ) ⎥ ⎢ γ ⎥
⎢ 1 ⎥ ⎢ ⎥
⎢ q2 (t )⎥ ⎢ α − βy(t ) ⎥
⎢ ⎥ ⎢ ⎥
⎢ q3 (t ) ⎥ ⎢ − γ + δx(t ) ⎥
⎢q4 (t )⎥ ⎢ (α − βy(t )) x(t ) ⎥
F ( q(t )) = q(t ) − f ( q(t )) = ⎢ ⎥−⎢ ⎥=0 (23.40)
⎢ q5 (t ) ⎥ ⎢( − γ + δx(t )) y(t )⎥
⎢ q (t ) ⎥ ⎢ x (t ) ⎥
⎢ 6 ⎥ ⎢ ⎥
⎢ q7 (t )⎥ ⎢ y(t ) ⎥
⎢ ⎥ ⎢ ⎥
⎢ q8 (t ) ⎥ ⎢ δx(t ) ⎥
⎣⎢ q9 (t )⎦⎥ ⎣⎢ βy(t ) ⎦⎥
where output signal indices (i) correspond to the block numbers shown beneath the blocks in Figure
23.16. However, although the system (23.37) may be computed using the Newton method, one would
expect that the more appropriate approach to study this nonlinear dynamical system is through the
determination of critical points and the drawing of phase portraits in the x–y plane, as shown in
Figure 23.20.
A simulation of the Lotka–Volterra system (23.37) was made with the parameters: α = 2, γ = 2,
β = 1, and δ = 0.5, where the initial conditions of integration were x(0) = 20 and y(0) = 10, the ini-
tial output signals for the Divide blocks (which must be set since the Divide blocks are involved in
Feedback-Based Signal Propagation 727
28.535910
25.366511
22.197111
19.027711
f (t)
15.858311
12.688911
9.519511
6.350111
3.180712
0.011312
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(a) t (s)
14.281638
12.695432
11.109227
9.523021
f (t)
7.936816
6.350610
4.764405
3.178199
1.591994
0.005789
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(b) t (s)
FIGURE 23.19 (a) Cyclic variations in the prey population for the Lotka–Volterra system (23.37) with
parameters α = 2, γ = 2, β = 1, and δ = 0.5, where x(0) = 20 and y(0) = 10, and δt = 10 −4 s. (b) Cyclic varia-
tions in the predator population for the Lotka–Volterra system (23.37) with parameters α = 2, γ = 2, β = 1, and
δ = 0.5, where x(0) = 20 and y(0) = 10, and δt = 10 −4 s.
two feedback loops) were x4(t0) = −8 and x5(t0) = 8, and a time-step size of δt = 10 −4 s was chosen.
The cyclical variations in the populations of the prey and predators are shown in Figure 23.19a and
b, respectively, where the population of the prey leads that of the predator, i.e., the variations are in
fact not in phase.
The mathematician will notice that although the initial population sizes of the prey and predator
are x(0) = 20 and y(0) = 10, respectively, as the system evolves, less than one of each species exists
and then replicates to grow beyond their initial numbers. This is not biologically possible with the
parameters chosen, but the results do indicate the presence of cyclic variations that are out of phase
in the model. The interested researcher may like to experiment further with different parameters to
produce biologically credible results, i.e., population sizes that do not fall below 2.
In addition, the phase portrait of the population of the predator vs. the prey, i.e., y(t) vs. x(t),
may be generated, as shown in Figure 23.20, by saving the output data through the Output blocks
and plotting the two population values against each other (in a third-party graphical application).
728 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
12
(x(0), y(0))
10
y (t)
0
0 5 10 15 20 25 30 35
x (t)
FIGURE 23.20 Phase portrait of y(t) vs. x(t) showing the change in population of the predator vs. the prey,
where (x(0), y(0)) = (20, 10): the saddle point is at (0, 0) and the center at (γ/δ, α/β).
A saddle point resides at the origin (x0, y0) = (0, 0), and a center is present at (x1, y1) = (γ/δ, α/β).
As the prey declines in number, the predator grows, and vice versa, and neither species becomes
extinct. As t → ∞, it is observed that the trajectories do in fact form a center and not a spiral.
where
x(t), y(t), and z(t) are spatial coordinates that are functions of time t
σ, r, and b are the Prandtl number, Rayleigh number, and a quantity related to the height of the
fluid being modeled, respectively: σ, r, b > 0 [13]
For a thorough discussion of the Lorenz equations and the chaotic motion of the trajectories of the
system that settle onto a set called a strange attractor, the interested reader should consult the work
of Strogatz [13] (Chapter 9).
The interest here in the Lorenz equations is to determine whether the DiagramEng applica-
tion can generate the chaotic motion of the trajectories and reveal the complicated dynamics of
this coupled nonlinear dynamical system. Figure 23.21 is the block diagram model of the Lorenz
Feedback-Based Signal Propagation 729
x(t) x(t)
σ ∫dt Out
( y–x)
+
xy z(t) z(t)
+ ∫dt Out
bz
b
equations (23.41) and shows the three Integrator blocks and numerous feedback paths: Figure 23.22
is the equivalent DiagramEng implementation of the model.
A simulation computing system (23.41) was conducted with the following parameter settings:
σ = 10, r = 28, and b = 8/3. The initial conditions that appear often in the literature are x(0) = 0,
y(0) = 1, and z(0) = 1.05. However, as a result, a simulation time of approximately 50 s is required
FIGURE 23.22 Block diagram model of the Lorenz equations made using DiagramEng, which is equivalent
to Figure 23.21.
730 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
14.188229
10.713107
7.237986
3.762865
0.287744
f (t)
–3.187378
–6.662499
–10.137620
–13.612741
–17.087863
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(a) t (s)
18.945628
14.341970
9.738312
5.134655
f (t)
0.530997
–4.072661
–8.676319
–13.279977
–17.883634
–22.487292
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(b) t (s)
40.306502
36.565628
32.824754
29.083880
f (t)
25.343005
21.602131
17.861257
14.120382
10.379508
6.638634
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(c) t (s)
FIGURE 23.23 Computation of (23.41) where σ = 10, r = 28 and b = 8/3 with initial conditions, x(0) = y(0) =
z(0) = 10 and δt = 1.0 × 10 −6 (s): (a) plot of x(t) vs. t, (b) plot of y(t) vs. t, (c) plot of z(t) vs. t.
Feedback-Based Signal Propagation 731
40
35
30
25
z (t)
20
15
(x(0), z(0))
10
0 –5
–20 –15 –10 0 5 10 15 20
x (t)
FIGURE 23.24 Phase portrait of z(t) vs. x(t) showing the chaotic motion on the strange attractor set, where
σ = 10, r = 28, and b = 8/3 with initial conditions x(0) = y(0) = z(0) = 10.
to observe the aperiodic, irregular oscillation of the trajectories. Here, the initial conditions x(0) =
y(0) = z(0) = 10 are chosen, to observe the erratic dynamics over a shorter time frame of 20 s. As
there are Divide blocks that are the loop-repeated nodes in the model (see Chapters 19 and 22 con-
cerning feedback loops in a model), the output signals for these blocks need to be set by the user: the
–
initial values for the terms x. (0), x(r − z), xy, and bz were 0, 180, 100, and 26.66 , respectively. The
Euler integration scheme was used with a time-step size of δt = 1.0 × 10 s, but this is a problem
−6
due to its low order, and, hence, the solutions are not as accurate as those that may be determined
by a fourth-order Runge–Kutta scheme.
The trajectories shown in Figure 23.23a through c were compared with those generated by
MATLAB® and Simulink® for the same parameters and conditions stated earlier, including a relative
error tolerance of 1.0 × 10 −8: in general the aperiodic oscillatory dynamics is present, but the results
of the Euler scheme are not as accurate due to the accumulation of truncation and roundoff errors.
It is interesting to note that with a slight change in parameter values and/or the initial conditions, or
changes in spatial values due to accumulated numerical error, the resulting trajectories are substan-
tially different. The interested reader may experiment with various initial conditions and parameter
input and observe the significant changes in the computed trajectories for x(t), y(t), and z(t). A higher-
order numerical integration scheme, e.g., the fourth-order Runge–Kutta method, will be built into the
DiagramEng software in future.
Figure 23.24 shows the phase portrait of the spatial coordinates z(t) vs. x(t) and reveals the cha-
otic motion on the strange attractor set, where σ = 10, r = 28, and b = 8/3, with initial conditions,
x(0) = 10, y(0) = 10, and z(0) = 10.
732 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The approach used here to compute linear, nonlinear, and coupled differential equations, in
particular those described with feedback loops, using the Newton method, is general and suitable
for the current DiagramEng project, but may not be the most appropriate to solve specific problems
where different computational and analytic procedures may be more effective. Hence, the developer
may need to extend the current software by providing different computational algorithms for prob-
lems belonging to certain application classes where the Newton-method-based approach may not
be optimally suited. In addition, higher-order numerical integration schemes or stiff system solvers
should be used where possible for problems exhibiting highly oscillatory dynamics.
23.7 SUMMARY
The previous chapter introduced Newton’s method and program functionality for constructing a
system of equations in preparation for computation using a Newton-method-based solver. In this
chapter, the newt() method of Press et al. [1,2] and all subsidiary functions were introduced.
A C++-oriented transformation was made, to replace global variables with class member variables,
to perform memory management with the operators new and delete, and to use array indexing from
“0” to “n − 1”, and the source and header files “Newton.cpp/.h” were provided. Five simple test case
examples were used to determine the feasibility and accuracy of the newt() method.
Then the Newton-method-based solver was added to the DiagramEng project using a
CNewtonMethod class with member methods and variables involving the files “NewtonMethod.
cpp/.h”. Testing of the Newton-method-based algebraic-loop-resolving code in the DiagramEng
application was then conducted using six problem types involving feedback loops: (1) a linear
problem, (2) a first-order linear ODE, (3) a second-order linear ODE representing a mechanical/
electrical problem transformed into the state-space equations, (4) a coupled linear system, (5) the
Lotka–Volterra system consisting of two coupled first-order nonlinear differential equations repre-
senting population dynamics, and (6) the Lorenz equations showing chaotic dynamical motion on
the strange attractor set.
REFERENCES
1. Press, W. H., Teukolsky, S. A., Vetterling, W. T., and Flannery, B. P., Numerical Recipes in C: The Art of
Scientific Computing, 2nd edn., Cambridge University Press, Cambridge, U.K., 2002.
2. Press, W. H., Teukolsky, S. A., Vetterling, W. T., and Flannery, B. P., Numerical Recipes in C++: The Art
of Scientific Computing, 2nd edn., Cambridge University Press, Cambridge, U.K., 2002.
3. Think Bottom Up Pty. Ltd., http://www.thinkbottomup.com.au/site/, (accessed in 2009).
4. Fisher, M. E., Introductory Numerical Methods with the NAG Software Library, The University of
Western Australia, Perth, WA, 1989.
5. Kelley, A. and Pohl, I., A Book On C: Programming in C, 2nd edn., Benjamin Cummings, Redwood City,
CA, 1990.
6. Shabana, A. A., Computational Dynamics, 2nd edn., John Wiley & Sons, New York, 2001.
7. Simulink® 6, Using Simulink®, MATLAB® & Simulink®, The MathWorks, Natick, MA, 2007.
8. Ogata, K., Modern Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.
9. Salas, S. L. and Hille, E., Calculus: One and Several Variables (Complex Variables, Differential Equations
Supplement), 6th edn., John Wiley & Sons, New York, 1990.
10. Fox, B., Jennings, L. S., and Zomaya, A. Y., Numerical computation of differential-algebraic equations
for non-linear dynamics of multibody systems involving contact forces, ASME Journal of Mechanical
Design, 123(2), 272–281, 2001.
11. Sears, F. W., Zemansky, M. W., and Young, H. D., University Physics, 7th edn., Addison-Wesley, Reading
MA, 1987.
12. Kibble, T. W. B. and Berkshire, F. H., Classical Mechanics, 5th edn., Imperial College Press, London,
U.K., 2004.
13. Strogatz, S. H., Nonlinear Dynamics and Chaos: With Applications to Physics, Biology, Chemistry, and
Engineering, Addison-Wesley, Reading MA, 1994.
24 Placing an Edit Box
on a Toolbar
24.1 INTRODUCTION
Now that the SignalPropagationDirect() and SignalPropagationSystemOfEqua
tions() functions have been shown to work consistently and predictably for a range of models,
further additions and refinements may be made to the DiagramEng application to enhance its use-
fulness. The following instructions indicate how to place an Edit box control, of type CEdit, on a
toolbar, which can be used to display the time value t ∈ [t0, tf ] (s) of the simulation and the execution
time texe (s) required, where t0 and tf are the initial and final time points, respectively. The instruc-
tions presented in the ensuing sections follow the work of Chapman [1] (Chapter 12, pp. 257–270),
where Chapman discusses the placement of a Combo box on a toolbar that accepts user input and
displays selected output. Here, however, a read-only Edit box is introduced that simply displays
time-based output, i.e., t and texe.
733
734 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
1. Add a protected member variable to the CMainFrame class with the name
“m_pctlEditBoxTime” of type pointer-to-CEdit (CEdit*).
2. Edit the CMainFrame::OnCreate() function as shown in bold to construct the Edit
box and place it on the Common Operations toolbar (the rest of the function remains
unchanged from Chapter 12).
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
…
// DiagramEng (start)
// -- CREATE THE CommonOps TOOLBAR
// NOTE: ‘this’ is the ptr to the parent frame wnd to which the
toolbar will be attached.
// CreateEx creates the toolbar
// LoadToolBar loads the toolbar specified by the ID
if(!m_wndTBCommonOps.CreateEx(this, TBSTYLE_FLAT, WS_CHILD |
WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |
CBRS_SIZE_DYNAMIC) || !m_wndTBCommonOps.
LoadToolBar(IDR_TB_COMMON_OPS) )
{
// Failed to create msg.
TRACE0(“Failed to create toolbar\n”);
return -1;
}
// -- PLACING AN EDIT BOX ON THE CommonOps TOOLBAR
// Properties of Edit box Control to be placed on CommonOps Toolbar
int nEditBoxWidth = 200; // with of the Edit box control
int nIndex = 10; // index of button or separator whose info
is to be set: btns (0-8), separator (9), separator placeholder (10)
// Set Properties of Placeholder Separator for Edit box Control
m_wndTBCommonOps.SetButtonInfo(nIndex, IDC_EB_COTB_TIME,
TBBS_SEPARATOR, nEditBoxWidth);
// Get the rectangle coords of the placeholder where the Edit box
will be created.
CRect rect;
m_wndTBCommonOps.GetItemRect(nIndex, &rect); // nIndex is index of
button or separator whose rectangle cords are rqd.
// Construct the CEdit object.
m_pctlEditBoxTime = new CEdit;
// Create the Edit box
m_pctlEditBoxTime->Create(ES_LEFT|ES_READONLY|WS_BORDER|WS_
CHILD|WS_VISIBLE, rect, &m_wndTBCommonOps, IDC_EB_COTB_TIME);
// -- END OF PLACING AN EDIT BOX ON THE CommonOps TOOLBAR
// -- END CREATION OF CommonOps TOOLBAR
Placing an Edit Box on a Toolbar 735
TABLE 24.1
Properties of the Common Operations Toolbar Buttons
Entity Index Entity (Button/Separator) Entity ID ID Value
0 Select All ID_EDIT_SELECTALL 32773
1 Add Multiple Blocks ID_EDIT_ADD_MULTI_BLOCKS 32839
2 Auto Fit Diagram ID_VIEW_AUTO_FIT_DIAGRAM 32852
3 Build Model ID_MODEL_BUILD 32783
4 Start Simulation ID_SIM_START 32784
5 Stop Simulation ID_SIM_STOP 32785
6 Numerical Solver ID_SIM_NUM_SOLVER 32848
7 Show Annotations ID_FORMAT_SHOW_ANNOTATIONS 32849
8 Track Multiple Items ID_INIT_TRACK_MULTIPLE_ITEMS 32837
9 Separator — —
10 Separator (Placeholder) To be: IDC_EB_COTB_TIME
Initially the properties of the placeholder separator on the Common Operations toolbar need to be set to
those of the to-be-created Edit box. Hence, the width is set to be the Edit box width, “nEditBoxWidth”,
and the index, “nIndex”, is that of the button or separator (here the separator) whose information is
to be set. Table 24.1 lists all the indices, names and IDs, of the entities on the Common Operations
toolbar. The placeholder separator is used to position the Edit box, and its index is “10” as shown.
Then the SetButtonInfo() function is called passing in the index, “nIndex”; the ID of the
Edit box control, “IDC_EB_COTB_TIME” (to be added); the type of control (a separator); and the
width of the separator, i.e., the width of the Edit box to appear in this location.
3. A unique ID is to be used for the Edit box control: right click on the DiagramEng resources
folder in the Resource View of the Workspace pane, select Resource Symbols, and add
the new control ID, “IDC_EB_COTB_TIME”, for the Edit box control residing on the
Common Operations toolbar that will display the model’s simulation and execution times,
as shown in Figure 24.1. For example, choose the ID, 32700, since at present this is not in
use, but check the “resource.h” header file to avoid a duplicate.
Then the rectangle coordinates of the placeholder, with index “nIndex”, where the Edit box is to be
created, are determined by the call to GetItemRect(). Thereafter the CEdit constructor is called
to construct a CEdit object where the address returned by new is assigned to the pointer-to-CEdit,
“m_pctlEditBoxTime”. Finally, the Create() function is called upon the “m_pctlEditBoxTime”
member variable, to create the Edit box. The first (compound) argument involves edit and window
styles; the second is the control’s size and position (rect); the third is the address of the control’s
parent window, i.e., the Common Operations toolbar; and the final argument is the ID of the Edit
box control.
736 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 24.1 Resource Symbols showing the Edit box control ID, “IDC_EB_COTB_TIME”.
1. A function in the Main frame class, CMainFrame, is to be called from the document
class to update the Edit box. Hence, add a public member function to the CMainFrame
class with the prototype void CMainFrame::UpdateEditBoxTimeOnCommonOps
Toolbar(CString string), and edit the function as shown. The incoming argument
is the simulation time (t) or execution time (texe) converted to a CString variable, origi-
nally generated by the signal propagation functions, SignalPropagationDirect()
or SignalPropagationSystemOfEquations().
Here both SetWindowText() and ReplaceSel() are called upon the pointer-to-CEdit
“m_pcltEditBoxSimTime”: the former function replaces all the text in the Edit box with the empty
string, in effect clearing the box, and the latter replaces the current selection with the incoming
string, and the messages used are WM_SETTEXT and EM_REPLACESEL, respectively. If there
is no current selection, as is the case here with the empty string in place, the replacement text is
inserted at the current cursor location [2]. This combination of calls allows the Edit box to be
Placing an Edit Box on a Toolbar 737
refreshed immediately. If SetWindowText() is solely used where the argument is the incoming
string, then the Edit box would not be refreshed fast enough to dynamically display the increment-
ing time values, t (s), during the simulation.
The reader will notice that SetWindowText() is a CWnd method, but because the Edit box
control is of type CEdit, and CEdit derives from CWnd, then the function may be called on the
CEdit control variable. The interested reader should consult the Microsoft Foundation Class Library
Hierarchy Chart [2] to see the class structure.
2. Now the function in the document class needs to be added, to call the CMainFrame function
to update the Edit box control. Hence, add a public member function to the CDiagramEngDoc
class with the following prototype: void CDiagramEngDoc::UpdateCommonOpsTool
bar(CString string), and edit the function as shown.
This function gets the first view position, “pos”; the pointer to the view, “pView”; and then
a pointer to the CMainFrame, “pFrame”, upon which the previously introduced function,
UpdateEditBoxTimeOnCommonOpsToolbar(), is called.
#include “stdafx.h”
#include “DiagramEng.h”
#include “DiagramEngDoc.h”
// DiagramEng (start)
#include <math.h> // rqd. since OnDeleteItem() uses math fns.
#include “afxext.h” // rqd. since TrackItem() uses a
CRectTracker obj.
#include “BlockLibDlg.h” // rqd. since OnEditAddMultipleBlocks() has
a CBlockLibDlg obj.
738 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CSystemModel::SignalPropagationDirect()
{
int n_TimeSteps; // total no. of time steps for numerical integration
…
list<CConnection*>::iterator it_con; // iterator for list of
CConnection ptrs.
// Get a ptr-to-CDiagramEngDoc to call UpdateCommonOpsToolbar().
CDiagramEngDoc *pDoc = GetDocumentGlobalFn();
// Get the actual connection list by reference
list<CConnection*> &con_list = GetConnectionList(); // get the
connection list
…
// -- Loop for n_TimeSteps + 1, from t_0 = 0, UP TO AND INCLUDING the
last time point t_n
for(t_cnt = 0; t_cnt <= n_TimeSteps; t_cnt++)
{
t = t_init + t_cnt*delta_t; // t cnt, i.e. i-th time point t_i,
or simply t.
…
// Update the Edit box on the CommonOps toolbar with the
simulation time value
sMsg.Empty();
sMsg.Format(“t = %e/ %g (s)”, t, m_dTimeStop); // %g is the
shorter of e-format or f-format
pDoc->UpdateCommonOpsToolbar(sMsg);
// Check if stop button pressed
bStop = CheckStopButtonPressed();
if(bStop)
{
break;
}
}// end (time)
// -- EXECUTION TIME STOP
tv2 = clock();
Placing an Edit Box on a Toolbar 739
(a)
(b)
FIGURE 24.2 Display of time values in the Edit box control on the Common Operations toolbar: (a) model
simulation time, t (s), (b) model execution time, texe (s).
The developer will notice that during the simulation, the current time point, t (s), is displayed in the Edit
box (Figure 24.2a), and after the simulation, the execution time, texe (s), is displayed (Figure 24.2b),
implicitly indicating the end of the experiment.
The message indicates that there is a memory leak, in particular, that the CEdit object, which was
created on line 133 of file “MainFrm.cpp”, with the statement, “m_pctlEditBoxTime = new CEdit;”,
Placing an Edit Box on a Toolbar 741
has not been deleted. Hence, delete the member variable “m_pcltEditBoxTime” of type pointer-to-
CEdit in the CMainFrame class destructor as follows.
CMainFrame::∼CMainFrame()
{
// DiagramEng (start)
if(m_pctlEditBoxTime != NULL)
{
delete m_pctlEditBoxTime;
m_pctlEditBoxTime = NULL;
}
// DiagramEng (end)
}
Now the CMainFrame class constructor should be augmented to initialize the member variable to
NULL to prevent erroneous deletion, as shown.
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
m_pctlEditBoxTime = NULL;
}
Now upon running the program using the debugger and then exiting normally, there is no memory
leak reported in the Debug output window.
24.6 SUMMARY
The work of Chapman [1] originally discusses the placement of a Combo box on a toolbar,
and here that development is adopted to place a read-only Edit box control on the Common
Operations toolbar that allows the user to dynamically see the simulation time, t (s), of a run-
ning experiment and then the execution time, texe (s), at the end of the simulation. The resource
file “DiagramEng.rc” is initially amended and two separators placed at the end of the Common
Operations toolbar. Then a separator is set up using SetButtonInfo() in preparation for
the creation of the Edit box control, using a pointer-to-CEdit variable “m_pctlEditBoxTime”,
and the functions GetItemRect() and Create(). The Edit box is updated through the
CMainFrame function, UpdateEditBoxTimeOnCommonOpsToolbar(), which is called
from the CDiagramEngDoc function, UpdateCommonOpsToolbar(), which in turn is
called from the CSystemModel simulation functions, SignalPropagationDirect() and
SignalPropagationSystemOfEquations(). Finally, a memory leak was detected using the
debugger, and the CMainFrame class member variable, “m_pctlEditBoxTime”, was initialized in
the constructor and deleted in the destructor.
REFERENCES
1. Chapman, D., Teach Yourself Visual C++ 6 in 21 Days, Sams Publishing, Indianapolis, IN, 1998.
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
25 Serialization
25.1 INTRODUCTION
“Serialization” and “deserialization” are the processes of storing and retrieving application data on
and from the system drive in the form of a file, respectively [1]. This may be performed either by using
the Document class Serialize() function in conjunction with the CArchive class or by writing a
specialized (virtual void) Serialize() function for a particular user-defined class that performs
the equivalent saving and restoring actions. In addition, two macros, DECLARE_SERIAL and
IMPLEMENT_SERIAL, are used in the class declaration and definition, respectively [1]. The inter-
ested reader should consult the work of Chapman [1] for examples of the use of the Serialize()
function, in particular, Chapter 10, Creating Single Document Interface Applications, and
Chapter 13, Saving and Restoring Work—File Access.
Here, an alternative method is used to perform the equivalent serialization and deserialization
actions, where data are written to and read from a file directly, using functions associated with the
Open, Save, Save As, Close, and Exit entries under the File menu. All the system model data, includ-
ing block-based, connection-based, and parameter-value-based data, are to be recorded such that
upon opening a previously saved model, the entire model can be immediately restored and validated
and the simulation rerun, to regenerate results identical to those obtained prior to serialization.
743
744 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
data types. Tables 25.1 through 25.19 present the data types, member variables, and brief descrip-
tions of the data that are to be both written to and read from a model text (“*.txt”) file: they are listed
by data type for convenience. Variables that are of type CPoint in the DiagramEng project are writ-
ten here in Cartesian component integer form. Some variables in Tables 25.1 through 25.19 are not
explicitly saved for one or more reasons: (1) they are not set by the user through the dialog parameter
input windows, (2) they have default static values used in object reconstruction, (3) they are deter-
mined dynamically, (4) they are the equivalent duplicates of existing member data in a different data
type, (5) they represent contained objects whose explicit data are saved by the appropriate contained
classes, or (6) they may be saved by alternative methods, e.g., the COutputBlock class will provide
a special mechanism to save output data directly to a separate data file.
TABLE 25.1
CSystemModel Serialization Data: Data Type, Member Variable,
and Description
Data Type Member Variable Description
int m_iModelStatusFlag Model status flag
int m_iNOutputBlocks No. of model output blocks
int m_iNSourceBlocks No. of model source blocks
int m_iNSourceLoopTours No. of source-to-loop-repeated-block tours
int m_iNSourceOutputTours No. of source-to-output-block tours
double m_dATOL Absolute error tolerance parameter
double m_dRTOL Relative error tolerance parameter
double m_dTimeStart Simulation start time
double m_dTimeStepSize Time-step size
double m_dTimeStop Simulation stop time
CString m_strIntegrationMethod Integration method: Euler, Runge–Kutta
CString m_strModelName Name of current system model
CString m_strTimeStepType Time-step type: fixed-step or variable-step
CString m_strWarning Diagnostic warning messages
list<int*> m_lstSourceLoopTour List of arrays of source-loop-block tours
list<int*> m_lstSourceOutputTour List of arrays of source-output-block tours
list<CBlock*> m_lstBlock List of blocks
list<CConnection*> m_lstConnection List of connections
list<CConnection*> m_lstConnectionError List of disconnected connections
Serialization 745
TABLE 25.2
CBlock Serialization Data: Data Type, Member Variable,
and Description
Data Type Member Variable Description
Int m_ptBlockPosition.x Block center of mass x-coordinate
Int m_ptBlockPosition.y Block center of mass y-coordinate
Int m_iPenColor Pen color used to draw blocks
int m_iPenWidth Pen width used to draw blocks
CBlockShape m_BlockShape Block-shape-contained member object
CString m_strBlockName Block name
CSystemModel* m_pParentSystemModel Pointer to the parent system model
CSystemModel* m_pSubSystemModel Pointer to a contained subsystem
model
vector<CPort*> m_vecInputPorts Vector of block input ports
vector<CPort*> m_vecOutputPorts Vector of block output ports
“m_pSubSystemModel”, is currently not in use since subsystem functionality has not been added
to the project at this stage and is to be added later. The integer members “m_iPenColor” and
“m_iPenWidth”, used in the drawing of blocks, do not need to be serialized as these are internal data
members and are not altered by the user. Only the block position “m_ptBlockPosition” and name
“m_strBlockName” need to be saved.
TABLE 25.3
CBlockShape Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
double m_dBlockHeight Block height
double m_dBlockWidth Block width
EBlockDirection m_eBlockDirection Block direction: left, right, up, down
EBlockShape m_eBlockShape Block shape: ellipse, rectangle, triangle
746 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 25.4
CPort Serialization Data: Data Type, Member Variable,
and Description
Data Type Member Variable Description
int m_ptPortPosition.x Port position x-coordinate
int m_ptPortPosition.y Port position y-coordinate
int m_ptPortSignPosition.x Port sign position x-coordinate
int m_ptPortSignPosition.y Port sign position y-coordinate
double m_dPortPositionAngle Angle defining the location of the port
CBlock &m_rRefToBlock Reference to parent block
CString m_strPortName Port name that reflects the port sign
EPortArrowDirection m_ePortArrowDirec Port arrow direction: left, right, up, down
TABLE 25.5
CConnection Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
int m_ptHead.x Head point x-coordinate
int m_ptHead.y Head point y-coordinate
int m_ptTail.x Tail point x-coordinate
int m_ptTail.y Tail point y-coordinate
CPoint* m_pRefFromPoint Point from which connection may be drawn
CPort* m_pRefFromPort Port from which connection emanates
CPort* m_pRefToPort Port to which connection is attached
CSignal* m_pSignal Signal object containing connection data
list<CPoint> m_lstBendPoints List of connection bend points
Serialization 747
TABLE 25.6
CSignal Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
int m_iNcols No. of columns of signal matrix
int m_iNrows No. of rows of signal matrix
int m_iValidity Signal-data-based validity
double** m_dSignalMatrix Matrix signal value
CString m_strOutputSignal Output signal as a CString
CString m_strSignalName Signal name
TABLE 25.7
CConstantBlock Serialization Data: Data Type,
Member Variable, and Description
Data Type Member Variable Description
int m_iNcols No. of columns of constant matrix
int m_iNrows No. of rows of constant matrix
double** m_dConstMatrix Constant matrix
CString m_strConstValue CString constant matrix
748 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 25.8
CDerivativeBlock Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
int m_i_t_cnt_ref Reference time count
int m_iDerivativeMethod Derivative method: three-point, five-point
int m_iNcallsPerTimeCnt No. of times function called per time point
int m_iNrows No. of rows of incoming signal data
double* m_f_at_t_minus_h f(t − h)
double* m_f_at_t_minus2h f(t − 2h)
double** m_dMatrixOut Matrix data of output signal
TABLE 25.9
CDivideBlock Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
int m_iMultType Multiplication type: elemental, matrix
int m_iNDivideInputs No. of division input ports
int m_iNMultiplyInputs No. of multiplication input ports
TABLE 25.10
CGainBlock Serialization Data: Data Type, Member Variable,
and Description
Data Type Member Variable Description
int m_iGainType Gain type: elemental, gain × input, input × gain
int m_iNcols No. of columns of gain matrix
int m_iNrows No. of rows of gain matrix
double** m_dGainMatrix Gain matrix
CString m_strGainValue CString gain matrix
and records the gain matrix as a string for dialog window parameter input purposes and hence does
not need to be serialized (Table 25.10).
TABLE 25.11
CIntegratorBlock Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
int m_i_t_cnt_ref Reference time count
int m_iLength Length of initial condition vector
int m_iNcallsPerTimeCnt No. of times function called per time point
int m_iNcols No. of columns of the initial condition vector
int m_iNrows No. of rows of the initial condition vector
int m_iNrowsOut No. of rows of output matrix data
double* m_dICVector Initial condition vector
double* m_y_at_t_minus_h y(t − h)
.
double* m_y_dot_at_t_minus_h y(t − h)
double** m_dMatrixOut Matrix data of output signals
CString m_strICVector CString initial condition vector
TABLE 25.12
CLinearFnBlock Serialization Data: Data Type,
Member Variable, and Description
Data Type Member Variable Description
double m_dDerivative Derivative of linear function
double m_dTimeFinal Final time of signal activation
double m_dTimeInit Initial time of signal activation
double m_dValueInit Initial value of signal
TABLE 25.13
COutputBlock Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
int m_iNcols No. of columns of output matrix data
int m_iNotation Type of notation: standard, scientific
int m_iNrows No. of rows of output matrix data
int m_iTimePtDisplay Time point display: show, hide
double** m_dOutputMatrix Output matrix data
TABLE 25.14
CSignalGeneratorBlock Serialization Data: Data Type,
Member Variable, and Description
Data Type Member Variable Description
double m_dAmplitude Amplitude of generated signal
double m_dFrequency Frequency of generated signal
double m_dPhase Phase of generated signal
CString m_strFnType Function type: random, sine, square
CString m_strUnits Units: Hz, rad/s
TABLE 25.15
CSubsystemBlock Serialization Data: Data
Type, Member Variable, and Description
Data Type Member Variable Description
CString m_strInputPortName Input port name
CString m_strOutputPortName Output port name
TABLE 25.16
CSubsystemInBlock Serialization Data:
Data Type, Member Variable,
and Description
Data Type Member Variable Description
CString m_strInputPortName Input port name
TABLE 25.17
CSubsystemOutBlock Serialization Data: Data
Type, Member Variable, and Description
Data Type Member Variable Description
CString m_strOutputPortName Output port name
TABLE 25.18
CSumBlock Serialization Data: Data Type,
Member Variable, and Description
Data Type Member Variable Description
int m_iNAddInputs No. of addition inputs
int m_iNSubtractInputs No. of subtraction inputs
TABLE 25.19
CTransferFnBlock Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
int m_iLengthDenom Length of denominator coefficient vector
int m_iLengthNumer Length of numerator coefficient vector
double* m_dDenomVector Vector of denominator coefficients
double* m_dNumerVector Vector of numerator coefficients
CString m_strDenomCoeffs Denominator coefficients
CString m_strNumerCoeffs Numerator coefficients
an existing file or prompts the user to provide a filename if one has not yet been provided, (4) Close
checks whether the document content has been modified and prompts the user to save before closing the
existing document, and (5) Exit closes existing documents and then exits the application. In addition,
other functions are required to complete the serialization-based user interaction process and include
saving all documents, opening recent files, closing all documents, and various utility accessor functions.
25.3.1.1 CDiagramEngDoc::OnFileOpen()
The OnFileOpen() function is required to initiate the opening of an existing file to restore a
previously saved model diagram.
1. Add an event-handler function associated with the ID_FILE_OPEN object ID for the
CDiagramEngDoc class with the prototype void CDiagramEngDoc::OnFileOpen
(void).
2. Edit the function as shown to first check whether a model already exists and to prompt
the user to erase it, generate a new document, or abort. If a new model is to be loaded,
the file path is obtained and passed to the to-be-added ReadDiagramEngDataFromFile()
function to read data from a file in order to restore a previously saved model.
void CDiagramEngDoc::OnFileOpen()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int btnSel; // button selection
BOOL dlgType = TRUE; // TRUE => File Open dlg,
FALSE => File Save dlg
CString strDefExt = “.txt”; // default file extension
CString strFileName = “model_data.txt”; // file name
CString strFilter = “All Files (*.*)|*.*|Plain Text (*.txt)|*.txt||”;
// string pairs specifying filters for file list box
CString sMsg;
DWORD dwFlags = OFN_ENABLESIZING | OFN_HIDEREADONLY;
// customization flags
if(btnSel == IDYES)
{
// Delete System Model Lists
m_SystemModel.DeleteBlockList();
m_SystemModel.DeleteConnectionList();
m_SystemModel.DeleteSourceOutputLoopLists();
if(dlgFile.DoModal() == IDOK)
{
m_strFilePath = dlgFile.GetPathName(); // get the path name
including file name and location
SetTitle(m_strFilePath); // set the title of the
document window
The conditional section concerning whether an existing model resides on the current document
is required to allow the user to either erase the existing model and hence load a new model on
the current document, or leave the current model on the current document but generate a whole
new document upon which a new model will be placed (Figure 25.1a). In the latter case, a call
to OnFileOpen() is made upon the application object, “theApp”: this function is described in
Section 25.3.2, but the flow of control is pursued here to clarify the behavior of the file opening
mechanism. CDiagramEngApp::OnFileOpen() calls CWinApp::OnFileNew() to create
the new document, and then CDiagramEngDoc::OnFileOpen() is ultimately called upon the
newly created document pointer. That is, the function mentioned earlier is in effect called a second
time (indirect recursion), but now with the new document in place, upon which the user-selected
model may be placed. The developer may comment out the line “theApp.OnFileOpen();”, for
now, and then uncomment it when the CDiagramEngApp::OnFileOpen() function is added to
the project a little later.
754 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
(a) (b)
FIGURE 25.1 File opening process: (a) a message box providing the option to erase the existing model and
(b) the CFileDialog Open dialog object created in the OnFileOpen() function.
Control flow then progresses to create a CFileDialog dialog window object used to present the
File Open dialog as shown in Figure 25.1b: the dialog type is TRUE for the File Open rather than
Save As dialog window, the default file extension is “.txt”, the initial filename is “model_data.txt”,
the flag combination enables sizing and hides the read only check box, the string filter consists
of string pairs to appear in the file list box, and the pointer to the dialog box’s parent window is
NULL [2].
The file path “m_strFilePath” is obtained using the function GetPathName() called upon the
dialog object, and used to set the document window title using SetTitle(), and then passed to the
ReadDiagramEngDataFromFile() function to be added in Section 25.5. If the read was suc-
cessful, i.e., if ReadDiagramEngDataFromFile() returns “0” (no error), then the file is added
to the application’s recent file list via the call to AddToRecentFileList() (the reason for this
will become clearer in the section concerning the OnFileRecentFileOpen() function). The
member variable “m_strFilePath” is also used in the OnFileSave() function (see Section 25.3.1.4)
and should be added to the document class.
1. Hence, add a CString private member variable to the CDiagramEngDoc class named
“m_strFilePath”.
2. Initialize this variable to an empty string in the CDiagramEngDoc constructor function as
shown in bold.
CDiagramEngDoc::CDiagramEngDoc()
{
// TODO: add one-time construction code here
// DiagramEng (start)
m_iKeyFlagTrack = 0;
m_iFineMoveFlag = 0;
m_iRubberBandCreated = 0;
m_dDeltaLength = 50.0;
m_strFilePath = “”;
// DiagramEng (end)
}
Serialization 755
25.3.1.2 CDiagramEngDoc::ReadDiagramEngDataFromFile()
The CDiagramEngDoc::OnFileOpen() function calls the ReadDiagramEngDataFromFile()
method to open an existing document whose data are used to reconstruct a system model.
1. Add a public member function to the CDiagramEngDoc class with the prototype int
CDiagramEngDoc::ReadDiagramEngDataFromFile(CString file_path).
2. Edit the function as shown to print out the file path and name: this function will be
completed in the section concerning the reading of data from a file (later).
return file_flag;
}
25.3.1.3 CDiagramEngDoc::OnFileSaveAs()
The Save As option under the File menu allows the user to save a document in a particular user-
specified file.
FIGURE 25.2 CFileDialog Save As dialog object created in the OnFileSaveAs() function.
756 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
void CDiagramEngDoc::OnFileSaveAs()
{
// TODO: Add your command handler code here
// DiagramEng (start)
BOOL dlgType = FALSE; // TRUE => File Open dlg,
FALSE => File Save dlg
CString strDefExt = “.txt”; // default file extension
CString strFileName = “model_data.txt”; // file name
CString strFilter = “All Files (*.*)|*.*|Plain Text (*.txt)|*.txt||”;
// string pairs specifying filters for file list box
DWORD dwFlags = OFN_ENABLESIZING | OFN_HIDEREADONLY; // customization
flags
// Create a CFileDialog wnd
CFileDialog dlgFile(dlgType, strDefExt, strFileName, dwFlags,
strFilter, NULL);
if(dlgFile.DoModal() == IDOK)
{
m_strFilePath = dlgFile.GetPathName(); // get the file path
and name
SetTitle(m_strFilePath); // set the title of the current doc
WriteDiagramEngDataToFile(m_strFilePath, 1); // 1 implies
overwrite warning will appear
}
// DiagramEng (end)
}
25.3.1.4 CDiagramEngDoc::OnFileSave()
The Save option allows the user to save a system model diagram: if the document content has changed
(IsModified()) and has not yet been saved to a file (m_strFilePath == “”), then the Save As dialog
should appear to obtain a file path from the user. If a file path already exists, then the document
content is written directly (without prompting) to the file specified by the path “m_strFilePath”.
void CDiagramEngDoc::OnFileSave()
{
// TODO: Add your command handler code here
// DiagramEng (start)
if(IsModified() )
{
// Check whether a file path already exists
if(m_strFilePath == “”)
{
OnFileSaveAs();
}
else
{
WriteDiagramEngDataToFile(m_strFilePath, 0);
}
}
// DiagramEng (end)
}
The WriteDiagramEngDataToFile() function is called here with the flag-like variable set to
“0”, indicating that upon a file-save action, no diagnostic message is to appear to the user concern-
ing file overwriting, since the intent of saving is to overwrite the contents of the existing file speci-
fied by the file path “m_strFilePath”.
25.3.1.5 CDiagramEngDoc::OnFileClose()
The Close option under the File menu first checks that the current document has been modified
using IsModified(); if so, a prompt appears asking the user if the changes to the document
should be saved before possibly closing the document via OnCloseDocument().
int CDiagramEngDoc::OnFileClose()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int btnSel; // button selection
int cancel = 0; // flag denoting cancellation of file-save action:
(0) no cancel, (1) cancel
{
// no action to save
}
else if(btnSel == IDCANCEL)
{
cancel = 1;
return cancel;
}
}
return cancel;
// DiagramEng (end)
}
The developer will notice that if the document is modified, then a message box appears to allow the
user to save the contents depending upon which button was selected: if “Yes”, the OnFileSave()
function is called and the document closed; if “No”, the document is simply closed; and if “Cancel”,
then no action to save or close the document is taken, and the function returns “cancel” set to 1. This
flag is required in the CDiagramEngApp::OnAppExit() function described as follows.
25.3.1.6 CDiagramEngApp::OnAppExit()
Exiting the application is currently performed by default via the CWinApp::OnAppExit()
function located in the “Appui.cpp” source file. If the developer places a breakpoint in the
CSystemModel destructor function and runs the program using the debugger and attempts
to exit the application, the call stack may be observed and the CWinApp::OnAppExit()
function found in the list of function calls. This default exiting mechanism will be over-
ridden here by adding an event-handler function to the ID_APP_EXIT object ID for the
CDiagramEngApp class such that active documents can be closed using the recently added
OnFileClose() function and then the existing CWinApp::OnAppExit() function called
to terminate the application.
void CDiagramEngApp::OnAppExit()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int cancel = 0; // flag indicating whether exit should
be cancelled: (0) do not cancel, (1) cancel
CDiagramEngDoc *pDoc = NULL; // pDoc used to call CDiagramEngDoc fns.
{
return;
}
1. Hence, add a public member function to the CDiagramEngDoc class with the prototype
int CDiagramEngDoc::OnFileCloseAccessor(void).
2. Edit the function as shown to simply call the protected OnFileClose() member func-
tion and return the value of the “cancel” variable to the calling environment, i.e., to
CDiagramEngApp::OnAppExit().
760 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
int CDiagramEngDoc::OnFileCloseAccessor()
{
int cancel = 0;
// Public interfacing fn for the protected OnFileClose() fn.
return cancel;
}
Now upon exiting the application (without cancellation), the call sequence is as fol-
lows: OnAppExit() → SaveAllDocuments() → OnFileCloseAccessor() →
OnFileClose() → OnFileSave(), which may be followed by OnFileSaveAs()
if a file name has not yet been provided, → OnCloseDocument(), and finally →
CWinApp::OnAppExit().
25.3.1.7 CDiagramEngApp::SaveAllDocuments()
The SaveAllDocuments() function in the CDiagramEngApp class iterates through all
documents of the application, prompting the user to save if the document concerned had been
modified.
1. Add a public member function to the CDiagramEngApp class with the following proto-
type: int CDiagramEngApp::SaveAllDocuments(void).
2. Edit the function as shown to iterate through all the documents of the application and
prompt the user to save if necessary.
int CDiagramEngApp::SaveAllDocuments()
{
int btnSel = -1;
int cancel = 0;
CString sMsg;
CString sPathName;
if(pDoc != NULL)
{
// Check whether document has been modified: non-zero
=> modified, zero => not modified
if(pDoc->IsModified() )
{
// Make the view active and flash it
POSITION pos_view = pDoc->GetFirstViewPosition();
if(pos_view != NULL)
{
CDiagramEngView* pFirstView =
(CDiagramEngView*)pDoc-
>GetNextView(pos_view); // first view
CFrameWnd *pFrameWnd = pFirstView-
>GetParentFrame(); // get parent
frame
pFrameWnd->BringWindowToTop(); // bring
window to top
pFrameWnd->FlashWindow(TRUE); // flash
window once
}
return cancel;
}
The function works as follows: (1) the first document template position in the application is obtained
via a call to GetFirstDocTemplatePosition(); (2) the position is used to get the first docu-
ment template by a call to GetNextDocTemplate(); (3) the string describing the application
is obtained using GetDocString(); (4) if the document is a DiagramEng (“DiaEng”) docu-
ment, the first document position is obtained via a call to GetFirstDocPosition() upon the
pointer-to-CDocTemplate; (5) then a pointer to the document, i.e., “pDoc”, of type pointer-to-
CDiagramEngDoc, is obtained by a call to GetNextDoc(); (6) if the document is valid and has
been modified, then the first view position is obtained by calling GetFirstViewPosition()
and is used to get a pointer to the first view via the call to GetNextView(); (7) then using this
pointer-to-CView, a pointer to the frame window is obtained by calling GetParentFrame(), and
this pointer is used to bring the window to the top (BringWindowToTop()) and flash the window
(FlashWindow()); (8) thereafter, a message box is displayed, prompting the user to save the docu-
ment’s contents (Figure 25.3); and finally, (9) if saving is desired, the OnFileSaveAccessor()
FIGURE 25.3 Message box displaying the prompt to save the stated file (“m_strFilePath”).
Serialization 763
function is called to chain to the OnFileSave() function to save the document. For more informa-
tion on the functions called earlier, the reader should consult the Index field under the Help menu of
MSDN Library Visual Studio 6.0 [2].
The developer will have noticed the call to OnFileSaveAccessor() rather than a direct call
to OnFileSave(): this is necessary since the OnFileSave() function has protected access and
cannot be called upon the document pointer “pDoc”.
1. Add a public member function to the CDiagramEngDoc class with the following proto-
type: void CDiagramEngDoc::OnFileSaveAccessor(void).
2. Edit the function as shown to simply chain up to the OnFileSave() protected member
function.
void CDiagramEngDoc::OnFileSaveAccessor()
{
OnFileSave();
}
Another call made in the SaveAllDocuments() function was that to obtain the file path using
GetFilePath().
1. Add a public member function to the CDiagramEngDoc class with the prototype const
CString &GetFilePath(void).
2. Edit the function as shown to simply return the member variable “m_strFilePath”.
25.3.1.8 CDiagramEngApp::OnFileRecentFileOpen
The Recent File entry under the File menu is used to open the most recently used (MRU) files by the
application, as shown by the highlighted entry in Figure 25.4, to open, e.g., the ordinary differential
equation (ODE) model.
The file names that appear in this list are added by the AddToRecentFileList() function
of the CWinApp class (as shown at the end of the OnFileOpen() function mentioned earlier).
At present, when the user selects a recently used file, a document is opened with the correct
FIGURE 25.4 Four MRU files displayed under the File menu with the ODE model highlighted.
764 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
file name, but no model diagram content is present. Hence, the default command event-handler,
OnOpenRecentFile(), of the CWinApp class, which calls the OpenDocumentFile() func-
tion, is to be overridden here. The following instructions have been derived from a document found
at Microsoft Support [3], titled “How to add command handlers for MRU menu items in MFC
applications,” but they are altered here to suit the DiagramEng implementation.
1. Add an ON_COMMAND_RANGE macro in the message map section at the top of the
“DiagramEng.cpp” source file that associates the to-be-provided event-handler func-
tion OnFileRecentFileOpen(), for the range of IDs, ID_FILE_MRU_FILE1 to
ID_FILE_MRU_FILE16, as shown in bold in the following code excerpt.
#include “stdafx.h”
#include “DiagramEng.h”
#include “MainFrm.h”
#include “ChildFrm.h”
#include “DiagramEngDoc.h”
#include “DiagramEngView.h”
#include “OutputBlockView.h” // rqd. since COutputBlockView is used in
InitInstance()
#include <afxadv.h> // rqd. for CRecentFileList m_pRecentFileList, in
OnFileRecentFileOpen()
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
BEGIN_MESSAGE_MAP(CDiagramEngApp, CWinApp)
//{{AFX_MSG_MAP(CDiagramEngApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
ON_COMMAND(ID_APP_EXIT, OnAppExit)
ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
ON_COMMAND(ID_WND_CLOSE_ALL_DOCS, OnWndCloseAllDocs)
//}}AFX_MSG_MAP
// Standard file based document commands
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
// Standard print setup command
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
ON_COMMAND_RANGE(ID_FILE_MRU_FILE1, ID_FILE_MRU_FILE16,
OnFileRecentFileOpen)
END_MESSAGE_MAP()
void CDiagramEngApp::OnFileRecentFileOpen(UINT i)
{
int btnSel; // button selection
int nIndex = i - ID_FILE_MRU_FILE1; // index of recent file
CString strFileName; // file name and path
CString sMsg; // msg
CDiagramEngDoc *pDoc = GetDocumentGlobalFn();
// ASSERTIONS
ASSERT_VALID(this);
ASSERT(m_pRecentFileList != NULL);
ASSERT(i >= ID_FILE_MRU_FILE1);
ASSERT(i < ID_FILE_MRU_FILE1 + (UINT)m_pRecentFileList->GetSize() );
ASSERT( (*m_pRecentFileList)[nIndex].GetLength() != 0);
// CHECK pDoc
if(pDoc == NULL)
{
return;
}
// CHECK FOR THE EXISTENCE OF A MODEL ON THE DOC: GET THE SYSTEM
MODEL BY REFERENCE!
CSystemModel &system_model = pDoc->GetSystemModel();
if( (system_model.GetBlockList().size() != 0) || (system_model.
GetConnectionList().size() != 0) )
{
sMsg.Format(“ A system model already exists. \n Do you want to
erase it? \n”);
btnSel = AfxMessageBox(sMsg, MB_YESNOCANCEL, 0);
if(btnSel == IDYES)
{
// Delete System Model Lists
system_model.DeleteBlockList();
system_model.DeleteConnectionList();
system_model.DeleteSourceOutputLoopLists();
{
return;
}
}
else if(btnSel == IDCANCEL)
{
return;
}
}
// GET THE FILE NAME/PATH
strFileName = (LPCTSTR)(*m_pRecentFileList)[nIndex];
pDoc->SetFilePath(strFileName);
pDoc->SetTitle(strFileName);
// IF UNABLE TO READ DATA FROM FILE, THEN REMOVE THE RECENT FILE LIST
ENTRY: (0) NO ERROR, (1) ERROR
if(pDoc->ReadDiagramEngDataFromFile(strFileName) == 1)
{
m_pRecentFileList->Remove(nIndex);
}
// MSGs
//strMsg.Format(“ CDiagramEngApp::OnFileRecentFileOpen()\n Open file
(%d) ‘%s’.\n”, ( (nIndex) + 1), strFileName);
//AfxMessageBox(strMsg);
}
1. Add a public member function to the CDiagramEngDoc class with the following proto-
type: void CDiagramEngDoc::SetFilePath(CString path).
2. Edit the function as shown to set the member variable “m_strFilePath” with the incoming
argument.
void CDiagramEngDoc::SetFilePath(CString path)
{
// Set file path including the file name
m_strFilePath = path;
}
Serialization 767
FIGURE 25.5 Two different model diagrams drawn on two separate documents: the upper diagram repre-
sents the state equations and the lower diagram a simple first-order linear ODE.
25.3.1.9 CDiagramEngDoc::OnWndCloseAllDocs()
The Close All Documents entry (ID_WND_CLOSE_ALL_DOCS) under the Window menu
for the Child frame currently calls the event-handler function OnWndCloseAllDocs() of the
CDiagramEngDoc class. However, now that the CDiagramEngApp::OnAppExit() method
works by iterating through the existing documents of the application, the same structure can be
used for the OnWndCloseAllDocs() function.
void CDiagramEngApp::OnWndCloseAllDocs()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int cancel = 0; // flag indicating whether exit should
be cancelled: (0) do not cancel, (1) cancel
CDiagramEngDoc *pDoc = NULL; // pDoc used to call
CDiagramEngDoc fns.
if(cancel == 1)
{
return;
}
// DiagramEng (end)
}
TABLE 25.20
File Menu Entry ID, Class, and Command Event-Handler Function
Object ID Class Command Event-Handler
File/Open ID_FILE_OPEN CDiagramEngApp OnFileOpen()
File/Recent File ID_FILE_MRU_FILE1–16 CDiagramEngApp OnFileRecentFileOpen()
25.3.2.1 CDiagramEngApp::OnFileOpen()
The Open entry under the File menu of the Main frame is to first open a new document and then
chain up to the OnFileOpen() function of the CDiagramEngDoc class discussed earlier.
void CDiagramEngApp::OnFileOpen()
{
// TODO: Add your command handler code here
// DiagramEng (start)
// DiagramEng (end)
}
The developer will notice the call to the accessor function OnFileOpenAccessor(): this is
required since OnFileOpen() of the CDiagramEngDoc class is a protected method, and hence an
interfacing function is required.
1. Add a public member method to the CDiagramEngDoc class with the proto-
type void CDiagramEngDoc::OnFileOpenAccessor(void).
2. Edit the function as shown to simply call the protected OnFileOpen() function of the
same class.
void CDiagramEngDoc::OnFileOpenAccessor()
{
OnFileOpen();
}
Now, upon building and then running the DiagramEng application and selecting Open from the
Main frame–based menu in the absence of an existing document, a new document is first created
(OnFileNew()), and the familiar CFileDialog File Open dialog is presented (OnFileOpen()) as
shown (Figure 25.6).
770 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 25.6 File Open dialog window generated via the Open entry under the File menu of the Main
frame window after the creation of a new document (shown in the background as “DiaEng2”).
1. Add a public member function to the CDiagramEngDoc class with the prototype
void CDiagramEngDoc::WriteDiagramEngDataToFile(CString file_
path, int display).
2. Edit the function as shown to open the file in output mode, to check its sta-
tus, and to pass the ofstream object “fout” by reference to the soon-to-be-added
WriteSystemModelDataToFile() function.
//sMsg.Format(“\n CDiagramEngDoc::WriteDiagramEngDataToFile()\n”);
//AfxMessageBox(sMsg, nType, nIDhelp);
if(display)
{
btnSel = l_wnd.MessageBox(sMsg, sCaption, nType);
}
fin.close();
fin.clear();
return;
}
}
fin.close();
fin.clear();
sMsg += sMsgTemp;
sMsgTemp.Format(“Aborting file i/o.\n”);
sMsg += sMsgTemp;
nType = MB_OK | MB_ICONSTOP;
AfxMessageBox(sMsg, nType, nIDhelp);
The developer will notice that if the output file already exists and the (incoming) “display”
variable is nonzero, a MessageBox() call is made to inform the user and deter-
mine whether the existing file should be overwritten, as shown in Figure 25.7: if so, the
WriteSystemModelDataToFile() function is called, and if not, the function simply
returns. The “display” variable is “0” or “1” if the calling function is OnFileSave() or
OnFileSaveAs(), respectively.
In addition, prior to the WriteSystemModelDataToFile() call being made, the preci-
sion of the output stream is set using setf() and precision(): the former has the “floatfield”
format set to “scientific” rather than “fixed”, and the precision is set using the constant integer
“FILE_IO_PRECISION”. Hence, add a constant integer to the “Block.h” header file, as shown
in bold in the following code excerpt, defining the precision to be 20; the ellipsis, “…”, denotes
omitted code for brevity.
FIGURE 25.7 MessageBox dialog window used to check the overwriting of an existing file.
Serialization 773
// Title: Block.h
#ifndef BLOCK_H
#define BLOCK_H // inclusion guard
Kelley and Pohl indicate that a double, d, has an approximate precision of 15 significant fig-
ures and an approximate range of 10 −308 – 10+308, and then clarify by example that the statement
d = 123.45123451234512345 will result in d being assigned a value stored approximately as
d = 0.123451234512345 × 10+3 [4]. Hence, the precision of 20 significant digits set earlier is actually
more than required: any extra digits present will be zeros.
The reason why a double involves only approximately 15 significant figures is because the double
data type is represented by a 64-bit, base 2 number, according to the IEEE Standard for Floating-
Point Arithmetic (IEEE 754-2008) [5]: 1 bit is used to denote the sign, 11 bits to denote the expo-
nent, and the remaining 52 bits to denote the fraction or significand (the total precision is in fact
53 bits). Salas and Hille [6] indicate that the log of a value x to the base p is
ln x
log p x = (25.1)
ln p
where
ln x = loge x (25.2)
Hence, log10 253 ≅ 15.95, i.e., 53 binary digits (bits) of precision is equivalent to approximately
15 decimal digits.
1. Hence, add a public member function to the CSystemModel class with the prototype void
CSystemModel::WriteSystemModelDataToFile(ofstream &fout), and edit
it as shown.
2. This requires including the “fstream.h” (#include <fstream>) header file at the top of the
“SystemModel.cpp” file just beneath the inclusion of the “stdafx.h” header file.
774 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
//AfxMessageBox(“\n CSystemModel::WriteSystemModelDataToFile()\n”,
MB_OK, 0);
strcpy(strVar, m_strIntegrationMethod);
fout << strVar << endl;
strcpy(strVar, m_strTimeStepType);
fout << strVar << endl;
strcpy(strVar, m_strWarning);
fout << strVar << endl;
3. The developer will have recognized that the length of the character arrays is defined by
“L_FILE_IO_STR”; hence, add this as a constant integer to the “Block.h” header file as
shown in bold in the following code excerpt.
Serialization 775
// Title: Block.h
#ifndef BLOCK_H
#define BLOCK_H // inclusion guard
1. Hence, add a public virtual member function to the CBlock class with the prototype
virtual void CBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to write CBlock-specific member data to a file and to call the
contained CBlockShape object’s WriteBlockShapeDataToFile() function and the
CPort object’s WritePortDataToFile() method.
3. Finally, include the “fstream.h” header file (#include <fstream>) at the top of the “Block.
cpp” source file, just beneath the inclusion of the “stdafx.h” header file.
{
(*it_port)->WritePortDataToFile(fout);
}
// Output ports
for(it_port = m_vecOutputPorts.begin(); it_port != m_vecOutputPorts.end();
it_port++)
{
(*it_port)->WritePortDataToFile(fout);
}
}
1. Add a public member function to the CBlockShape class with the prototype
void CBlockShape::WriteBlockShapeDataToFile(ofstream &fout).
2. Edit the function as shown to simply write the CBlockShape member data to a file.
1. Add a public member function to the CPort class with the prototype void
CPort::WritePortDataToFile(ofstream &fout).
2. Edit the function as shown to write the relevant port member data to a file.
25.4.6.1 CConstantBlock::WriteBlockDataToFile()
The CConstantBlock class’ data members to be written to a file are those shown in Table 25.7,
i.e., the number of rows, “m_iNrows”, and columns, “m_iNcols”, of data and the data matrix,
“m_dConstMatrix”, itself.
1. Add a public virtual member function to the CConstantBlock class with the prototype
virtual void CConstantBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to write the Constant-block-specific member data to a file after
first calling the base CBlock class’ WriteBlockDataToFile() method.
void CConstantBlock::WriteBlockDataToFile(ofstream &fout)
{
int i;
int j;
//AfxMessageBox(“\n CConstantBlock::WriteBlockDataToFile()\n”, MB_OK, 0);
// Write CBlock member data to file
CBlock::WriteBlockDataToFile(fout);
// Write CConstantBlock member data to file.
fout << m_iNrows << endl;
fout << m_iNcols << endl;
for(i=0; i<m_iNrows; i++)
{
for(j=0; j<m_iNcols; j++)
{
fout << m_dConstMatrix[i][j] << “”;
}
fout << endl;
}
25.4.6.2 CDerivativeBlock::WriteBlockDataTofile()
The CDerivativeBlock class only needs to write the derivative method, “m_iDerivativeMethod”, to a file.
1. Add a public virtual member function to the CDerivativeBlock class with the prototype
virtual void CDerivativeBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and then to write the CDerivativeBlock member data to a file.
void CDerivativeBlock::WriteBlockDataToFile(ofstream &fout)
{
//AfxMessageBox(“\n CDerivativeBlock::WriteBlockDataToFile()\n”,
MB_OK, 0);
// Write CBlock member data to file
CBlock::WriteBlockDataToFile(fout);
// Write CDerivativeBlock member data to file.
fout << m_iDerivativeMethod << endl;
}
778 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
25.4.6.3 CDivideBlock::WriteBlockDataToFile()
The Divide block has member variables that record the number of multiplication and division input
ports, denoted by “m_iNDivideInputs” and “m_iNMultiplyInputs”, respectively. The type of mul-
tiplication or division recorded in “m_iMultType” may be elemental- or matrix-based. Hence, all
three data members need to be recorded. The port name reflects the port sign, equivalently the
mathematical operator, which is already handled by the CPort::WritePortDataToFile()
function.
1. Add a public virtual member function to the CDivideBlock class with the prototype
virtual void CDivideBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to first call the base CBlock class’ WriteBlockDataToFile()
method and then to write the CDivideBlock member data to a file.
25.4.6.4 CGainBlock::WriteBlockDataToFile()
The CGainBlock class’ data members to be written to a file are shown in Table 25.10, i.e., the
number of rows, “m_iNrows”, and columns, “m_iNcols”, of data and the data matrix,
“m_dConstMatrix”, itself as well as the type of gain, “m_iGainType”, i.e., the order of the gain
operation.
1. Add a public virtual member function to the CGainBlock class with the prototype
virtual void CGainBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to call the base CBlock class WriteBlockDataToFile()
function before writing the CGainBlock member data to a file.
//AfxMessageBox(“\n CGainBlock::WriteBlockDataToFile()\n”,
MB_OK, 0);
// Write CBlock member data to file
CBlock::WriteBlockDataToFile(fout);
25.4.6.5 CIntegratorBlock::WriteBlockDataToFile()
The CIntegratorBlock class’ member data are shown in Table 25.11, where only the initial condition
vector, “m_dICVector”, and its length, “m_iLength”, need to be written to a file.
1. Add a public virtual member function to the CIntegratorBlock class with the prototype
virtual void CIntegratorBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to call the base CBlock class WriteBlockDataToFile()
function before writing the CIntegratorBlock data to a file.
25.4.6.6 CLinearFnBlock::WriteBlockDataToFile()
The CLinearFnBlock’s member data are shown in Table 25.12 and involve four double-type member
variables defining the linear function source signal.
1. Add a public virtual member function to the CLinearFnBlock class with the
prototype virtual void CLinearFnBlock::WriteBlockDataToFile
(ofstream &fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and to write the CLinearFnBlock data to a file.
25.4.6.7 COutputBlock::WriteBlockDataToFile()
The COutputBlock class will have a mechanism added that will allow the user to save numeri-
cal data separately to a file. For the purpose of restoring a saved model, only the type of notation,
“m_iNotation”, and time point display, “m_iTimePtDisplay”, options, as shown in Table 25.13, need
to be saved.
1. Add a public virtual member function to the COutputBlock class with the prototype
virtual void COutputBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and to write the COutputBlock output graph options to a file.
25.4.6.8 CSignalGeneratorBlock::WriteBlockDataToFile()
The CSignalGeneratorBlock class’ member data to be written to a file are shown in Table 25.14: the
CString data first need to be converted to character arrays prior to using “fout”.
1. Add a public virtual member function to the CSignalGeneratorBlock class with the
prototype virtual void CSignalGeneratorBlock::WriteBlockDataToFile
(ofstream &fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and to write all CSignalGeneratorBlock member data to a file.
strcpy(strData, m_strFnType);
fout << strData << endl;
strcpy(strData, m_strUnits);
fout << strData << endl;
}
25.4.6.9 CSubsystemBlock::WriteBlockDataToFile()
The CSubsystemBlock class’ member data at present consist of only the input and output port
names, “m_strInputPortName” and “m_strOutputPortName”, respectively, as shown in Table 25.15.
1. Add a public virtual member function to the CSubsystemBlock class with the prototype
virtual void CSubsystemBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and to write the two CSubsystemBlock data members to a file by first using
strcpy().
25.4.6.10 CSubsystemInBlock::WriteBlockDataToFile()
The CSubsystemInBlock class’ member data consist of only the input port name,
“m_strInputPortName”, which is to be written to a file.
1. Add a public virtual member function to the CSubsystemInBlock class with the prototype
virtual void CSubsystemInBlock::WriteBlockDataToFile(ofstream
&fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and to write the CSubsystemInBlock member data to a file.
void CSubsystemInBlock::WriteBlockDataToFile(ofstream &fout)
{
char strPortName[L_FILE_IO_STR]; // char array reflecting the input
port name
//AfxMessageBox(“\n CSubsystemInBlock::WriteBlockDataToFile()\n”,
MB_OK, 0);
782 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
25.4.6.11 CSubsystemOutBlock::WriteBlockDataToFile()
The CSubsystemOutBlock class’ member data consist of only the output port name,
“m_strOutputPortName”, which is to be written to a file.
1. Add a public virtual member function to the CSubsystemOutBlock class with the prototype
virtual void CSubsystemOutBlock::WriteBlockDataToFile(ofstream
&fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and to write the CSubsystemOutBlock member data to a file.
25.4.6.12 CSumBlock::WriteBlockDataToFile()
The CSumBlock’s member data are shown in Table 25.18 and consist of the number of addition and
subtraction inputs, “m_iNAddInputs” and “m_iNSubtractInputs”, respectively, that are to be writ-
ten to a file.
1. Add a public virtual member function to the CSumBlock class with the prototype virtual
void CSumBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and to write the CSumBlock integer member data to a file.
25.4.6.13 CTransferFnBlock::WriteBlockDataToFile()
The CTransferFnBlock’s member data are shown in Table 25.19: only the numerator and denomina-
tor vectors, “m_dNumerVector” and “m_iDenomVector”, and their lengths, “m_iLengthNumer”
and “m_iLengthDenom”, need to be written to a file.
1. Add a public virtual member function to the CTransferFnBlock class with the prototype vir-
tual void CTransferFnBlock::WriteBlockDataToFile(ofstream &fout).
2. Edit the function as shown to call the base CBlock class’ WriteBlockDataToFile()
method and to write the CTransferFnBlock integer and double data to a file.
//AfxMessageBox(“\n CTransferFnBlock::WriteBlockDataToFile()\n”,
MB_OK, 0);
1. Add a public member function to the CConnection class with the prototype void
CConnection::WriteConnectionDataToFile(ofstream &fout).
2. Edit the function as shown to write CConnection member data to a file, including the head and
tail points, “m_ptHead” and “m_ptTail”, respectively, and to iterate over the list of bend points,
“m_lstBendPoints”, writing out the x and y coordinates of each point.
3. Finally, include the “fstream.h” header file (#include <fstream>) at the top of the “Signal.
cpp” source file just beneath the inclusion of the “stdafx.h” header file.
void CConnection::WriteConnectionDataToFile(ofstream &fout)
{
char strName[L_FILE_IO_STR]; // name of connection
list<CPoint>::iterator it_pt; // bend points list iterator
784 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
//AfxMessageBox(“\n CConnection::WriteConnectionDataToFile()\n”,
MB_OK, 0);
// Write CConnection member data to file.
strcpy(strName, “connection”);
fout << strName << endl;
fout << m_ptHead.x << “” << m_ptHead.y << endl;
fout << m_ptTail.x << “” << m_ptTail.y << endl;
fout << m_lstBendPoints.size() << endl;
for(it_pt = m_lstBendPoints.begin(); it_pt != m_lstBendPoints.end();
it_pt++)
{
fout << (*it_pt).x << “” << (*it_pt).y << endl;
}
}
introduced in Chapter 23, where y, t, and u(t) are the dependent variable, independent variable, and
source signal generation function, respectively. The model diagram, repeated here for convenience,
is shown in Figure 25.8, and the equivalent DiagramEng model diagram is shown in Figure 25.9.
The content of the output data file “model_data.txt” is shown in the left column of Table 25.21:
the right column provides a brief description of each line of the file which is not present in the
. y(t)
f0(t) u(t) y(t)
x0 x1 ∫ dt x2 Out
0 1 2 4
–2y(t)
x3 k3 = –2
3
.
FIGURE 25.8 Block diagram model of the ODE y(t) = −2y(t) + u(t), with block numbers written below the
blocks, and signals xi(t), for i ∈ [0, s − 1] written beneath the block output connections, where s is the number
of blocks with output signals.
FIGURE 25.9 Block diagram model of the ODE (25.4) drawn with DiagramEng.
Serialization 785
TABLE 25.21
The Content of the Output Data File “model_data.txt”
and a Brief Description of Each Line of Output
Content of Output File Description of Content
system_model m_strModelName
0.0001 m_dATOL
0.0001 m_dRTOL
0 m_dTimeStart
0.01 m_dTimeStepSize
5 m_dTimeStop
Euler (1st Order) m_strIntegrationMethod
Fixed-step m_strTimeStepType
OK m_strWarning
linear_fn_block m_strBlockName
71 86 m_ptBlockPosition.x, m_ptBlockPosition.y
01 no. of input ports, no. of output ports
100 100 m_dBlockWidth, m_dBlockHeight
1 m_eBlockShape (e_rectangle)
1 m_eBlockDirection (e_right)
0_ m_dPortPositionAngle, m_strPortName (no sign)
2 m_ePortArrowDirec (e_right_arrow)
2 m_dDerivative
0 m_dTimeInit
10 m_dTimeFinal
0 m_dValueInit
sum_block m_strBlockName
219 86 m_ptBlockPosition.x, m_ptBlockPosition.y
21 no. of input ports, no. of output ports
100 100 m_dBlockWidth, m_dBlockHeight
0 m_eBlockShape (e_ellipse)
1 m_eBlockDirection (e_right)
180 + m_dPortPositionAngle, m_strPortName (addition)
2 m_ePortArrowDirec (e_right_arrow)
270 + m_dPortPositionAngle, m_strPortName (addition)
1 m_ePortArrowDirec (e_up_arrow)
0_ m_dPortPositionAngle, m_strPortName (no sign)
2 m_ePortArrowDirec (e_right_arrow)
2 m_iNAddInputs
0 m_iNSubtractInputs
integrator_block m_strBlockName
403 86 m_ptBlockPosition.x, m_ptBlockPosition.y
11 no. of input ports, no. of output ports
100 100 m_dBlockWidth, m_dBlockHeight
1 m_eBlockShape (e_rectangle)
1 m_eBlockDirection (e_right)
180 _ m_dPortPositionAngle, m_strPortName (no sign)
2 m_ePortArrowDirec (e_right_arrow)
0_ m_dPortPositionAngle, m_strPortName (no sign)
2 m_ePortArrowDirec (e_right_arrow)
1 m_iLength
0 m_dICVector
(continued)
786 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
actual text file. The reader will notice that as the connection entering the Gain block is attached
to the bend point of the connection emanating from the Integrator block, its tail point coordinates
are the same as the bend point coordinates (the description concerned is shown in italics). In addi-
tion, the numerical values have been generated, for ease of viewing here, using “set_prec” assigned
to “0” in the function WriteDiagramEngDataToFile(): this can be reset to “1” for scientific
notation as desired.
//sMsg.Format(“\n CDiagramEngDoc::ReadDiagramEngDataFromFile()\n”);
//AfxMessageBox(sMsg, nType, nIDhelp);
fin.close();
fin.clear();
file_flag = 1; // error
return file_flag;
}
return file_flag;
}
The developer will have noticed that after the ReadSystemModelDataFromFile() func-
tion is called, the SnapConnectionEndPointsAfterInitialConstruction() is called
to snap any model connection end points to either an input/output port or a bend point residing on
another connection object. Thereafter, the UpdateAllViews() function is called to invoke the
OnDraw() method to draw the newly loaded model. The SetModifiedFlag() function is called
with the argument set to FALSE since as the model has just been loaded, then there is no need to
mark the document as having been changed. Finally, the input file stream is closed and its flags reset.
1. Add a public member function to the CSystemModel class with the prototype
void CSystemModel::ReadSystemModelDataFromFile(ifstream &fin).
2. Edit the function as shown, to iterate through the whole data file in a while loop and filter
out string name identifiers to construct the relevant model objects.
void CSystemModel::ReadSystemModelDataFromFile(ifstream &fin)
{
int cnt = 0; // counter to discern correctness of
first line of file
int error_cnt = 0; // counter to discern an inf. loop
int max_cnt = 10000; // max cnt for error detection
char strLine[L_FILE_IO_STR]; // current line as a character array
char strVar[L_FILE_IO_STR]; // character array var to work with
“fin >>”
CString sMsg; // msg string
CString sMsgTemp; // temp msg string
streampos stream_posn; // position in the stream
//AfxMessageBox(“\n CSystemModel::ReadSystemModelDataFromFile()\n”,
MB_OK, 0);
// READ THROUGH CONTENTS OF FILE
while(!fin.eof() )
{
// Get current stream posn
stream_posn = fin.tellg();
// Get current line (NOTE: getline() did not work as expected
here and returned an empty line)
fin >> strLine;
Serialization 789
// Now go back to the start of the line, such that the first data
element can be read in.
fin.seekg(stream_posn);
// Integration method
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If string not empty then assume string on line is
correct
if(strcmp(strVar, “”) != 0)
{
m_strIntegrationMethod = strVar;
break;
}
}
// Time-step type
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
790 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
// If string not empty then assume string on line is
correct
if(strcmp(strVar, “”) != 0)
{
m_strTimeStepType = strVar;
break;
}
}
// Warning msg.
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If string not empty then assume string on line is
correct
if(strcmp(strVar, “”) != 0)
{
m_strWarning = strVar;
break;
}
}
}// end system model
// CONNECTION
if(strcmp(strLine, “connection”) == 0)
{
CConnection *pCon = new CConnection(CPoint(0,0),
CPoint(100,100) ); // pass in dummy
connection head and tail pts.
pCon->ReadConnectionDataFromFile(fin); // read and
assign the connections member data
m_lstConnection.push_back(pCon); // add the
connection to the connection list
}
// CONSTANT BLOCK
if(strcmp(strLine, “constant_block”) == 0)
{
CBlock *p_block = new CConstantBlock(this,
CPoint(100,100), e_rectangle); // pass in
&system_model, a dummy block posn and shape
p_block->ReadBlockDataFromFile(fin); // read and assign
the block member data
m_lstBlock.push_back(p_block); // add the block to
the block list
}
// DERIVATIVE BLOCK
if(strcmp(strLine, “derivative_block”) == 0)
{
CBlock *p_block = new CDerivativeBlock(this,
CPoint(100,100), e_rectangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// DIVIDE BLOCK
if(strcmp(strLine, “divide_block”) == 0)
Serialization 791
{
CBlock *p_block = new CDivideBlock(this, CPoint(100,100),
e_rectangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// GAIN BLOCK
if(strcmp(strLine, “gain_block”) == 0)
{
CBlock *p_block = new CGainBlock(this, CPoint(100,100),
e_triangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// INTEGRATOR BLOCK
if(strcmp(strLine, “integrator_block”) == 0)
{
CBlock *p_block = new CIntegratorBlock(this,
CPoint(100,100), e_rectangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// LINEAR FN BLOCK
if(strcmp(strLine, “linear_fn_block”) == 0)
{
CBlock *p_block = new CLinearFnBlock(this, CPoint(100,100),
e_rectangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// OUTPUT BLOCK
if(strcmp(strLine, “output_block”) == 0)
{
CBlock *p_block = new COutputBlock(this, CPoint(100,100),
e_rectangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// SUBSYSTEM BLOCK
if(strcmp(strLine, “subsystem_block”) == 0)
{
CBlock *p_block = new CSubsystemBlock(this,
CPoint(100,100), e_rectangle);
p_block->ReadBlockDataFromFile(fin);
792 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
m_lstBlock.push_back(p_block);
}
// SUBSYSTEM IN BLOCK
if(strcmp(strLine, “subsystem_in_block”) == 0)
{
CBlock *p_block = new CSubsystemInBlock(this,
CPoint(100,100), e_rectangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// SUBSYSTEM OUT BLOCK
if(strcmp(strLine, “subsystem_out_block”) == 0)
{
CBlock *p_block = new CSubsystemOutBlock(this,
CPoint(100,100), e_rectangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// SUM BLOCK
if(strcmp(strLine, “sum_block”) == 0)
{
CBlock *p_block = new CSumBlock(this, CPoint(100,100),
e_ellipse);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
// TRANSFER FN BLOCK
if(strcmp(strLine, “transfer_fn_block”) == 0)
{
CBlock *p_block = new CTransferFnBlock(this,
CPoint(100,100), e_rectangle);
p_block->ReadBlockDataFromFile(fin);
m_lstBlock.push_back(p_block);
}
}// end strcmp()
The ReadSystemModelDataFromFile() function reads the system model data file in a while
loop until the input file stream reaches the end of the file (“fin.eof()”) or an error condition
occurs (“error_cnt > max_cnt”). Initially the current position of the pointer in the stream is obtained
by a call to tellg(), then the first line of the file is read into the local variable character array,
“strLine”, and then the stream pointer is reset to the beginning of the line, since the line contains
information that needs to be processed later.
The first conditional statement “if(strcmp(strLine, “”) != 0)” checks whether the first line is empty,
and if not, “strLine” is filtered using a string comparison by the nested conditional statements to detect
whether a system model, connection, or a derived-block object is to have its data read. The first item
in the model data file is in fact a system model object (see WriteSystemModelDataToFile()
for the writing sequence); hence, data are read into the member variables through the input file
stream “fin”. The getline() function calls, within the while statements, are used to get a com-
plete line of data that is stored in the character array “strVar”. This is necessary since if the array
is empty, then the next line of data is to be retrieved; if the array is found by comparison to not
be empty, then it is assumed to hold the value of a CSystemModel member variable to which it is
subsequently assigned. If the first line of data does not begin with “system_model”, then the file is
assumed to be of the incorrect format and the function returns.
After the CSystemModel member variables have been assigned, control flow returns to the top
of the while loop for the next line of data to be obtained, which is the identifier of the type of
object that needs to be reconstructed: either a CConnection or a derived CBlock object. The con-
structor function is called for each object, and then the ReadConnectionDataFromFile() or
ReadBlockDataFromFile() functions are called upon the pointer-to-CConnection or pointer-
to-CBlock objects, respectively. Finally, the constructed object with the correct member data is
added to the appropriate list.
The following sections detail all the file-reading functions to be added to the project,
i.e., the public virtual function CBlock::ReadBlockDataFromFile(), CBlockShape::
ReadBlockShapeDataFromFile(), CConnection::ReadConnectionDataFrom
File(), CPort::ReadPortDataFromFile(), and all the overriding derived-block
ReadBlockDataFromFile() functions that facilitate a polymorphic file-reading behavior:
the correct derived-block function is called based on the derived run-time type of the pointer-to-
CBlock, “p_block”.
1. Add a public member function to the CConnection class with the prototype
void CConnection::ReadConnectionDataFromFile(ifstream &fin).
2. Edit the function as shown to read in the CConnection data and assign it to the class mem-
ber variables.
//AfxMessageBox(“\n CConnection::ReadConnectionDataFromFile()\n”,
MB_OK, 0);
1. Add a public virtual member function to the CBlock class with the following prototype:
virtual void CBlock::ReadBlockDataFromFile(ifstream &fin).
2. Edit the function as shown to read in the block data in the same order as it was written out
by the WriteBlockDataToFile() function given in the previous section, and to call
the CBlockShape and CPort data-reading functions.
if(n_extra_ports > 0)
{
for(i=0; i<n_extra_ports; i++)
{
p_port = new CPort(*this);
m_vecInputPorts.push_back(p_port);
}
}
// Input ports
for(it_port = m_vecInputPorts.begin(); it_port != m_vecInputPorts.end();
it_port++)
{
(*it_port)->ReadPortDataFromFile(fin);
}
// Output ports
for(it_port = m_vecOutputPorts.begin(); it_port != m_vecOutputPorts.end();
it_port++)
{
(*it_port)->ReadPortDataFromFile(fin);
}
}
The derived-block constructors set a default number of input and output ports for each block type,
e.g., the Derivative block has one input and one output port. However, the Sum and Divide blocks
have a user-defined number of input ports that could exceed the default setting. Hence, if the number
of input ports being read in from a file is larger than the default value, extra ports are constructed and
added to the vector of input ports, before their data are read using ReadPortDataFromFile().
1. Add a public member function to the CBlockShape class with the following prototype:
void CBlockShape::ReadBlockShapeDataFromFile(ifstream &fin).
2. Edit the function as shown to read in the block shape data in the same order it was written
out by the WriteBlockShapeDataToFile() function.
void CBlockShape::ReadBlockShapeDataFromFile(ifstream &fin)
{
int l_e_block_shape; // local EBlockShape
int l_e_block_direction; // local EBlockDirection
//AfxMessageBox(“\n CBlockShape::ReadBlockShapeDataFromFile()\n”,
MB_OK, 0);
// Read CBlockShape member data from file
fin >> m_dBlockWidth >> m_dBlockHeight;
fin >> l_e_block_shape;
m_eBlockShape = static_cast<EBlockShape>(l_e_block_shape);
fin >> l_e_block_direction;
m_eBlockDirection = static_cast<EBlockDirection>(l_e_block_
direction);
}
796 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
1. Add a public member function to the CPort class with the following proto-
type: void CPort::ReadPortDataFromFile(ifstream &fin).
2. Edit the function as shown to read in the CPort data in the same order it was written out by
the WritePortDataToFile() function.
The reader will notice the call to CalculatePortPosition() to calculate the port position
from the port position angle member variable, “m_dPortPositionAngle”: this calculates the port’s
position and stores it in the member variable, “m_ptPortPosition”.
25.5.6.1 CConstantBlock::ReadBlockDataFromFile()
The CConstantBlock’s ReadBlockDataFromFile() function is called upon the
pointer-to-CBlock after a new CConstantBlock is created in the CSystemModel’s
ReadSystemModelDataFromFile() function as shown earlier.
1. Add a public virtual member function to the CConstantBlock class with the following prototype:
virtual void CConstantBlock::ReadBlockDataFromFile(ifstream &fin).
2. Edit the function as shown to read in the base CBlock class’ member data before reading
in the CConstantBlock data members.
Serialization 797
3. Add a global function prototype to the “DiagramEngDoc.h” header file at the “Global
Functions” section, of the form int ConvertDoubleMatrixToString(CString
&str, double **matrix, int nrows, int ncols).
4. Edit the function in the “DiagramEngDoc.cpp” source file as shown to convert the double
matrix data to the equivalent string form.
// Matrix content
for(i=0; i<nrows; i++)
{
for(j=0; j<ncols; j++)
{
strTemp.Format(“%.10lf”, matrix[i][j]); // convert to
10 decimal places
str += strTemp;
}
return 0;
}
25.5.6.2 CDerivativeBlock::ReadBlockDataFromFile()
A CDerivativeBlock is constructed in a similar manner to the CConstantBlock in the
ReadSystemModelDataFromFile() function, and its data are read from a file using the
ReadBlockDataFromFile() function.
1. Add a public virtual member function to the CDerivativeBlock class with the follow-
ing prototype: virtual void CDerivativeBlock::ReadBlockDataFromFile
(ifstream &fin).
2. Edit the function as shown to first call the base CBlock class’ ReadBlockDataFromFile()
function and then read in all the CDerivativeBlock data members.
Serialization 799
25.5.6.3 CDivideBlock::ReadBlockDataFromFile()
The CDivideBlock object is constructed and has ReadBlockDataFromFile() called upon it in
a similar manner to the previous blocks.
1. Add a public virtual member function to the CDivideBlock class with the follow-
ing prototype: virtual void CDivideBlock::ReadBlockDataFromFile
(ifstream &fin).
2. Edit the function as shown to call the CBlock class’ file-reading function and then to read
in all the CDivideBlock data members.
The developer can experiment with setting more than the two default input ports, to test that the
CBlock::ReadBlockDataFromFile() function does in fact add as many ports as specified in
the data file.
25.5.6.4 CGainBlock::ReadBlockDataFromFile()
The ReadBlockDataFromFile() for the Gain block is similar in its structure to that of the
CConstantBlock, since user input is allowed to set the gain constant “m_strGainValue”, equiva-
lently “m_dGainMatrix”.
1. Add a public virtual member function to the CGainBlock class with the follow-
ing prototype: virtual void CGainBlock::ReadBlockDataFromFile
(ifstream &fin).
2. Edit the function as shown to delete any existing memory as a result of block con-
struction, to call the base class ReadBlockDataFromFile() function, then read in
all the CGainBlock data members, and finally initialize “m_strGainValue” using the
double data and the call to ConvertDoubleMatrixToString().
800 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
25.5.6.5 CIntegratorBlock::ReadBlockDataFromFile()
The data to be read in for the CIntegratorBlock class consist of the length of the initial condition
vector, “m_iLength”, and the double vector itself, “m_dICVector”. The CIntegratorBlock con-
structor converts a default string into a double vector, allocating memory for it in the process.
Serialization 801
Hence, memory needs to be first deallocated and then the size of the vector read in and new
memory allocated, in a similar manner to that required for the Constant and Gain blocks.
1. Add a public virtual member function to the CIntegratorBlock class with the follow-
ing prototype: virtual void CIntegratorBlock::ReadBlockDataFromFile
(ifstream &fin).
2. Edit the function as shown to delete any existing memory, read in the vector length, allo-
cate new memory, read in the vector values, and finally initialize the “m_strICVector”
using the double data and the call to ConvertDoubleVectorToString().
//AfxMessageBox(“\n CIntegratorBlock::ReadBlockDataFromFile()\n”,
MB_OK, 0);
// MEMORY DELETE
if(m_dICVector != NULL)
{
delete [] m_dICVector;
}
// MEMORY NEW
m_dICVector = new double[m_iLength];
The developer will have noticed the call to a new function, ConvertDoubleVector
ToString(): this is similar in operation to the matrix conversion function introduced earlier.
3. Add a global function prototype to the “DiagramEngDoc.h” header file at the “Global
Functions” section, of the form int ConvertDoubleVectorToString(CString
&str, double *vec, int length).
4. Edit the function in the “DiagramEngDoc.cpp” source file as shown to convert the double
vector data to the equivalent string form.
802 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Vector content
for(i=0; i<length; i++)
{
strTemp.Format(“%.10lf”, vec[i]); // convert to 10 decimal places
str += strTemp;
}
return 0;
}
25.5.6.6 CLinearFnBlock::ReadBlockDataFromFile()
The CLinearFnBlock simply writes and reads four double-type variables to and from a file, as
shown in Table 25.12: there are no vector or matrix member objects and the constructor does not
explicitly allocate any memory.
1. Add a public virtual member function to the CLinearFnBlock class with the prototype
virtual void CLinearFnBlock::ReadBlockDataFromFile(ifstream &fin).
2. Edit the function as shown to call the base CBlock class file-reading function and then to
read in its own class member data.
25.5.6.7 COutputBlock::ReadBlockDataFromFile()
The COutputBlock writes and reads two integer variables to and from a file. The constructor
function initializes the member matrix variable “m_dOutputMatrix” to NULL but does not allocate
any memory.
1. Add a public virtual member function to the COutputBlock class with the prototype
virtual void COutputBlock::ReadBlockDataFromFile(ifstream &fin).
2. Edit the function as shown to call the base CBlock class’ ReadBlockDataFromFile()
function and then to read in the COutputBlock class integer data.
Serialization 803
25.5.6.8 CSignalGeneratorBlock::ReadBlockDataFromFile()
The CSignalGeneratorBlock class has double and CString member data types. The double data can
simply be read in directly from a file, but the CString data need to be read into character arrays
using the getline() function to capture all words on a particular line separated with spaces.
1. Add a public virtual member function to the CSignalGeneratorBlock class with the pro-
totype virtual void CSignalGeneratorBlock::ReadBlockDataFromFile
(ifstream &fin).
2. Edit the function as shown to call the base CBlock class’ ReadBlockDataFromFile()
function and then to read in the CSignalGeneratorBlock class data using getline()
within a while loop.
//AfxMessageBox(“\n CSignalGeneratorBlock::ReadBlockDataFromFile()\n”,
MB_OK, 0);
// Note: geline() is used here since the string may consist of blank
spaces.
// Fn type
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If strVar not an empty string then assume the string is the
complete identifier on the line
if(strcmp(strVar, “”) != 0)
{
m_strFnType = strVar;
break;
}
}
// Units
while(fin.getline(strVar, L_FILE_IO_STR, ‘n’) )
804 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
// If strVar not an empty string then assume the string is the
complete identifier on the line
if(strcmp(strVar, “”) != 0)
{
m_strUnits = strVar;
break;
}
}
}
25.5.6.9 CSubsystemBlock::ReadBlockDataFromFile()
The CSubsystemBlock at present simply has two CString member variables that are to be read in
from a file: “m_strInputPortName” and “m_strOutputPortName”.
1. Add a public virtual member function to the CSubsystemBlock class with the prototype
virtual void CSubsystemBlock::ReadBlockDataFromFile(ifstream
&fin).
2. Edit the function as shown to call the base CBlock class’ reading operation and then to read
in the CSubsystemBlock class CString data using getline().
void CSubsystemBlock::ReadBlockDataFromFile(ifstream &fin)
{
char strVar[L_FILE_IO_STR]; // character array var to work with “fin >>”
//AfxMessageBox(“\n CSubsystemBlock::ReadBlockDataFromFile()\n”,
MB_OK, 0);
// Read CBlock member data from file
CBlock::ReadBlockDataFromFile(fin);
// Read CSubsystemBlock member data from file
// Note: geline() is used here since the name the user enters may
consist of blank spaces.
// Input port name
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If strVar not an empty string then assume the string is the
complete name on the line
if(strcmp(strVar, “”) != 0)
{
m_strInputPortName = strVar;
break;
}
}
// Output port name
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If strVar not an empty string then assume the string is the
complete name on the line
if(strcmp(strVar, “”) != 0)
{
m_strOutputPortName = strVar;
break;
}
}
}
Serialization 805
The developer will notice that getline() is used in the reading of the name variables: this is
required since the default names and those that the user chooses may have spaces, e.g., “input port
name” rather than “input_port_name”. Hence, the whole line is read in and stored in the local char-
acter array “strVar”, and if it is nonempty, it is assigned to the CString member variable.
25.5.6.10 CSubsystemInBlock::ReadBlockDataFromFile()
The CSubsystemInBlock’s file I/O is similar to that of the CSubsystemBlock where only one CString
member variable is present: “m_strInputPortName”.
1. Add a public virtual member function to the CSubsystemInBlock class with the prototype
virtual void CSubsystemInBlock::ReadBlockDataFromFile(ifstream
&fin).
2. Edit the function as shown to call the base class file-reading function and then read in the
member variable using getline().
//AfxMessageBox(“\n CSubsystemInBlock::ReadBlockDataFromFile()\n”,
MB_OK, 0);
25.5.6.11 CSubsystemOutBlock::ReadBlockDataFromFile()
The CSubsystemOutBlock’s file I/O is similar to that of the CSubsystemInBlock where only one
CString member variable is present: “m_strOutputPortName”.
1. Add a public virtual member function to the CSubsystemOutBlock class with the pro-
totype virtual void CSubsystemOutBlock::ReadBlockDataFromFile
(ifstream &fin).
2. Edit the function as shown in a similar manner to the previous function.
//AfxMessageBox(“\n CSubsystemOutBlock::ReadBlockDataFromFile()\n”,
MB_OK, 0);
806 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
25.5.6.12 CSumBlock::ReadBlockDataFromFile()
The CSumBlock class has two integer member variables, “m_iNAddInputs” and
“m_iNSubtractInputs”, denoting the number of addition and subtraction input ports on the Sum
block, respectively. There are two default input ports set up in the constructor, but the user may
change the number of input ports in a similar manner to that when working with the Divide block.
Hence, the CBlock::ReadBlockDataFromFile() function adds any additional ports as speci-
fied in the data file and the CPort function then sets the appropriate port values.
1. Add a public virtual member function to the CSumBlock class with the prototype
virtual void CSumBlock::ReadBlockDataFromFile(ifstream &fin).
2. Edit the function as shown to read in the number of addition and subtraction ports.
25.5.6.13 CTransferFnBlock::ReadBlockDataFromFile()
The CTransferFnBlock member data consist of two double arrays and their integer lengths. The con-
structor function, called from within CSystemModel::ReadSystemModelDataFromFile(),
uses default string values to initialize the double arrays, allocating memory for them in the process.
This memory needs to be deallocated in the CTransferFnBlock’s ReadBlockDataFromFile()
function, and new memory allocated, corresponding to the array lengths read from the file.
Finally, the double data are converted to the equivalent representative string form for use in the
BlockDlgWndParameterInput() function.
1. Add a public virtual member function to the CTransferFnBlock class with the prototype
virtual void CTransferFnBlock::ReadBlockDataFromFile(ifstream
&fin).
2. Edit the function as shown to deallocate memory, read member data, allocate new mem-
ory, and initialize the CString variables.
Serialization 807
//AfxMessageBox(“\n CTransferFnBlock::ReadBlockDataFromFile()\n”,
MB_OK, 0);
// MEMORY DELETE
if(m_dNumerVector != NULL)
{
delete [] m_dNumerVector;
}
if(m_dDenomVector != NULL)
{
delete [] m_dDenomVector;
}
// MEMORY NEW
m_dNumerVector = new double[m_iLengthNumer];
m_dDenomVector = new double[m_iLengthDenom];
25.6 SAVING THE INITIAL OUTPUT SIGNAL FOR THE DIVIDE BLOCK
At present, for model diagrams involving feedback loops where the loop-repeated or junction node
is a Divide block, the initial output signal must be set by the user via the COutputSignalDialog dia-
log window object. This is activated by right-clicking on the Divide block and selecting “Set Output
Signal” from the Context menu, which then calls CDiagramEngDoc::OnSetOuputSignal()
808 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
1. Add two private integer member variables to the CDivideBlock class to record the number
of rows and columns of the initial output signal matrix, i.e., “m_iNrowsInitSignal” and
“m_iNcolsInitSignal”, respectively.
2. Add a private variable, “m_dInitSignalMatrix”, of type “double**” to the CDivideBlock
class to record the initial output signal as a matrix.
3. Finally, add a private CString variable, “m_strInitSignal”, to the CDivideBlock class to
record the CString equivalent of the double matrix for dialog window display purposes.
The class destructor, empty thus far in the project, should be amended as shown in bold in the follow-
ing to delete any allocated memory that was used to hold the initial output signal, “m_dInitSignal”.
CDivideBlock::∼CDivideBlock(void)
{
int i;
// MEMORY DELETE
// Delete the initial signal matrix whose memory was allocated in
AssignBlockOutputSignal()
if(m_dInitSignal != NULL)
{
for(i=0; i<m_iNrowsInitSignal; i++)
{
delete [] m_dInitSignal[i];
}
delete [] m_dInitSignal;
m_dInitSignal = NULL;
}
}
1. Add a public virtual member function to the CDivideBlock class with the following proto-
type: virtual void CDivideBlock::AssignBlockOutputSignal(void).
2. Edit the function as shown to perform a similar general operation to the base class
version, but to also add specific functionality to allow the CDivideBlock class to
remember the initial output signal entered by the user through the dialog window.
void CDivideBlock::AssignBlockOutputSignal()
{
int i;
int attached = 0; // flag denoting the attachment of an output
connection to a block output port
int input = 0; // input: (0) incomplete, (1) complete
int valid; // used to set signal validity
CSignal *signal = NULL; // local ptr-to-signal
CString sMsg; // string msg. to be displayed
CString sMsgTemp; // temp string msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
list<CConnection*>::iterator it_con; // iterator for list of
CConnection ptrs.
vector<CPort*>::iterator it_port; // iterator for vector of CPort
ptrs.
vector<CPort*> vector_of_output_ports = GetVectorOfOutputPorts();
// vector of output ports
810 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// MEMORY DELETE
if(m_dInitSignal != NULL)
{
for(i=0; i<m_iNrowsInitSignal; i++)
{
delete [] m_dInitSignal[i];
}
delete [] m_dInitSignal;
m_dInitSignal = NULL;
m_iNrowsInitSignal = 0;
Serialization 811
m_iNcolsInitSignal = 0;
// NOTE: m_strInitSignal is not set to empty, since its value is
used to setup the new matrix m_dInitSignal
}
input = 0;
}
else // Non-NULL input
{
// UPDATE ALL CONNECTION OBJECTS WHOSE REFERENCE-FROM
PORT IS THAT OF THE CURRENT BLOCK’S OUTPUT PORT
valid = WriteDataToOutputSignal(m_dInitSignal,
m_iNrowsInitSignal, m_iNcolsInitSignal);
{
(*it_con)->SetRefFromPort(NULL); // set ref-from-port to
NULL (since not attached to a block)
}
}
// MEMORY DELETE
// m_dInitSignal is deleted upon entry into this fn and then in the
CDivideBlock destructor.
}
//AfxMessageBox(“\n CDivideBlock::WriteBlockDataToFile()\n”,
MB_OK, 0);
//AfxMessageBox(“\n CDivideBlock::ReadBlockDataFromFile()\n”,
MB_OK, 0);
the dialog window. An accessor method is to be added to the CDivideBlock class, and then the
PreliminarySignalPropagation() function needs to be amended.
1. Add a public member function to the CDivideBlock class with the prototype double
**GetInitialSignalMatrix(int &nrows, int &ncols).
2. Edit the function as shown to assign the initial signal matrix (“m_dInitSignal”) dimensions
and to return its base address.
double** CDivideBlock::GetInitialSignalMatrix(int &nrows, int &ncols)
{
// Assign the member variables
nrows = m_iNrowsInitSignal;
ncols = m_iNcolsInitSignal;
}// end j
}// end i
}// end j
}// end i
…
return valid;
}
The developer will notice the type cast “(CDivideBlock*)(*it_blk)” used to cast what
was a pointer-to-CBlock “*it_blk” to now a pointer-to-CDivideBlock, prior to calling the
GetInitialSignalMatrix() function (introduced earlier). This is necessary as the func-
tion GetInitialSignalMatrix() belongs to the CDivideBlock class and not the CBlock
class and is only called for a Divide block, the name of which was filtered via a call to
GetBlockName().
818 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 25.10 Lotka–Volterra predator–prey model with two Divide blocks forming feedback loops.
FIGURE 25.11 OutputSignalDialog dialog window used to set the initial output signal of a Divide block
forming a feedback loop.
Now the code may be recompiled and rerun. If the user builds a model, where the Divide
block is the junction node in a feedback loop (Figure 25.10), then its initial output signal,
“m_dInitSignal”, entered via the dialog window (Figure 25.11), is recorded upon saving the model.
Then, after reloading the model, the correct output signal value will automatically be present in the
COutputSignalDialog dialog window and written to the output connection’s CSignal data member.
That is, the user need not reenter the initial signal value after reloading a model if it was saved to
the model data file.
Serialization 819
25.7.1 Add a Save Data Button to the Output Block Dialog Resource
Select the IDD_OUTPUT_BLK_DLG_BTN_SD dialog resource from the ResourceView of the
Workspace pane and add an additional Save Data button control on the dialog (Figure 25.12b) with
the properties shown in Table 25.22: existing controls are shown shaded in gray.
(a) (b)
FIGURE 25.12 OutputBlockDialog dialog window used to set properties to display signals on a graph:
(a) its current form and (b) with the intended Save Data button.
820 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 25.22
Dialog Object, Properties, and Settings for the
OutputBlockDialog Dialog Window
(IDD_OUTPUT_BLK_DLG)
Object Property Setting
Group Box ID ID_OUTPUT_BLK_DLG_GPBOXNOT
Caption Textual Output Settings
Radio Button ID ID_OUTPUT_BLK_DLG_RB_STDNOT
Group Checked
Caption Standard notation
Radio Button ID ID_OUTPUT_BLK_DLG_RB_SCINOT
Group Unchecked
Caption Scientific notation
Group Box ID ID_OUTPUT_BLK_DLG_GPBOXTPTS
Caption Graphical Output Settings
Radio Button ID ID_OUTPUT_BLK_DLG_RB_TPTS
Group Checked
Caption Show time points
Radio Button ID ID_OUTPUT_BLK_DLG_RB_NOTPTS
Group Unchecked
Caption Hide time points
Button ID ID_OUTPUT_BLK_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
Button ID ID_OUTPUT_BLK_DLG_BTN_SG
Caption &Show Graph
Button ID ID_OUTPUT_BLK_DLG_BTN_SD
Caption Save &Data
TABLE 25.23
Objects, IDs, Class, and Event-Handler Functions for the COutputBlockDialog Class
Object ID Class Command Event-Handler
Cancel button IDCANCEL (default) COutputBlockDialog OnCancel()
OK button ID_OUTPUT_BLK_DLG_BTN_OK COutputBlockDialog OnOutputBlkDlgBtnOk()
Save Data ID_OUTPUT_BLK_DLG_BTN_SD COutputBlockDialog OnOutputBlkDlgBtnSaveData()
button
Show Graph ID_OUTPUT_BLK_DLG_BTN_SG COutputBlockDialog OnOutputBlkDlgBtnShowGraph()
button
Serialization 821
void COutputBlockDialog::OnOutputBlkDlgBtnSaveData()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
int btnSel; // button selection
ifstream fin; // infile stream
ofstream fout; // outfile stream
BOOL dlgType = FALSE; // TRUE => File Open dlg, FALSE => File
Save dlg
CString sCaption = “DiagramEng - Overwrite”; // message box caption
CString strDefExt = “.txt”; // default file
extension
CString strFileName = “output_data.txt”; // file name
CString strFilePath; // local file path var
CString strFilter = “All Files (*.*)|*.*|Plain Text (*.txt)|*.txt||”;
// string pairs specifying filters for file list box
CString sMsg; // main msg string
CString sMsgTemp; // temp msg string
CWnd l_wnd; // local CWnd
DWORD dwFlags = OFN_ENABLESIZING | OFN_HIDEREADONLY; // customization
flags
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
//sMsg.Format(“\n COutputBlockDialog::OnOutputBlkDlgBtnSaveData()\n”);
//AfxMessageBox(sMsg, nType, nIDhelp);
// CREATE A CFileDialog DIALOG WND
CFileDialog dlgFile(dlgType, strDefExt, strFileName, dwFlags,
strFilter, NULL);
// CHECK BUTTON SELECTION
if(dlgFile.DoModal() == IDOK)
{
strFilePath = dlgFile.GetPathName();
{
fout.open(strFilePath, ios::out); // open file in
output mode
}
else if(btnSel == IDNO)
{
sMsg.Format(“”);
sMsg.Format(“\n Aborting writing model data to file. \n”);
nType = MB_OK | MB_ICONSTOP;
//AfxMessageBox(sMsg, nType, nIDhelp);
fin.close();
fin.clear();
return;
}
}
fin.close();
fin.clear();
Hence, add a public member function to the COutputBlock class with the prototype
void COutputBlock::WriteNumericalOutputDataToFile(ofstream &fout).
Edit the function as shown to iterate through the data matrix “m_dOutputMatrix” and print the
data to a file in columns.
//sMsg.Format(“\n COutputBlock::WriteNumericalOutputDataToFile()\n”);
//AfxMessageBox(sMsg, nType, nIDhelp);
where, fs(t), for s ∈ {1, …, 4} (four signals are used here for simplicity), are the individual signals
being recorded for each time point ti ∈ [t0, tn], for initial and final simulation time points, t0 and tn,
respectively. The WriteNumericalOutputDataToFile() function first determines the time-
based data, t0, tn; the time-step size, δt; and the number of data points, equivalently, the number of
submatrices within the data matrix M. Then, for each time point ti, represented by “t_cnt”, the rows
(“m_iNrows”) of the matrix and the number of signals per row (“n_signals_per_row”) are both
iterated over, and the particular signal value located at column, “col”, is extracted and written to
a file. The effect of this nested iteration is to write a data file where each row of data in the output
file corresponds to all signal output for a particular time point ti and is hence of the following form:
If there are no data in the output matrix, “m_dOutputMatrix”, then the number of rows and the
number of columns, “m_iNrows” and “m_iNcols”, respectively, are zero, and only the time points
corresponding to the system model simulation parameters will be written to the output file with the
default name “output_data.txt”.
25.8 SUMMARY
The serialization process involves writing and reading data to and from a file using output
(ofstream) and input (ifstream) file stream objects, respectively, supported by the file stream header
file “fstream.h”. Initially, the DiagramEng project data structure was revised to determine the mem-
ber data to be written to and read from a file. The classes involved were CSystemModel, CBlock,
CBlockShape, CPort, CConnection, and the derived CBlock-based classes. The CSignal member
data were not serialized since they are determined dynamically during a simulation.
Event-handler functions were added for the Main frame– and Child frame–based windows to
the CDiagramEngApp and CDiagramEngDoc classes to initiate the serialization process. The
CDiagramEngDoc functions added include OnFileOpen(), OnFileSaveAs(), OnFileSave(),
Serialization 825
REFERENCES
1. Chapman, D., SAMS Teach Yourself Visual C++ 6 in 21 Days, Sams Publishing, Indianapolis, IN, 1998.
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
3. Microsoft Support, How to add command handlers for MRU menu items in MFC applications,
Microsoft Corporation, 2009. http://support.microsoft.com/kb/243751
4. Kelley, A. and Pohl, I., A Book On C: Programming in C, 2nd edn., Benjamin Cummings, Redwood City,
CA, 1990.
5. IEEE Standard for Floating-Point Arithmetic (IEEE 754-2008), IEEE, 2008.
6. Salas, S. L. and Hille, E., Calculus: One and Several Variables (Complex Variables, Differential Equations
Supplement), 6th edn., John Wiley & Sons, New York, 1990.
Part III
Refinement
INTRODUCTION
Part III, consisting of Chapters 26 through 34, starts by reviewing the existing menu and toolbar-
based functionality in the project to determine the remaining features that need to be added to allow
the user to perform efficient modeling and simulation activities. Printing and print previewing is
facilitated by adding key functionality to set the number of pages to be printed, to prepare the device
context, set mapping modes, and to perform a transformation between the window and viewport
rectangles. Modifications are then made to implement a scrolling view to permit the user to draw
large diagrams: conversions are required between device points and logical points to accommodate
for the change in position of the view. Automatic fitting of the viewport scale to the physical win-
dow, zooming in and out, and resetting the default diagram geometry are also implemented. The
Edit menu is extended with the Undo, Redo, Cut, Copy, and Paste actions, which, through the use
of a clipboard object and the relevant copy constructors and assignment operators, allow the user
to efficiently edit a model diagram. Annotations are then introduced to add explanative detail to a
model diagram and involve attributes, including the font, that are specified using a dialog window.
The Tools menu is augmented with a diagnostic information entry that upon selection displays
process physical and virtual memory usage information. The Help menu is completed with a Using
DiagramEng entry that invokes a process to display instructions to the user about the usage of the
application. Finally, a concluding section summarizes the developmental process and presents sug-
gestions for improving the software application.
827
26 Review of Menu and
Toolbar-Based Functionality
Part II
26.1 INTRODUCTION
The previous chapters provided instructions to add functionality to the DiagramEng project for the
Main frame and Child frame window-based menu items, the Context menu, and the Main frame,
Common Operations, and Common Blocks toolbars. A consolidation was made in Chapter 13 to
assess the most important features to be developed at that stage. The current chapter reviews the
remaining menu and toolbar-based functionality to be added, specifically for the Child frame–
based menus, since the Main frame–based operations work as desired.
829
830 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 26.1 DiagramEng MDI application with Child frame–based menus visible.
TABLE 26.1
Key Child Frame Menus and Their Corresponding Entries for Which Functionality
Is to Be Added
File Edit View Model Simulation Format Tools Window Help
Print Undo Auto Fit Build Show Diagnostic Using
Diagram Subsystem Annotations Info. DiagramEng
Print Cut Zoom In
Preview
Copy Zoom Out
Paste
Select
All
The Simulation and Window menus do not require further development (blank).
The Tools menu has a Diagnostic Info. entry to which the event-handler function of the
CDiagramEngDoc class, OnToolsDiagnosticInfo(), is attached. The function will ultimately
activate a dialog window that will provide useful information to the user concerning memory usage
information for the system and main application process.
Finally, the Help menu has an entry titled Using DiagramEng with the associated event-handler
function, OnHelpUsingDiagramEng(), of the CDiagramEngDoc class, that is to generate a
Portable Document Format (PDF) document named, “UsingDiagramEng.pdf”, providing instruc-
tions and examples on how to use the DiagramEng software application for modeling and simulation.
In each of the following chapters, a menu and its entries will be discussed and functionality
added to the project, to allow the user to easily interact in a predictable manner with the application,
to print, edit, view, simulate, and format model diagrams.
26.3 SUMMARY
A brief review is made of the remaining Child frame–based menu functionality that is to be added
to the DiagramEng project to allow the user to perform efficient modeling and simulation. The key
menus, for which details are to be added in the following chapters, are: File, Edit, View, Model,
Format, Tools, and Help.
27 Printing and Print Preview
27.1 INTRODUCTION
The previous chapter reviewed the remaining functionality to be added for the Child frame
window-based menu items for the DiagramEng project. The File menu entries, Print and Print Preview,
and the functions OnPreparePrinting(), OnBeginPrinting(), and OnEndPrinting()
are provided by the MFC AppWizard on the creation of the DiagramEng application since support
was chosen for printing and print preview. However, to get printing and print preview to work prop-
erly and consistently, such that the model diagram drawn in print is the same size as that on screen
and paginated correctly upon output, additional details and overriding methods need to be added to
the CDiagramEngView class.
The developer is enthusiastically encouraged to read Chapter 6 “Printing and Print Preview” of
Using Visual C++ 6 by Gregory [1] and Chapter 4 “Basic Event Handling, Mapping Modes, and
a Scrolling View” and Chapter 19 “Printing and Print Preview” of Programming Visual C++ by
Kruglinski et al. [2].
Gregory [1] provides a very clear introduction to the Printing and Print Preview pro-
cesses with an example that draws rectangles on the screen upon mouse-button-click events.
Scaling, Printing Multiple Pages, Setting the Origin, and MFC and Printing are the four
sections presented that cover the responsibilities of the developer to make the Printing and
Print Preview processes work properly. The key functions to be added by the developer to
the CView-derived class, as Gregory presents them, are OnDraw(), OnBeginPrinting(),
and OnPrepareDC(). OnDraw() is used to draw on the screen, print to the printer, and
draw the print preview; OnBeginPrinting() is used to set the maximum number of pages
to be printed (SetMaxPage()); and OnPrepareDC() is used to set the viewport origin
(SetViewportOrg()), such that the correct page appears in print preview and on printing.
In addition, MSDN Library for Visual Studio 6.0 [3] presents a flowchart diagram (see Figure 27.1),
indicating the order of the MFC-based printing process function calls in the Printing Loop:
OnPrint() invokes the OnDraw() method and OnPrint() and OnPrepareDC() are called
for each page to be printed. The dashed arrows and lines (added by the authors here) represent
inherent and suggested function calls, respectively. In addition, the MSDN Library for Visual
Studio 6.0 [3] provides the brief descriptions presented in Table 27.1, for the functions shown
in Figure 27.1.
Kruglinski et al. [2] also discuss the Printing and Print Preview processes and provide exam-
ples concerning printing text and graphics on the screen. The interested reader should consult
exercise 19B for an example of using the OnPrint() function to augment the printable output
with headers and footers. In Chapter 4 of Ref. [2], a section titled “Mapping Modes” discusses
the common mapping modes used to provide a coordinate conversion from the logical coordinate
system to the device coordinate system. In particular, the MM_TEXT, MM_ISOTROPIC, and
MM_ANISOTROPIC modes are explained in detail. The software developer will appreciate the
discussion on scaling between the logical and device coordinate systems; this is also addressed
in the instructions that follow.
831
832 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
OnPreparePrinting() DoPreparePrinting()
OnPrint() OnDraw()
OnEndPrinting()
FIGURE 27.1 The MFC-based Printing process calling CView-derived member functions (with anno-
tations to the right). (From Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual
Studio™ 6.0 Development System, Microsoft Corporation, 1998.)
TABLE 27.1
Descriptions of the Functions Used in the Printing Loop
Function Description
OnPreparePrinting() Override to enable printing and print preview and call
DoPreparePrinting() passing the CPrintInfo *pInfo parameter
DoPreparePrinting() Called to invoke the Print dialog box and to create a printer device context
OnBeginPrinting() Override to allocate GDI resources, e.g., pens and fonts specifically
needed for printing. SetMaxPage() may also be called from here
OnPrepareDC() Override to adjust the attributes of the device context.
SetViewportOrg() may also be called from here
OnPrint() Called by the framework to print or preview a page of the document.
The default implementation calls the OnDraw() member function
OnDraw() Called by the framework to perform screen display, printing, and print
preview, and passes a different device context in each case
OnEndPrinting() Override to free any GDI resources that may have been allocated in
OnBeginPrinting()
Source: Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0
Development System, Microsoft Corporation, 1998.
TABLE 27.2
Mapping Modes to Transform Page-Space Units into Device-Space
Units [3]
Mapping Mode Details
MM_ANISOTROPIC Unit: application-specified. (x, y) axes increase is application-specified
MM_HIENGLISH Unit: 0.001 inch. (x, y) axes increase (right, up)
MM_HIMETRIC Unit: 0.01 millimetre. (x, y) axes increase (right, up)
MM_ISOTROPIC Unit: application-specified. (x, y) axes increase is application-specified
MM_LOENGLISH Unit: 0.01 inch. (x, y) axes increase (right, up)
MM_LOMETRIC Unit: 0.1 millimetre. (x, y) axes increase (right, up)
MM_TEXT Unit: device pixel. (x, y) axes increase (right, down)
MM_TWIPS Unit: 1/1400 inch. (x, y) axes increase (right, up)
Source: Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0
Development System, Microsoft Corporation, 1998.
834 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Window Rectangle
int iWndRect_x1 = 0; // x1 coord of window rectangle
int iWndRect_y1 = 0; // y1 coord of window rectangle
int iWndRect_x2 = 1000; // x2 coord of window rectangle
int iWndRect_y2 = 1000; // y2 coord of window rectangle
CRect WndRect(iWndRect_x1, iWndRect_y1, iWndRect_x2, iWndRect_y2);
// (x1, y1, x2, y2)
pDC->SetWindowExt(WndRect.Size() ); // set extent of Window rect
pDC->SetWindowOrg(WndRect.TopLeft() ); // set origin of Window rect
The viewport-specific code, also to be added to OnDraw(), shows how the viewport rectangle is
set up; in this case, the x and y extents of the viewport are scaled up from the window extents to
enlarge the diagram for printing and print preview purposes only (scaling is discussed in Section
27.3.2). For simple drawing to the screen, i.e., when not printing (IsPrinting() returns FALSE),
no scaling needs to be performed.
// Viewport Rectangle
int iVptRectExt_x; // x extent of viewport
int iVptRectExt_y; // y extent of viewport
double scale = 1.0; // scale of the transformation
CRect client; // client rect
GetClientRect(&client); // get client rect
iVptRectExt_x = int(scale*(iWndRect_x2 - iWndRect_x1) ); // x extent of
viewport
iVptRectExt_y = int(scale*(iWndRect_y2 - iWndRect_y1) ); // y extent of
viewport
pDC->SetViewportExt(iVptRectExt_x, iVptRectExt_y); // set extent of
viewport
pDC->SetViewportOrg(client.TopLeft() ) // set origin of viewport
The developer should keep in mind in which quadrant a drawing should be placed. If, e.g., a diagram
is drawn in the positive quadrant in the logical coordinate system, where x increases to the right
and y increases downward, then the statement CRect WndRect(0, 0, 1000, 1000) (where
the arguments in order specify the left, top, right, and bottom positions of the CRect object) with
SetWindowExt(1000, 1000) would allow the drawing to appear on screen exactly as it does
in the logical coordinate system. If, however, CRect WndRect(−500, 500, 500, −500) and
Printing and Print Preview 835
SetWindowExt(1000, −1000) were used, then the drawing would still appear in the positive
quadrant, but this quadrant would be the upper left corner of the display, i.e., the y axis would appear
flipped vertically about the x axis and would increase upward. The developer should experiment with
various values to gain insight into the way the transformation is made.
xp
s= (27.1)
xs
where xp and xs are the number of pixels per logical inch along the device width for the printer and
screen, respectively. The code excerpt shown in the following makes the assumption that xs = 96 pixels
and that the scaling only occurs upon printing, where the call to IsPrinting() returns TRUE.
Other arguments may also be passed to the GetDeviceCaps() function to retrieve information
about the capacity of the device; a selection is shown in Table 27.3.
If the developer runs an application and uses the debugger or a message box to display the
returned value of GetDeviceCaps(), called upon a pointer-to-CDC (CDC *pDC), using
the arguments provided in Table 27.3, then depending on whether the application is printing
(IsPrinting() = TRUE) or simply drawing in the absence of printing (IsPrinting() = FALSE),
836 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 27.3
A Selection of Arguments for GetDeviceCaps() to
Obtain Information about the Capacity of the Device
Argument Description
HORZRES Width of the display in pixels
VERTRES Height of the display in pixels
HORZSIZE Width of the physical display in millimeters (mm)
VERTSIZE Height of the physical display in millimeters (mm)
LOGPIXELSX Number of pixels per logical inch along the display width
LOGPIXELSY Number of pixels per logical inch along the display height
TABLE 27.4
Sample Returned Values When Calling GetDeviceCaps() with a
Selection of Arguments, When Printing (IsPrinting() = TRUE) or
Drawing without Printing (IsPrinting() = FALSE)
Argument IsPrinting() = TRUE, Printer Device IsPrinting() = FALSE, Screen Device
HORZRES 4760 pixels 1280 pixels
VERTRES 6779 pixels 1024 pixels
HORZSIZE 202 mm 375 mm
VERTSIZE 287 mm 300 mm
LOGPIXELSX 600 pixels/in. 96 pixels/in.
LOGPIXELSY 600 pixels/in. 96 pixels/in.
different values will be returned corresponding to the device context being passed, as shown in
Table 27.4; when IsPrinting() is TRUE or FALSE, the printer device context or screen device
context is used, respectively. These values can then be used for scaling and setting the viewport
origin (SetViewportOrg()) such that upon printing, the correct diagram scale is used and page
is printed.
⎛x ⎞⎛y ⎞
n = ⎜ e + 1⎟ ⎜ e + 1⎟ (27.2)
⎝ w ⎠⎝ h ⎠
Printing and Print Preview 837
The OnBeginPrinting() function can now be augmented to set the maximum number of pages
to be printed via a call to SetMaxPage() upon the “pInfo” pointer-to-CPrintInfo as shown.
// DiagramEng (start)
int numPages; // no. of pages
int pageHeight; // page height of printer device (mm)
int pageWidth; // page width of printer device (mm)
double max_x; // extreme x posn (mm)
double max_y; // extreme y posn (mm)
CPoint ptMax; // pseudo extreme point on diagram (extreme
coords may be from diff. elements)
CDiagramEngDoc *pDoc = GetDocument();
// Get extreme coords from diagram entities and convert to mm. (96
pixels = 1 inch = 25.4 mm)
ptMax = pDoc->GetSystemModel().DetermineDiagramCoordinateExtrema();
max_x = 25.4*double(ptMax.x)/96.0;
max_y = 25.4*double(ptMax.y)/96.0;
Initially the page width and height in millimeters are obtained via the calls to
GetDeviceCaps() with the arguments HORZSIZE and VERTSIZE, respectively, made
on the “pDC” pointer-to-CDC, in this case, the printing device. Then a call is made to
DetermineDiagramCoordinateExtrema() to retrieve the most extreme positive (xe, ye)
coordinates of diagram entities, i.e., blocks, connection head or tail points, and connection bend
points. The extreme coordinates are then converted into the common millimeter measurement,
838 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
given 96 pixels/in. for the screen device, and the number of pages horizontally (“m_iNpagesWide”)
and vertically (“m_iNpagesHigh”) are determined and used to set the total number of pages
(“m_numPages”) (n in (27.2)) to be printed via the call to SetMaxPage().
The developer will have noticed the introduction of two new member variables to record the num-
ber of pages horizontally and vertically; these are used later in the OnPrepareDC() function to
set the viewport origin such that the correct page may be previewed or printed. Hence, add two new
private integer member variables to the CDiagramEngView class with names “m_iNpagesWide”
and “m_iNpagesHigh” to record the number of pages to be printed horizontally and vertically,
respectively.
The new function to determine the coordinate extrema is added to the project as follows.
1. Add a public member function to the CSystemModel class with the prototype:
CPoint CSystemModel::DetermineDiagramCoordinateExtrema(void).
2. Edit the function as shown to iterate through the list of blocks, connections, and connection
bend points to determine the diagram object CPoint coordinate extrema.
CPoint CSystemModel::DetermineDiagramCoordinateExtrema(void)
{
int pt_rad = 5; // radius of a bend point (assume fixed and small)
double blk_height; // block width
double blk_width; // block height
CPoint ptBend; // bend point
CPoint ptBlkPosn; // block position
CPoint ptHead; // connection head point
CPoint ptMax; // pseudo extreme point (extreme coords may be
from diff. elements)
CPoint ptTail; // connection tail point
CPoint ptTemp; // temporary point
list<CBlock*>::iterator it_blk; // block iterator
list<CConnection*>::iterator it_con; // connection iterator
list<CPoint>::iterator it_pt; // bend points iterator
The function simply iterates through the list of blocks, connections, and connection bend points,
retrieving their (x, y) coordinate locations, making a comparison with the current extremum coor-
dinates (xe, ye), “ptMax.x” and “ptMax.y”, and updating them appropriately. For blocks, the block
width and height are added to the block position denoting the extreme point of the block and a
comparison made. For connection bend points, a distance equal to the radius of the bend point’s
ellipse is added to the bend point coordinates and a comparison made. Thereafter, the pseudo
840 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
extreme point (CPoint), “ptMax”, is updated and returned. The extreme point is described as
being “pseudo” extreme since the xe and ye components defining the point may be updated inde-
pendently from different diagram entities. For example, given two diagram entities’ coordinates
(x1, y1) and (x2, y2), if x1 > x2 and y2 > y1, then the pseudo extreme point, “ptMax”, has coordinates
(xe, ye) = (x1, y2).
// DiagramEng (start)
CView::OnPrepareDC(pDC, pInfo); // call base class version first
if(pDC->IsPrinting() )
{
int i; // row index
int j; // col index
int originX; // x-coord of viewport origin
int originY; // y-coord of viewport origin
int page_no; // page number calculated
int pageHeight; // page height in dots
int pageWidth; // page width in dots
int val = 0; // loop-breaking value
{
page_no = j*m_iNpagesHigh + i + 1; // add 1, since
m_nCurPage is an element of [1,n] (starts from 1).
// DiagramEng (end)
}
m_iNpagesWide
pageWidth
FIGURE 27.2 A sample diagrammatic representation of the algorithm used to set the viewport origin for a
multiple page print or print preview instance, with “m_iNpagesWide” and “m_iNpagesHigh” set to 4 and 3,
respectively.
// DiagramEng (start)
pDoc->GetSystemModel().DrawSystemModel(pDC);
// DiagramEng (end)
}
The OnDraw() function should be augmented as shown in the following to first set the window
dimensions, followed by a scaling of the window dimensions to create an enlarged viewport in
preparation for printing and print preview. The setting of the window and viewport extents and
origins, to affect the transformation, is discussed in Section 27.3.1, and the scaling used based upon
the different resolutions of the screen and printer is explained in Section 27.3.2.
// DiagramEng (start)
Printing and Print Preview 843
The developer will notice that SetMapMode() and SetViewportOrg() are not called in
OnDraw() since they are already called upon printing (IsPrinting()) in OnPrepareDC().
Finally, the user may draw a diagram model that occupies large a screen area, and upon print-
ing and print preview, the whole diagram is presented on numerous pages of output as shown in
Figure 27.3.
844 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 27.3 The Lorenz equation model diagram requiring two pages for printing and print preview:
“m_iNpagesHigh” and “m_iNpagesWide” are 1 and 2, respectively.
27.7 SUMMARY
The Printing and Print Preview processes, the action of which is identified via a call to
IsPrinting(), typically involve the CView-derived functions: OnPreparePrinting(),
DoPreparePrinting(), OnBeginPrinting(), OnPrepareDC(), OnPrint(), OnDraw(),
and OnEndPrinting(), where OnPrepareDC() and OnPrint() are iteratively called for
each page to be printed. A mapping mode defines the transformation from page-space units into
device-space units and the axis orientations. The window and viewport rectangles with the extent-
and origin-setting functions, SetWindowExt(), SetWindowOrg(), SetViewportExt(), and
SetViewportOrg(), define the mapping from the window to the viewport. In addition, a scal-
ing may be used in the transformation and is typically based upon the ratio of the screen and
printer device capacities obtained using the GetDeviceCaps() function passing the appropriate
argument.
The three key methods to which functionality is added are OnBeginPrinting(),
OnPrepareDC(), and OnDraw(). OnBeginPrinting() is used to set the maximum num-
ber of pages to be printed via a call to SetMaxPage(); OnPrepareDC() prepares the device
context, sets the mapping mode via SetMapMode(), and sets the viewport origin using a call to
SetViewportOrg(); and, finally, OnDraw() performs the appropriate transformation including
scaling between the window and the viewport rectangles, before drawing the model diagram.
REFERENCES
1. Gregory, K., Using Visual C++ 6: Special Edition, Que Publishing, Indianapolis, IN, 1998.
2. Kruglinski, D. J., Wingo, S., and Shepherd, G., Programming Visual C++, 5th edn., Microsoft Press,
Redmond, WA, 1998.
3. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
4. Brinkster, ScrollViews, http://aclacl.brinkster.net//MFC//ch10b.htm, (accessed June 30, 2010).
28 Implementing a Scroll View
28.1 INTRODUCTION
The previous chapter demonstrated how a diagram that encompasses a large viewing area may be
aptly printed on multiple pages. This was achieved using a transformation-defining mapping mode
and the setting of the extents of the window and viewport rectangles using scaling and the inbuilt
convenience of scroll bars in Print Preview mode. However, when not in Printing or Print Preview
mode, no vertical or horizontal scroll bars are present on the main CView-derived (model-drawing)
window, and hence, the user is limited in the amount of space available to draw a model diagram.
Hence, changes need to be made to the DiagramEng project to convert the existing CView-based
code to a CScrollView-based implementation.
The CView-based functionality supported at present allows the software user to draw model dia-
grams that typically consist of blocks, connections, and connection bend points in a CView-derived
view window. Connections may be drawn by clicking the left mouse button, moving the mouse,
and releasing the button. Diagram entities may be moved by left-clicking an item and dragging it
to a different location, or grouping a selection of items explicitly and moving them simultaneously,
through the use of a CRectTracker object. In addition, the user may double-left-click a block to set
its properties or right-click an entity to invoke a Context menu to perform deletion, insertion, move-
ment, orientation, and property-setting actions.
The CDiagramEngView class was originally derived from the CView class, using the AppWizard
upon application construction, as may be seen in the “DiagramEngView.h” header file, i.e., “class
CDiagramEngView : public CView”. The instructions here detail the changes that need to be made
to the declarations and definitions of various functions to convert them from CView-derived methods
to CScrollView-derived methods. However, in converting the main CView-based view window to a
CScrollView-based scrolling view, the CPoint, “point”, arguments used in mouse-button-click-related
user interaction sequences, e.g., that in CDiagramEngView::OnLButtonDown(UINT nFlags,
CPoint point), need to be converted from the device coordinate system to the logical coordi-
nate system for consistent underlying document-related action due to a scroll-based shift of the logi-
cal coordinate system with respect to the device coordinate system. In addition, CScrollView-related
characteristics need to be set up in the CDiagramEngView::OnInitialUpdate() function.
Finally, the tracking of individual and multiple items needs to be altered to cater for the new scrolling
view. The following instruction is divided into seven sections: (1) coordinate systems, (2) conversion
from CView to CScrollView, (3) device point to logical point coordinate conversions, (4) tracking of
multiple items, (5) limiting diagram entity placement, (6) fitting the logical view to the physical view,
and (7) zooming in and out of the view.
845
846 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Viewing window
FIGURE 28.1 Coincident logical (xi,1, yi,1) and device (xi,d, yi,d) coordinate systems using a CView-derived
view, with no scroll bars and the default MM_TEXT mapping mode (i ∈ [0, n − 1]).
in this mode,” with “a one-to-one mapping ratio of logical to physical device units” [1], where the
horizontal (x) and vertical (y) axes increase to the right and downward, respectively.
The DiagramEng application, up until now, has used a CView-derived class, CDiagramEngView,
and a view window through which user interaction may take place. Without scroll bars and no change
in the window and viewport origins and extents, using the default MM_TEXT mapping mode, the
device coordinates (xi,d, yi,d) and logical coordinates (xi,1, yi,1) are coincident, and no conversion needs
to be made of the CPoint, “point”, object (xi, yi) between the device and logical coordinate systems
(Figure 28.1), for i ∈ [0, n − 1], where n is the number of coordinates, or points, being transformed, and
the subscripts d and l refer to device and logical points, respectively. However, with scroll bars, due to a
moving view, the CPoint, “point”, object of the device coordinate system is different to that of the logi-
cal coordinate system, and a conversion is necessary from the device point to logical point coordinates
(DPtoLP()) and in certain situations from the logical point to device point coordinates (LPtoDP()).
Figure 28.2 shows the effect of scrolling on the transformation between the device and logical
coordinate systems using the default MM_TEXT mapping mode without a change in the win-
dow and viewport origins and extents; as the user scrolls down and across, the effective logical
coordinate system is offset from the device by the amount scrolled in both the x and y directions,
Δx and Δy, respectively. That is, the ith logical coordinate is related to the device coordinate as
follows: (xi,1, yi,1) = (xi,d + Δx, yi,d + Δy) for i ∈ [0, n − 1].
(x0,l, y0,l) x
(x0,d, y0,d) x
Δx
Viewing window
FIGURE 28.2 The transformation between the logical and device coordinate systems as a result of scrolling
in the horizontal and vertical directions using the default MM_TEXT mapping mode.
Implementing a Scroll View 847
The message map section is altered as shown in bold below, where CView is replaced by
CScrollView.
BEGIN_MESSAGE_MAP(CDiagramEngView, CScrollView) // DiagramEng: CView
changed to CScrollView
//{{AFX_MSG_MAP(CDiagramEngView)
ON_COMMAND(ID_VIEW_ZOOM_IN, OnViewZoomIn)
ON_COMMAND(ID_VIEW_ZOOM_OUT, OnViewZoomOut)
ON_COMMAND(ID_VIEW_AUTO_FIT_DIAGRAM, OnViewAutoFitDiagram)
ON_WM_LBUTTONDOWN()
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONUP()
ON_WM_CONTEXTMENU()
ON_WM_LBUTTONDBLCLK()
ON_WM_KEYDOWN()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CScrollView::OnFilePrint)
// DiagramEng: CView changed to CScrollView
ON_COMMAND(ID_FILE_PRINT_DIRECT, CScrollView::OnFilePrint)
// DiagramEng: CView changed to CScrollView
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CScrollView::OnFilePrintPreview)
// DiagramEng: CView changed to CScrollView
END_MESSAGE_MAP()
The aforementioned functions are now changed in the same manner, where CView is replaced by
CScrollView, as shown in bold in the following:
void CDiagramEngView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate(); // DiagramEng: CView changed to
CScrollView
// TODO: Add your specialized code here and/or call the base class
// DiagramEng (start)
InvalidateRect(NULL); // Invalidates the client area,
i.e. forces a redraw on the next WM_PAINT message.
Implementing a Scroll View 849
// DiagramEng (end)
}
The reader will recall from Chapter 27 that the call to CView::OnPrepareDC() (now
CScrollView::OnPrepareDC() as shown earlier in CDiagramEngView::OnPrepareDC())
was made prior to the conditional section concerning printing (IsPrinting()). This is neces-
sary since “CScrollView overrides OnPrepareDC() and in it calls CDC::SetMapMode()
to set the mapping mode and CDC::SetViewportOrg() to translate the viewport ori-
gin an amount that equals the horizontal and vertical scroll positions” [3]. In addition, “when
CScrollView::OnPrepareDC() returns, the device context’s mapping mode is set to the map-
ping mode specified when SetScrollSizes() is called” [4]; SetScrollSizes() is called in
CDiagramEngView::OnInitialUpdate() introduced in the following, defining the mapping
mode and the size of the scroll view. Thus, CScrollView::OnPrepareDC() should be called
prior to the conditional printing section, as shown earlier, for the correct mapping mode and viewport
origin to take effect for printing and print previewing purposes.
In addition, the developer should be aware that no changes from CView to CScrollView need to be
made to the functions: CDiagramEngDoc::DeleteOutputBlockView(), CDiagramEng
Doc::UpdateCommonOpsToolbar(), and CSystemModel::ValidateModel(). This is
the case since within all three functions, a call to GetNextView() is made to obtain a pointer-
to-CView, “pView”, and because CScrollView is derived from CView, i.e., as it is a kind of
CView, then this pointer (“pView”) is of the right base type and suffices for the view-based action
taken in these functions.
Implementing a Scroll View 851
Now the software user may load or draw a diagram on the palette, as shown in Figure 28.3, and use
the scroll bars to pan horizontally and vertically. However, interaction with the diagram entities is
inconsistent after scrolling of the diagram since no point conversions have yet been made to align
the device and logical coordinates coherently.
FIGURE 28.3 The placement of scroll bars on the view window allowing the user to pan horizontally and
vertically.
852 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
28.4.1.1 CDiagramEngView::OnContextMenu()
The changes that need to be made to the OnContextMenu() function are shown in bold in the
following. A device context, “dc”, of type CClientDC is required in order to call the DPtoLP()
conversion function to convert the CPoint, “ptClient”, variable from device coordinates to logical
coordinates. The conversion is required, as illustrated in Section 28.2, since if the user scrolls
the view, then the appropriate logical coordinate (xi,l, yi,l) needs to be obtained using the device
coordinate (xi,d, yi,d) and the amount scrolled in the horizontal (Δx) and vertical (Δy) directions,
i.e., (xi,l, yi,l) = (xi,d + Δx, yi,d + Δy), where, here, the ith point is that at which the right-mouse-button-
down event invokes the Context menu.
An implicit call to the CClientDC constructor is made, passing the “this” pointer, of type pointer-to-
CWnd, as an argument to construct a device context, “dc”, object that “accesses the client area of the CWnd
pointed to by pWnd” [2]. The MSDN Library resource documents indicate that the OnPrepareDC()
function is invoked before OnDraw() is called for screen display and before OnPrint() is called for
each page during printing or print preview [2]. Furthermore, the MSDN Library resource documents
state that the default implementation takes no action for screen display, but the function is overridden
in derived classes, e.g., CScrollView, to adjust attributes of the device context [2]. Here, given that the
CDiagramEngView class is now derived from the CScrollView base class, the OnPrepareDC() func-
tion is called to set up the device context, then the device point to logical point conversion function,
DPtoLP(), is called upon the “dc” object, where the address of the client CPoint coordinate, “ptClient”,
is passed as an argument and the point ultimately used to set the document class’s copy of the Context
menu point, “m_ptContextMenu”, via a call to SetContextMenuPoint().
28.4.1.2 CDiagramEngView::OnLButtonDblClk()
The changes to the OnLButtonDblClk() function are shown in bold in the following: initially
a device context object is created using the “this” pointer of type pointer-to-CWnd, the attributes
of the device context are set up via the call to OnPrepareDC(), and finally the CPoint “point”
object is converted from device units to logical units using the call to DPtoLP() prior to calling
the DoubleLeftClickBlock() function of the CDiagramEngDoc class, used to check whether
a model block is double clicked.
28.4.1.3 CDiagramEngView::OnLButtonDown()
The OnLButtonDown() function is invoked when the user presses the left mouse button,
where the arguments, “nFlags” and “point”, represent a combination of virtual key flags and
the CPoint, “point”, coordinate location of the cursor. The TrackItem() function performs its
own local processing of the “point” object and is discussed later; if no object is being tracked
(tracker_flag == 0), then the familiar CScrollView-related modifications are made as shown in
bold in the following. The device context is created using the “this” pointer, the device context
attributes are set up, and then the “point” object is converted from device units to logical units
to correctly record the points, “m_ptPrevPos” and “m_ptOrigin”, used for both drawing and
constructing a connection object.
// DiagramEng (end)
//CView::OnLButtonDown(nFlags, point);
CScrollView::OnLButtonDown(nFlags, point); // DiagramEng: CView
changed to CScrollView
}
28.4.1.4 CDiagramEngView::OnLButtonUp()
The OnLButtonUp() function is invoked upon releasing the left mouse button, whereupon the
CPoint, “point”, object is used to define the end point of a connection object if no model entity track-
ing is being performed (cf. OnLButtonDown()). The CScrollView-related changes are shown in
bold in the following, where the three familiar steps are taken, i.e., the device context is constructed,
and its attributes are defined and then used to convert the “point” object from device to logical units.
28.4.1.5 CDiagramEngView::OnMouseMove()
The final CDiagramEngView-based function to be altered is OnMouseMove(), which is used to
draw a connection object (“con_obj2”) from the starting point, “m_ptOrigin”, to the current cur-
sor point, “point”, and reverse the drawing color, effectively overwriting the previous connection
(“con_obj1”), defined by the starting and ending points, “m_ptOrigin” and “m_ptPrevPos”, respec-
tively. Similar modifications made to the previous functions, concerning the device context, need
to be performed here, with the addition of a new local CPoint variable, “ptLogical”, recording
the CPoint, “point”, coordinates converted from device to logical units; this is required since the
original “point” argument is finally used in the framework-provided call to the base class function
CScrollView::OnMouseMove() before the function returns. The changes required are shown
in bold in the following.
28.4.2.1 CDiagramEngDoc::TrackBlock()
The CDiagramEngDoc::TrackBlock() function shown in the following is initially called
from within the TrackItem() function of the same class to determine whether a block is being
independently tracked/moved. The main for loop iterates over the list of blocks, and the condi-
tional statement determines whether the block has been clicked; if so, the CRectTracker object,
“m_RectTracker”, is set up (SetRect()), the mouse cursor position is tracked (Track()), and the
final block position is set (CenterPoint() and SetBlockPosition()).
However, due to the presence of the CScrollView base class, a conversion needs to be made
between the screen device coordinates and the underlying document logical coordinates. Initially
a local CPoint object, “ptLogical”, is declared and initialized to that of the CPoint, “point”, object.
Then the device context “dc” is initially constructed using the pointer-to-CWnd, “pWnd”. However,
to call OnPrepareDC() from within the CDiagramEngDoc-based function, the pointer-to-CWnd,
“pWnd”, is first cast to a pointer-to-CScrollView, and then the device context attributes are set.
Thereafter, the cursor point is converted from device units to logical units in order to determine
the Euclidean distance between the cursor point (“ptLogical”) and the block position (“blk_posn”).
If the block is clicked, the CRectTracker, “m_RectTracker”, object’s top-left (“top_left”) and
bottom-right (“bottom_right”) coordinates need to be set based upon the logical-units-based block
position. Since the “m_RectTracker” concerns the screen device coordinates, then the “top_left”
and “bottom_right” coordinates need to be converted from the underlying logical coordinates to
device coordinates using LPtoDP(). Finally, after the block has been moved, the block position,
“blk_posn”, determined via CenterPoint() and hence in device units needs to be converted
from device units to logical units using DPtoLP().
CPoint blk_posn;
list<CBlock*>::iterator it_blk;
list<CBlock*> blk_list = GetSystemModel().GetBlockList();
// CScrollView-related change
CPoint ptLogical = point; // local point to record the point in
logical units
CClientDC dc(pWnd); // create a device context using the
pointer-to-CWnd pWnd
( (CScrollView *)pWnd)->OnPrepareDC(&dc); // prepare the device
context casting pWnd to ptr-to-CScrollView
dc.DPtoLP(&ptLogical); // use the dc to convert ptLogical from
device to logical units
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
Implementing a Scroll View 859
28.4.2.2 CDiagramEngDoc::TrackConnectionBendPoint()
The changes to be made to the TrackConnectionBendPoint() function, shown in bold in the
following, are very similar to those made for the TrackBlock() function earlier, i.e., (1) a local
CPoint object to record a logical point is initialized, (2) the device context is constructed, (3) the
attributes of the device context are set, (4) the cursor point in device units is converted to logi-
cal units (DPtoLP()), (5) the Euclidean distance between the logical point (“ptLogical”) and a
connection bend point (“bend_pt”) is determined, (6) the “m_RectTracker” bounding coordinates,
“top_left” and “bottom_right”, determined using the logical bend point position, are converted
from logical units to device units, and, finally, (7) after the bend point is moved, the bend point
position (CenterPoint()) is converted from device units back to the underlying document-based
logical units.
int CDiagramEngDoc::TrackConnectionBendPoint(CPoint point, CWnd *pWnd)
{
int delta = (int)(0.25*m_dDeltaLength); // integer increment
int tracker_flag = 0;
double disc_r = 0.1*m_dDeltaLength;
double dist;
CPoint bend_pt;
CPoint bottom_right;
CPoint top_left;
list<CConnection*>::iterator it_con;
list<CConnection*> con_list = GetSystemModel().GetConnectionList();
list<CPoint>::iterator it_pt;
// CScrollView-related change
CPoint ptLogical = point; // local point to record the point in
logical units
CClientDC dc(pWnd); // create a device context using the
pointer-to-CWnd pWnd.
( (CScrollView*)pWnd)->OnPrepareDC(&dc); // prepare the device
context casting pWnd to ptr-to-CScrollView
dc.DPtoLP(&ptLogical); // use the dc to convert ptLogical from
device to logical units
// Iterate through connection list
for(it_con = con_list.begin(); it_con != con_list.end(); it_con++)
{
// Get connection bend pts.
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
// Iterate through all bend pts. for this connection
for(it_pt = bend_pts_list.begin(); it_pt != bend_pts_list.end();
it_pt++)
{
bend_pt = *it_pt;
860 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
28.4.2.3 CDiagramEngDoc::TrackConnectionEndPoint()
The changes to be made to the TrackConnectionEndPoint() function are essentially the
same as those made for the TrackConnectionBendPoint() function, except both head and
Implementing a Scroll View 861
tail points need to be considered; the seven changes made earlier are implemented in the appropriate
locations as shown in bold in the following.
// CScrollView-related change
CPoint ptLogical = point; // local point to record the point in
logical units
CClientDC dc(pWnd); // create a device context using the
pointer-to-CWnd pWnd.
( (CScrollView*)pWnd)->OnPrepareDC(&dc); // prepare the device
context casting pWnd to ptr-to-CScrollView
dc.DPtoLP(&ptLogical); // use the dc to convert ptLogical from
device to logical units
// CScrollView-related change
dist_to_tail = sqrt(pow(tail_pt.x - ptLogical.x,2) +
pow(tail_pt.y - ptLogical.y,2) );
dist_to_head = sqrt(pow(head_pt.x - ptLogical.x,2) +
pow(head_pt.y - ptLogical.y,2) );
// CScrollView-related change
dc.LPtoDP(&top_left); // convert logical pt based on
logical block position to device pt
dc.LPtoDP(&bottom_right); // convert logical pt based on
logical block position to device pt
// Get the new tracker position and update the new connection
end pt position with the tracker position
tail_pt = m_RectTracker.m_rect.CenterPoint();
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should redraw.
break;
}
// CScrollView-related change
dc.LPtoDP(&top_left); // convert logical pt based on
logical block position to device pt
dc.LPtoDP(&bottom_right); // convert logical pt based on
logical block position to device pt
// Get the new tracker position and update the new connection
end pt position with the tracker position
head_pt = m_RectTracker.m_rect.CenterPoint();
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
864 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
CPoint head_pt;
CPoint tail_pt;
CRectTracker temp_tracker;
list<CBlock*> &blk_list = GetSystemModel().GetBlockList();
list<CBlock*>::iterator it_blk;
list<CConnection*>::iterator it_con;
list<CConnection*> &con_list = GetSystemModel().GetConnectionList();
list<CPoint>::iterator it_pt;
CClientDC dc(pWnd);
// CScrollView-related change
CPoint ptLogical; // logical point given change in scroll
position.
( (CScrollView*)pWnd)->OnPrepareDC(&dc); // prepare the dc given the
usage of the CScrollView base class
ptLogical = point; // make a copy of the device point
dc.DPtoLP(&ptLogical); // change point from device to logical coords
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having
been modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should
redraw.
}
}
// HEAD POINT
head_pt = (*it_con)->GetConnectionPointHead();
//item_tracked = DetermineTrackerDeltaPosition
(pWnd, point, tracker_init, delta_posn);
item_tracked = DetermineTrackerDeltaPosition
(pWnd, ptLogical, tracker_init, delta_posn);
}
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having
been modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should
redraw.
}
}
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews(NULL); // indicate that sys. should
redraw.
}
}// end for it_pt
}// end for it_con
// Set flags
m_iRubberBandCreated = 0; // end the rubber band state
SetKeyFlagTrack(0); // reset the key-based track flag
since tracking aborted
tracker_final = m_RectTracker.m_rect.CenterPoint();
// Check to make sure that connection is greater than the rqd. min.
connection length.
con_length = sqrt(pow( (ptFrom.x - ptTo.x),2) +
pow( (ptFrom.y - ptTo.y),2) );
if(con_length < min_length)
Implementing a Scroll View 871
{
…
}
else
{
…
}
return valid;
}
// Track block
tracker_flag = TrackBlock(point, pWnd);
if(tracker_flag != 0)
{
GetSystemModel().NullifyNegativeCoordinates();
// CScrollView-related change
return tracker_flag; // return since an item was tracked
}
else
{
tracker_flag = 0;
}
Finally, add a new public member function to the CSystemModel class with the prototype,
int CSystemModel::NullifyNegativeCoordinates(void), and edit the function as
shown. The NullifyNegativeCoordinates() function simply iterates through the list of
blocks and connection head, tail, and bend points and assigns any negative coordinate compo-
nents to zero.
int CSystemModel::NullifyNegativeCoordinates(void)
{
CPoint ptBlk; // block position
CPoint ptCon; // connection-based point
location
list<CBlock*>::iterator it_blk; // block iterator
list<CConnection*>::iterator it_con; // connection iterator
list<CPoint>::iterator it_pt; // point iterator
// TAIL POINT
ptCon = (*it_con)->GetConnectionPointTail();
if(ptCon.x < 0)
{
ptCon.x = 0;
}
if(ptCon.y < 0)
{
ptCon.y = 0;
}
(*it_con)->SetConnectionPointTail(ptCon);
// BEND POINTS
list<CPoint> &bend_pts_list = (*it_con)-
>GetConnectionBendPointsList();
return 0;
}
Figure 28.4 shows the result of trying to drag blocks and connections off the palette in the negative
coordinate directions; the relevant components are reset to zero. Note that block position coordi-
nates locate the center of the block.
FIGURE 28.4 Diagram blocks and connections whose relevant coordinates are nullified as a result of an
attempted placement in the negative x and y directions.
874 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 28.1
Menu Entry Object, ID, Caption, Prompts, and Settings
Object Property Setting
View/Auto Fit Diagram ID ID_VIEW_AUTO_FIT_DIAGRAM
Caption Auto Fit &Diagram
Prompts: status bar and tooltips Auto fit diagram to viewnAuto Fit Diagram
View/Zoom In ID ID_VIEW_ZOOM_IN
Caption Zoom &In
Prompts: status bar and tooltips Zoom in to detail\nZoom In
View/Zoom Out ID ID_VIEW_ZOOM_OUT
Caption Zoom &Out
Prompts: status bar and tooltips Zoom out of detail\nZoom Out
void CDiagramEngView::OnViewAutoFitDiagram()
{
// TODO: Add your command handler code here
// DiagramEng (start)
//AfxMessageBox(“\n CDiagramEngView::OnViewAutoFitDiagram()\n”,
MB_OK, 0);
// CScrollView-related change
int buffer = 20; // 20 pixel point fixed buffer size
CPoint ptExtreme = GetDocument()->GetSystemModel().
DetermineDiagramCoordinateExtrema(); // get extreme coords
SIZE szExtreme; // extreme size
Implementing a Scroll View 875
// DiagramEng (end)
}
The developer will notice the introduction of the member variable, “m_iAutoFitFlag”, which is
required to toggle between fitting the diagram to the view window and resetting the scroll view.
In addition, the member variable is used to update the user interface, i.e., the View menu itself, with a
tick-like check mark (✓), indicating that the Auto Fit Diagram option has been selected. Hence, add
a private integer member variable to the CDiagramEngView class, named “m_iAutoFitFlag”, and
initialize it in the CDiagramEngView constructor to zero. The first time the user selects Auto Fit
Diagram from the View menu, the member variable, “m_iAutoFitFlag”, has value zero, and the
viewport scale is fit to the physical viewing window. The subsequent call has the effect of reinstating
the scroll view through the call to SetScrollSizes().
To implement the check mark feature, perform the following.
// DiagramEng (start)
UINT nID = ID_VIEW_AUTO_FIT_DIAGRAM; // menu entry ID
876 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// DiagramEng (end)
}
The purpose of the function is to toggle the check mark reflecting the view-based toggling of the
OnViewAutoFitDiagram() function; if the SetScaleToFitSize() function is called in
the latter, then upon exiting the function, the “m_iAutoFitFlag” modulus two will not have value
zero, and hence, the Auto Fit Diagram entry under the View menu will have a check mark beside
it (1%2 ≠ 0).
To actually place a check mark next to the menu item, first, the address of the main menu, “pMenu”,
is retrieved via the pointer-to-CWinThread, “m_pMainWnd”, obtained through GetMainWnd(),
then a call to CheckMenuItem() is made upon the menu pointer, where the first argument, “nID”,
is the ID of the View menu entry, i.e., ID_VIEW_AUTO_FIT_DIAGRAM, and the second argu-
ment is a combination of flags: MF_CHECKED or MF_UNCHECKED with MF_BYCOMMAND
or MF_BYPOSITION [2].
An aside: initially a pointer to the main menu was retrieved, and then the address of the
submenu, “pSubMenu”, was obtained via a call to GetSubMenu(2), where the argument refers
to the submenu with index two, i.e., the View menu (File (0), Edit (1), and View (2)). However,
there appeared to be a problem when calling CheckMenuItem() upon the submenu pointer.
If the user creates a drawing in a child document that is not maximized to fit the main window,
then upon selecting and deselecting the Auto Fit Diagram entry, the check mark would cor-
rectly appear and disappear, respectively, next to the View menu entry. However, if the child
document is maximized to fit the parent window, then the state of the menu item is persis-
tently that prior to maximizing the window regardless of whether the Auto Fit Diagram state is
toggled. This is inconsistent, as indicated in the comments in the code earlier, and hence, the
main menu pointer, “pMenu”, rather than the submenu pointer, “pSubMenu”, was used to call
CheckMenuItem() instead.
Figure 28.5 shows the before and after states of fitting a diagram to the window extents: (a) the
state equation diagram that is larger than the window size with scroll bars visible and (b) the same
diagram scaled to fit the window whereupon the scroll bars automatically disappear.
Implementing a Scroll View 877
(a)
(b)
FIGURE 28.5 Scaling of the linear state equation diagram: (a) unscaled view requiring a greater window
size and (b) scaled view to automatically fit the current window.
set a scale factor variable, “scale_factor”, whose value and reciprocal are used by the zoom in and
zoom out functions, respectively, to scale the system model’s underlying diagram entity coordinates.
void CDiagramEngView::OnViewZoomIn()
{
// TODO: Add your command handler code here
// DiagramEng (start)
double length; // used to scale m_dDeltaLength
double scale_factor = 1.1; // scale factor: inverse of that used in
OnViewZoomOut()
// DiagramEng (end)
}
void CDiagramEngView::OnViewZoomOut()
{
// TODO: Add your command handler code here
// DiagramEng (start)
double length; // used to scale m_dDeltaLength
double scale_factor = 1/1.1; // scale factor: inverse of that used
in OnViewZoomIn()
The developer will notice that the scale factor, “scale_factor”, is first set and then used to scale
the “m_dDeltaLength” variable of the CDiagramEngDoc class prior to being passed to the
ScaleSystemModel() function of the CSystemModel class. Hence, add a public member func-
tion to the CDiagramEngDoc class with the following prototype, void CDiagramEngDoc::
SetDeltaLength(double length), and edit it as shown.
// Block height
height = (*it_blk)->GetBlockShape().GetBlockHeight();
height = scale_factor*height;
(*it_blk)->GetBlockShape().SetBlockHeight(height);
// Block posn
ptBlk = (*it_blk)->GetBlockPosition();
ptBlk.x = scale_factor*ptBlk.x;
ptBlk.y = scale_factor*ptBlk.y;
(*it_blk)->SetBlockPosition(ptBlk);
}
}
(a)
(b)
FIGURE 28.6 Zooming in and out of a diagram representing an ordinary differential equation: (a) zooming
in increases the diagram size and (b) zooming out decreases the diagram size.
be added to the CDiagramEngDoc class with the following prototype: void CDiagramEngDoc::
ResetDefaultGeometry(void). Edit the function as shown to determine the scale factor given
the default value (50.0) and current value of “m_dDeltaLength” and call ScaleSystemModel()
to reset all diagram entity components.
void CDiagramEngDoc::ResetDefaultGeometry()
{
double scale_factor;
This function may be called explicitly via a View menu entry and indirectly via the
CDiagramEngDoc::OnFileSave() and CDiagramEngDoc::OnFileSaveAs() functions
prior to serialization. Table 28.2 shows the additional entry and its properties and settings that
should be placed beneath the Zoom Out item on the View menu.
Add an event-handler function to the CDiagramEngView class, for the COMMAND event mes-
sage associated with the ID_VIEW_RESET_DIAGRAM menu entry, and edit it as shown in the
following to call ResetDefaultGeometry() upon a pointer-to-CDiagramEngDoc returned by
GetDocument().
void CDiagramEngView::OnViewResetDiagram()
{
// TODO: Add your command handler code here
// DiagramEng (start)
// DiagramEng (end)
}
The developer should be warned, however, that since CPoint arguments involve integer coordinate
values and numerous zooming in and out action scale diagram entities using a double type scale
factor, there will inevitably be a loss of accuracy, and the original diagram geometry may not be
able to be perfectly recovered through a resetting operation.
In addition, prior to any serialization, the diagram can be rescaled to its default
“m_dDeltaLength”-based geometry. This is done such that upon restoring a previously saved
diagram and adding new diagram entities to it, all blocks and connections on the palette will
have default uniform size. Hence, augment the OnFileSave() and OnFileSaveAs()
TABLE 28.2
Menu Entry Object, ID, Caption, Prompts, and Settings
Object Property Setting
View/Reset Diagram ID ID_VIEW_RESET_DIAGRAM
Caption R&eset Diagram
Prompts: status bar and tooltips Reset diagram to default scale\nReset Diagram
Implementing a Scroll View 883
The reason why the scaled diagram using the zooming functions is not serialized is that the
View menu entries concern, primarily, viewing of a model and not the editing of its underlying
data attributes.
28.9 SUMMARY
The implementation of a scroll view requires deriving the original CView-based CDiagramEngView
class from CScrollView and changing CDiagramEngView- and CDiagramEngDoc-based
functions; the CDiagramEngView::OnInitalUpdate() function, wherein a call to
CScrollView::SetScrollSizes() is made, is used to set scrolling characteristics.
However, since a scroll view is in place, a conversion is required to be made between device
points and logical points DPtoLP() and vice versa, LPtoDP(), and the points are related
as follows, (xi,l, yi,l) = (xi,d + Δx, yi,d + Δy), where the change in scroll position, (Δx, Δy), may
be obtained using CScrollView::GetScrollPosition(). The functions that required
explicit point conversions are the CDiagramEngView-based methods, OnContextMenu(),
OnLButtonDblClk(), OnLButtonDown(), OnLButtonUp(), and OnMouseMove(); and
the CDiagramEngDoc-based methods, TrackBlock(), TrackConnectionBendPoint(),
TrackConnectionEndPoint(), TrackMultipleItems(), and DetermineTracker
DeltaPosition(). As a result of the scroll-view-based changes, the user could place blocks
off the palette, and hence, a CSystemModel::NullifyNegativeCoordinates() func-
tion was introduced to reset negative coordinate values to zero.
The automatic fitting of the viewport scale to the physical window is performed using the
CScrollView::SetScaleToFitSize() function, and a member variable, “m_iAutoFitFlag”,
was introduced to toggle between the original and the scaled views and set a check mark
(CheckMenuItem()) next to the “Auto Fit Diagram” View menu entry.
Zooming in and out of a view displaying the model diagram was performed using the
CDiagramEngView event-handler functions OnViewZoomIn() and OnViewZoomOut(),
and the CSystemModel::ScaleSystemModel() function used to scale the underlying dia-
gram entities’ geometry, including that of blocks, connection head, tail, and bend points, and the
CDiagramEngDoc member variable, “m_dDeltaLength”.
Finally, to complete the View menu, a Reset Diagram entry and its OnViewResetDiagram()
event-handler function were added to call the ResetDefaultGeometry() method of the
CDiagramEngDoc class to allow the user to reset the default diagram geometry; this function is
also called by the OnFileSave() and OnFileSaveAs() methods of the same class prior to
serialization.
REFERENCES
1. Microsoft Support, Logical and Physical Coordinate Relationship, http://support.microsoft.com/kb/74044,
(accessed July 31, 2010).
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
3. Brinkster, Scroll Views, http://aclacl.brinkster.net/MFC/ch10b.htm, (accessed July 31, 2010).
4. Prosise, J., Programming Windows 95 with MFC, Part VIII: Printing and print previewing, Microsoft
Systems Journal, 11(4), 39–58, April, 1996.
29 Edit Menu
29.1 INTRODUCTION
The Edit menu currently has the entries, Undo, Cut, Copy, Paste, Delete Grouped Items, Select
All, and Add Multiple Blocks, as shown in Table 29.1: functionality exists for Delete Grouped
Items and Add Multiple Blocks, but is absent for the remaining items. Here, all event-handler
functions and related class member methods will be added for the Edit menu to allow the user to
easily and efficiently perform the most common editing actions typically available in Windows-
based applications. The CDiagramEngDoc-based event-handler functions, OnEditUndo(),
OnEditCut(), OnEditCopy(), and OnEditPaste(), listed in Table 29.1, are intended, but
do not currently exist in the project, and OnEditSelectAll() exists but currently has no
working functionality.
CDiagramEngDoc::CDiagramEngDoc()
{
// TODO: add one-time construction code here
// DiagramEng (start)
885
886 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 29.1
Menu Entry Objects, IDs, Captions, Prompts, Settings, and Event-Handler Functions
Object Property Setting Event-Handler Function
Edit/Undo ID ID_EDIT_UNDO OnEditUndo()
Caption &Undo\tCtrl+Z
Prompts Undo the last action\nUndo
Edit/Cut ID ID_EDIT_CUT OnEditCut()
Caption Cu&t\tCtrl+X
Prompts Cut the selection and put it on the
Clipboard\nCut
Edit/Copy ID ID_EDIT_COPY OnEditCopy()
Caption &Copy\tCtrl+C
Prompts Copy the selection and put it on the
Clipboard\nCopy
Edit/Paste ID ID_EDIT_PASTE OnEditPaste()
Caption &Paste\tCtrl+V
Prompts Insert Clipboard contents\nPaste
Edit/Delete ID ID_EDIT_DELETE OnEditDeleteDeleteGroupedItems()
Grouped Caption &Delete
Items Prompts Delete the selection\nDelete
Edit/Select ID ID_EDIT_SELECTALL OnEditSelectAll()
All Caption Select &All
Prompts Selection of all content\nSelect All
Edit/Add ID ID_EDIT_ADD_MULTI_BLOCKS OnEditAddMultipleBlocks()
Multiple Caption Add &Multiple Blocks
Blocks Prompts Add multiple blocks\nAdd Multiple
Blocks
m_iKeyFlagTrack = 0;
m_iFineMoveFlag = 0;
m_iRubberBandCreated = 0;
m_iSelectAll = 0;
m_dDeltaLength = 50.0;
m_strFilePath = “”;
// DiagramEng (end)
}
void CDiagramEngDoc::OnEditSelectAll()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int buffer = 20; // 20 point buffer
CPoint ptExtrema; // extreme coordinates of diagram entities
of system model
CPoint ptMinima; // minimum coordinates of diagram entities
of system model
// Get the position of the first view in the list of views for the
current doc.
POSITION posn = GetFirstViewPosition();
// CScrollView-related impl.
((CScrollView*)pView)->OnPrepareDC(&dc); // prepare device context
casting pView to ptr-to-CScrollView
// Set flags.
m_iRubberBandCreated = 1; // rubber band state is
active
m_iSelectAll = 1; // select all state is active
}
// DiagramEng (end)
}
The developer will notice that usually to construct a device context object of type CClientDC()
that a pointer-to-CWnd is required. However, as OnEditSelectAll() is a CDiagramEngDoc
member method, the “this” pointer, e.g., cannot be used to set up the device context as it could be
in a CDiagramEngView member function. Hence, a pointer-to-CView is obtained, and because
CView is derived from CWnd and thus a type of CWnd, it may be used to construct the device con-
text. Then on preparing the device context using OnPrepareDC(), the pointer-to-CView needs to
be cast as a pointer-to-CScrollView, since CDiagramEngView is now derived from CScrollView
(see Chapter 28).
The developer will have noticed the call to the CSystemModel function
DetermineDiagramCoordinateExtrema(), introduced in Chapter 27, to determine the
CPoint coordinate extrema of diagram entities on the palette. Now a similar function is to be intro-
duced to determine the CPoint coordinate minima. Hence, add a public member function to the
CSystemModel class with the following prototype, CPoint CSystemModel::Determine
DiagramCoordinateMinima(void), and edit as shown.
CPoint CSystemModel::DetermineDiagramCoordinateMinima()
{
int pt_rad = 5; // radius of a bend point
(assume fixed and small)
int int_max = 2147483647; // INT_MAX = 2147483647.
double blk_height; // block width
double blk_width; // block height
CPoint ptBend; // bend point
CPoint ptBlkPosn; // block position
CPoint ptHead; // connection head point
CPoint ptMin; // pseudo minimum point
(minimum coords may be from diff. elements)
CPoint ptTail; // connection tail point
CPoint ptTemp; // temporary point
list<CBlock*>::iterator it_blk; // block iterator
list<CConnection*>::iterator it_con; // connection iterator
list<CPoint>::iterator it_pt; // bend points iterator
blk_width = (*it_blk)->GetBlockShape().GetBlockWidth();
ptTemp.x = ptBlkPosn.x - int(0.5*blk_width);
ptTemp.y = ptBlkPosn.y - int(0.5*blk_height);
return ptMin;
}
The function iterates through the list of blocks, connections, and connection-based bend points;
determines the minimum coordinates; and returns the coordinate minima as a CPoint object:
in this regard, the actual CPoint object is a pseudominimum point, since its individual coor-
dinate components may come from two different diagram entities (as is the case with the
DetermineDiagramCoordinateExtrema() function).
int CDiagramEngDoc::ResetSelectAll()
{
int buffer = 20; // 20 point buffer
CPoint ptExtrema; // extreme coordinates of diagram entities of
system model
CPoint ptMinima; // minimum coordinates of diagram entities of
system model
// Reset flags
if(m_iSelectAll == 1)
{
m_iRubberBandCreated = 0;
m_iSelectAll = 0;
}
// Get the position of the first view in the list of views for the
current doc.
POSITION posn = GetFirstViewPosition();
// CScrollView-related impl.
((CScrollView*)pView)->OnPrepareDC(&dc); // prepare device context
casting pView to ptr-to-CScrollView
// DiagramEng (start)
// DiagramEng (end)
//CView::OnLButtonDown(nFlags, point);
CScrollView::OnLButtonDown(nFlags, point); // DiagramEng: CView
changed to CScrollView
}
The developer will have noticed that the member variable “m_iSelectAll” is returned using
the accessor function GetSelectAllFlag(). Hence, add a public constant member func-
tion to the CDiagramEngDoc class with the following prototype, int CDiagramEngDoc::
GetSelectAllFlag(void) const, and edit the function as shown to return the member variable:
Finally, given that the CRectTracker object, “m_RectTracker”, may be set up in both the
CDiagramEngDoc functions, OnEditSelectAll() and TrackMultipleItems(), to avoid
a conflict in the context of its use, upon clicking the Track Multiple Items button on the Common
Edit Menu 893
FIGURE 29.1 Selecting the complete diagram automatically, as shown by the gray rectangle, by choosing
Select All from the Edit menu.
Operations toolbar, the state of the select-all action (if active) needs to be reset. Hence, add the code
shown in bold to the CDiagramEngDoc::OnInitTrackMultipleItems() function, to turn
off the select-all state prior to performing any diagram entity movement subsequently done via
CDiagramEngDoc::TrackItem().
void CDiagramEngDoc::OnInitTrackMultipleItems()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int key_flag = 1; // 0 => can’t call TrackMultipleItems(),
1 => can call TrackMultipleItems.
// DiagramEng (end)
}
Now if the user runs the application and chooses Select All from the Edit menu, the entire diagram
is selected as shown in Figure 29.1. If the user performs a left-button-down event or selects the Track
Multiple Items button, then the select-all state is deactivated.
operations. The general steps to add the edit-based functionality to the project are as listed, where
specific details follow:
1. void CDiagramEngDoc::OnEditCut(void)
2. void CDiagramEngDoc::OnEditCopy(void)
3. void CDiagramEngDoc::OnEditPaste(void)
void CDiagramEngDoc::OnEditCut()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int intersected = 0;
int intersected_head = 0;
int intersected_tail = 0;
double blk_width;
double delta = 0.25*m_dDeltaLength;
//CBlock *pBlock = NULL;
//CConnection *pCon = NULL;
CPoint blk_posn;
CPoint head_pt;
CPoint tail_pt;
CRectTracker temp_tracker;
list<CBlock*> &blk_list = m_SystemModel.GetBlockList();
//list<CBlock*> &blk_list_cb = m_Clipboard.GetBlockList();
list<CBlock*>::iterator it_blk;
list<CConnection*> &con_list = m_SystemModel.GetConnectionList();
//list<CConnection*> &con_list_cb = m_Clipboard.GetConnectionList();
list<CConnection*>::iterator it_con;
// -- IF A RUBBER BAND HAS BEEN CREATED
if(m_iRubberBandCreated == 1)
{
// Create a temp tracker since m_RectTracker will have its coords
updated when the rubber band is moved.
temp_tracker = m_RectTracker;
// Delete block
delete *it_blk; // delete block pointed to by it_blk
it_blk = blk_list.erase(it_blk); // delete element at
offset it_blk in list (that held the block)
}
else // only increment the iterator if there were no
intersection
{
it_blk++;
}
}// end for it_blk
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
UpdateAllViews (NULL); // indicate that sys. should redraw.
// DiagramEng (end)
}
void CDiagramEngDoc::OnEditCopy()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int intersected = 0;
int intersected_head = 0;
int intersected_tail = 0;
double blk_width;
double delta = 0.25*m_dDeltaLength;
//CBlock *pBlock = NULL;
//CConnection *pCon = NULL;
CPoint blk_posn;
CPoint head_pt;
CPoint tail_pt;
CRectTracker temp_tracker;
list<CBlock*> &blk_list = m_SystemModel.GetBlockList();
//list<CBlock*> &blk_list_cb = m_Clipboard.GetBlockList();
Edit Menu 897
list<CBlock*>::iterator it_blk;
list<CConnection*> &con_list = m_SystemModel.GetConnectionList();
//list<CConnection*> &con_list_cb = m_Clipboard.GetConnectionList();
list<CConnection*>::iterator it_con;
}
}// end for it_blk
void CDiagramEngDoc::OnEditPaste()
{
// TODO: Add your command handler code here
// DiagramEng (start)
list<CBlock*> &blk_list = m_SystemModel.GetBlockList();
//list<CBlock*> &blk_list_cb = m_Clipboard.GetBlockList();
list<CBlock*>::iterator it_blk;
list<CConnection*> &con_list = m_SystemModel.GetConnectionList();
//list<CConnection*> &con_list_cb = m_Clipboard.
GetConnectionList();
list<CConnection*>::iterator it_con;
// Add clipboard block list contents to end of system model block
list
// merge() merges the arg list into the list upon which merge() is
called leaving, the arg list empty.
//blk_list.merge(blk_list_cb);
// Add clipboard connection list contents to end of system model
connection list
// merge() merges the arg list into the list upon which merge() is
called, leaving the arg list empty.
//con_list.merge(con_list_cb);
// Snap connection end points to port or bend point
SnapConnectionEndPointsAfterInitialConstruction();
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
UpdateAllViews (NULL); // indicate that sys. should redraw.
// DiagramEng (end)
}
The CDiagramEngDoc::OnEditPaste() function gets the block list, “blk_list_cb”, and con-
nection list, “con_list_cb”, from the clipboard object, “m_Clipboard” (to be added later), and merges
these lists with the corresponding system-model-based lists, “blk_list and “con_list”, respectively.
Edit Menu 899
The “merge” operation for the lists used earlier merges the list argument with “*this”, i.e., the object
upon which merge is called, and empties the argument list [1]: i.e., the clipboard lists are merged
with the system model lists and the former lists emptied. After the merge is complete, the function
SnapConnectionEndPointsAfterInitialConstruction() is called, since in the copy
construction procedure (to be added), the CConnection-based pointer members, “m_pRefFromPort”,
“m_pRefToPort”, and “m_pRefFromPoint”, are intentionally set to NULL (see the following text),
and hence, these need to be set after the underlying block and connection lists have been established.
These pointers are set to NULL for two reasons: (1) if a copy (using Edit/Copy) of a connection is
being made and the connection is attached to a bend point or a port, then the copy cannot refer to the
same port in the diagram from which it was copy-constructed, and (2) if a connection is being cut
(using Edit/Cut) and an associated bend point or port is not selected (using the tracking rectangle) in
the cut operation, then the pointers should not refer to any port or point but rather be NULL.
The class member methods may be added either through the Workspace pane or manually, as
shown in the “Clipboard.cpp” source file. The developer will need to include both the “Block.h”
and “Signal.h” header files for CBlock and CConnection class declarations, respectively,
as shown.
#include “stdafx.h”
#include “DiagramEng.h”
#include “Block.h” // rqd. for CBlock objs.
#include “Signal.h” // rqd. for CConnection objs.
#include “Clipboard.h”
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CClipboard::CClipboard()
{
// Do nothing for now
}
CClipboard::∼CClipboard()
{
// Delete block list
DeleteBlockList();
list<CBlock*> &CClipboard::GetBlockList()
{
return m_lstBlock;
}
list<CConnection*> &CClipboard::GetConnectionList()
{
// Return the connection list by reference
return m_lstConnection;
}
void CClipboard::DeleteBlockList()
{
list<CBlock*>::iterator it_blk;
{
delete (*it_blk); // delete what it_blk is pointing to:
i.e. deref the it_blk ptr and delete the ptr-to-CBlock.
}
m_lstBlock.clear();
}
void CClipboard::DeleteConnectionList()
{
list<CConnection*>::iterator it_con;
// Delete connection list
for(it_con = m_lstConnection.begin(); it_con != m_lstConnection.
end(); it_con++)
{
delete (*it_con); // delete what it_conn is pointing to:
i.e. deref the it_conn ptr and delete the ptr-to-CConnection.
}
m_lstConnection.clear();
}
The class constructor function does nothing at present but may be added to later. The
class destructor deletes the block and connection lists using DeleteBlockList() and
DeleteConnectionList(), respectively: these functions both iterate through their respective
member lists, dereferencing the list iterator and deleting the pointer, implicitly calling the appropri-
ate class destructors to destroy their objects. Thereafter, the lists are cleared using clear(). The
accessor functions GetBlockList() and GetConnectionList() simply return, by reference,
the member lists, “m_lstBlock” and “m_lstConnection”, respectively. The behavior of the accessor
functions and list deletion methods is identical to those used for the CSystemModel class.
Finally, add the new CClipboard object named, “m_Clipboard”, to the CDiagramEngDoc class
declaration. No special construction is required for this object in the CDiagramEngDoc class con-
structor, and hence, the latter need not be changed. In addition, the “Clipboard.h” header file should
be included at the top of the “DiagramEngDoc.h” header file, since the CDiagramEngDoc class
contains the CClipboard object, “m_Clipboard”.
The developer will notice that this virtual base CBlock class version of the CopyBlockData()
function returns a NULL pointer: the derived block class implementations will return a pointer-
to-CBlock, i.e., the address of a new block object constructed using the appropriate derived
block class copy constructor. But since only derived blocks represent complete blocks, the base
CBlock::CopyBlockData() method should return a NULL pointer.
The base CBlock class copy constructor may be added with prototype, CBlock::
CBlock(constCBlock &blk), as shown, to invoke the CBlockShape copy constructor through
initialization and then extract the variable values from the reference-to-constant-CBlock variable,
“blk”, and assign these to the underlying member variables of the block under construction.
CBlock::CBlock(const CBlock &blk):
m_BlockShape(blk.m_BlockShape) // call the contained object’s copy
constructor
{
m_iPenColor = blk.GetPenColor();
m_iPenWidth = blk.GetPenWidth();
m_pParentSystemModel = blk.GetParentSystemModel();
m_pSubSystemModel = blk.GetSubSystemModel();
m_ptBlockPosition = blk.GetBlockPosition();
m_strBlockName = blk.GetBlockName();
}
The developer will have noticed the call to get a pointer to a possibly contained subsystem model,
“m_pSubSystemModel”. Hence add a public member function to the CBlock class with the follow-
ing prototype, CSystemModel *CBlock::GetSubSystemModel(void) const, and edit
the function as shown:
CSystemModel *CBlock::GetSubSystemModel() const
{
return m_pSubSystemModel;
}
The accessor functions used in the CBlock copy constructor provided earlier are all constant mem-
ber functions: this is consistent with the incoming argument “blk” being a reference-to-a-constant-
CBlock object.
In the work that follows, the derived block classes are required to make a copy of the vec-
tors of input and/or output ports. At present, the functions GetVectorOfInputPorts() and
GetVectorOfOutputPorts() return, as a reference-to-vector<CPort*>, the underlying mem-
bers “m_vecInputPorts” and “m_vecOutputPorts”, respectively. This is useful if the underlying
members are to be acted upon, or changed, in which case these accessor functions should not be
constant. However, for the purpose of derived (CBlock) type copy construction and the need for con-
structor arguments to be constant, two new member functions need to be introduced to the CBlock
class that return the base address of these vectors: i.e., a pointer-to-a-constant-vector<CPort*> is
returned. Hence, add two constant public member functions to the CBlock class with the following
prototypes, and edit the functions as shown.
The keyword “const” is required for the function itself and also the pointer being returned, since to
satisfy the constant state, what is being pointed to (as a result of using the “address of” (&) operator),
i.e., the vector of CPort pointers (vector<CPort*>), must also be constant.
The four constant accessor methods are called upon the incoming argument “bs” to initialize the
underlying CBlockShape object.
The developer should be aware that the reference-to-CBlock, “blk”, is not constant, since the mem-
ber variable, “m_rRefToBlock”, being initialized with “blk” is itself not a constant reference-to-
CBlock. However, all the CPort accessor methods are constant.
The developer will notice that in order to preserve the constant nature of the incoming “con”
argument, the connection-based bend points list, “m_lstBendPoints”, must be retrieved by value
rather than by reference. Previously, the GetConnectionBendPointsList() function was
used to return a reference-to-list<CPoint>: here, however, a copy of the list should be returned (by
value). Hence, add a constant public member function to the CConnection class with the following
prototype, list<CPoint> CConnection::GetConnectionBendPointsListCopy()
const, and edit the function as shown, to return a copy of the member list.
and edit it as shown. Note the call to the base CBlock class copy constructor in the initialization list
(shown in bold): this allows the base member variables to be constructed prior to construction of the
derived member variables.
// Port Construction
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for const
vector of CPort ptrs.
The developer will have noticed the call to a new method named, GetStringConstValue(): this
function returns the CString, “m_strConstValue”, member which retains the CString equivalent of the
double constant matrix, “m_dConstMatrix”. The double constant matrix and its dimensions are then
reconstructed based upon the CString member through the call to ConvertStringToDouble().
Hence, add a new constant public member function to the CConstantBlock class with the proto-
type, CString CConstantBlock::GetStringConstValue(void) const, and edit it
as shown.
In addition, for the input argument to the CConstantBlock copy constructor to be a reference
to a constant CBlock, the CBlock::GetVectorOfOutputPortsCopy() function, intro-
duced earlier, is called to return a pointer to a constant vector of pointers-to-CPort (const
vector<CPort*> *).
Furthermore, when iterating through the vector, the iterator, “it_port”, must be a constant itera-
tor, “const_iterator”, to be consistent with the const vector of pointers-to-CPort. To start the for
loop, the pointer, “p_vec_output_ports”, is dereferenced to access the first element of the vector,
i.e., (*p_vec_output_ ports).begin(). In the body of the loop, when the CPort copy con-
structor is called, the iterator is dereferenced twice (**it_port): once to gain access to the CPort-
pointer and a second time to dereference the CPort-pointer to gain access to the CPort object itself.
This object is then passed as an argument to the constructor, where upon entry in the latter, is a
reference to a constant CPort object (as shown in Section 29.3.3.3).
906 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
CBlock *CConstantBlock::CopyBlockData(void)
{
//AfxMessageBox(“\n CConstantBlock::CopyBlockData()\n”, MB_OK, 0);
return pBlock;
}
(a)
(b)
FIGURE 29.2 A copy and paste action of CConstantBlock and CConnection objects: (a) a selection of items
to be edited and (b) items pasted after moving the original selection to the right.
Edit Menu 907
// Port Construction
const vector<CPort*> *p_vec_input_ports = blk.
GetVectorOfInputPortsCopy();
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for
const vector of CPort ptrs.
The developer will notice that in the constructor, the derivative method is obtained by an accessor
function. Hence, add a public constant member function to the CDerivativeBlock class with the
908 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
CBlock* CDerivativeBlock::CopyBlockData()
{
// Create a new CDerivativeBlock
CBlock *pBlock = new CDerivativeBlock(*this);
return pBlock;
}
// Port Construction
const vector<CPort*> *p_vec_input_ports = blk.
GetVectorOfInputPortsCopy();
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for
const vector of CPort ptrs.
{
// Deref it_port to get ptr-to-CPort and then deref the
ptr-to-CPort to get CPort.
CPort *p_output_port = new CPort(**it_port, *this); // CPort copy
constructor
GetVectorOfOutputPorts().push_back(p_output_port); // add the
new port
}
}
Four constant accessor functions, with the prototypes given, need to be added to the CDivideBlock
class to retrieve the member variables: edit these functions as shown.
1. int CDivideBlock::GetNMultiplyInputs(void) const
2. int CDivideBlock::GetNDivideInputs(void) const
3. int CDivideBlock::GetMultType(void) const
4. CString CDivideBlock::GetStringInitialSignal(void) const
int CDivideBlock::GetNMultiplyInputs() const
{
return m_iNMultiplyInputs;
}
int CDivideBlock::GetNDivideInputs() const
{
return m_iNDivideInputs;
}
int CDivideBlock::GetMultType() const
{
return m_iMultType;
}
CString CDivideBlock::GetStringInitialSignal() const
{
return m_strInitSignal;
}
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CDivideBlock::CopyBlockData(void), may be added as shown.
CBlock* CDivideBlock::CopyBlockData()
{
// Create a new CDivideBlock
CBlock *pBlock = new CDivideBlock(*this);
return pBlock;
}
// Port Construction
const vector<CPort*> *p_vec_input_ports = blk.
GetVectorOfInputPortsCopy();
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for
const vector of CPort ptrs.
for(it_port = (*p_vec_input_ports).begin(); it_port
!= (*p_vec_input_ports).end(); it_port++)
{
// Deref it_port to get ptr-to-CPort and then deref the ptr-to-
CPort to get CPort.
CPort *p_input_port = new CPort(**it_port, *this); // CPort copy
constructor
GetVectorOfInputPorts().push_back(p_input_port); // add the
new port
}
for(it_port = (*p_vec_output_ports).begin(); it_port
!= (*p_vec_output_ports).end(); it_port++)
{
// Deref it_port to get ptr-to-CPort and then deref the
ptr-to-CPort to get CPort.
CPort *p_output_port = new CPort(**it_port, *this); // CPort copy
constructor
GetVectorOfOutputPorts().push_back(p_output_port); // add the
new port
}
}
The developer will have noticed the introduction of two accessor functions to retrieve the gain type,
“m_iGainType”, and CString gain value, “m_strGainValue”, which is then converted into a double
matrix. Hence, add the following public constant member functions, and edit them as shown.
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CGainBlock::CopyBlockData(void), may be added as shown:
CBlock* CGainBlock::CopyBlockData()
{
// Create a new CGainBlock
CBlock *pBlock = new CGainBlock(*this);
return pBlock;
}
Edit Menu 911
// Port Construction
const vector<CPort*> *p_vec_input_ports = blk.
GetVectorOfInputPortsCopy();
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for
const vector of CPort ptrs.
The developer will have noticed the introduction of two new public constant accessor functions to
return the initial condition vector as a CString, “m_strICVector” and the length of the corresponding
912 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
double vector, “m_iLength”. Hence, add the public constant member functions, with the provided
prototypes, and edit them as shown.
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CIntegratorBlock::CopyBlockData(void), may be added as shown:
CBlock* CIntegratorBlock::CopyBlockData()
{
// Create a new CIntegratorBlock
CBlock *pBlock = new CIntegratorBlock(*this);
return pBlock;
}
// Port Construction
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for
const vector of CPort ptrs.
The developer will have noticed the introduction of four new public constant member functions to
access private member data. Hence, add these accessor functions with the declarations and defini-
tions provided as follows.
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CLinearFnBlock::CopyBlockData(void), may be added as shown:
CBlock* CDivideBlock::CopyBlockData()
{
// Create a new CDivideBlock
CBlock *pBlock = new CDivideBlock(*this);
return pBlock;
}
m_iNotation = blk.GetNotation();
m_iTimePtDisplay = blk.GetTimePtDisplay();
914 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
blk.GetDataDimensions(m_iNrows, m_iNcols);
matrix = blk.GetOutputMatrix();
// MEMORY NEW
m_dOutputMatrix = new double *[m_iNrows];
for(i=0; i<m_iNrows; i++)
{
m_dOutputMatrix[i] = new double[m_iNcols];
}
// Port Construction
const vector<CPort*> *p_vec_input_ports = blk.
GetVectorOfInputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for const
vector of CPort ptrs.
The developer will notice the four public constant accessor methods that were introduced in the
project at an earlier developmental stage. The constant nature of the GetOutputMatrix() func-
tion implies that the pointer being returned, i.e., “m_dOutputMatrix” (of the incoming argument
“blk”), is constant, although the data pointed to by the pointer may change. After the data and their
dimensions are retrieved, new memory is allocated and the data values assigned to the underlying
member variable, “m_dOutputMatrix”.
Finally, the public virtual CopyBlockData() method with the prototype, virtual
CBlock *COutputBlock::CopyBlockData(void), may be added as shown.
CBlock* COutputBlock::CopyBlockData()
{
// Create a new COutputBlock
CBlock *pBlock = new COutputBlock(*this);
return pBlock;
}
Edit Menu 915
The developer will have noticed the introduction of five new public constant member functions to
access the private member data of the CSignalGeneratorBlock class. Hence, add the accessor meth-
ods with the declarations and definitions provided as follows.
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CSignalGeneratorBlock::CopyBlockData(void), may be added as shown.
CBlock* CSignalGeneratorBlock::CopyBlockData()
{
// Create a new CSignalGeneratorBlock
CBlock *pBlock = new CSignalGeneratorBlock(*this);
return pBlock;
}
// Port Construction
const vector<CPort*> *p_vec_input_ports = blk.
GetVectorOfInputPortsCopy();
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for
const vector of CPort ptrs.
The developer will have noticed the two new public constant methods to retrieve the CString mem-
ber variables. Hence, add the accessor methods with the declarations and definitions provided as
follows.
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CSubsystemBlock::CopyBlockData(void), may be added as shown:
CBlock* CSubsystemBlock::CopyBlockData()
{
// Create a new CSubsystemBlock
CBlock *pBlock = new CSubsystemBlock(*this);
return pBlock;
}
// Port Construction
{
// Deref it_port to get ptr-to-CPort and then deref the
ptr-to-CPort to get CPort.
CPort *p_output_port = new CPort(**it_port, *this); // CPort copy
constructor
GetVectorOfOutputPorts().push_back(p_output_port); // add the
new port
}
}
The developer will have noticed the accessor method to retrieve the input port name. Hence, add
a public constant accessor method to the CSubsystemInBlock class with the following prototype,
CString CSubsystemInBlock::GetInputPortName() const, and edit the function as
shown.
CString CSubsystemInBlock::GetInputPortName() const
{
return m_strInputPortName;
}
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CSubsystemInBlock::CopyBlockData(void), may be added as shown:
CBlock* CSubsystemInBlock::CopyBlockData()
{
// Create a new CSubsystemInBlock
CBlock *pBlock = new CSubsystemInBlock(*this);
return pBlock;
}
The developer will have noticed the accessor method to retrieve the output port name. Hence, add
a public constant accessor method to the CSubsystemOutBlock class with the following prototype,
CString CSubsystemOutBlock::GetOutputPortName() const, and edit the function
as shown.
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CSubsystemOutBlock::CopyBlockData(void), may be added as shown.
CBlock* CSubsystemOutBlock::CopyBlockData()
{
// Create a new CSubsystemOutBlock
CBlock *pBlock = new CSubsystemOutBlock(*this);
return pBlock;
}
// Port Construction
const vector<CPort*> *p_vec_input_ports = blk.
GetVectorOfInputPortsCopy();
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for
const vector of CPort ptrs.
The developer will have noticed the introduction of two public constant accessor methods to retrieve
the number of addition and subtraction inputs. Hence, add the accessor methods with the declara-
tions and definitions provided as follows.
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CSumBlock::CopyBlockData(void), may be added as shown.
CBlock* CSumBlock::CopyBlockData()
{
// Create a new CSumBlock
CBlock *pBlock = new CSumBlock(*this);
return pBlock;
}
// Port Construction
const vector<CPort*> *p_vec_input_ports = blk.
GetVectorOfInputPortsCopy();
const vector<CPort*> *p_vec_output_ports = blk.
GetVectorOfOutputPortsCopy();
vector<CPort*>::const_iterator it_port; // const iterator for
const vector of CPort ptrs.
for(it_port = (*p_vec_input_ports).begin(); it_port
!= (*p_vec_input_ports).end(); it_port++)
{
// Deref it_port to get ptr-to-CPort and then deref the
ptr-to-CPort to get CPort.
CPort *p_input_port = new CPort(**it_port, *this); // CPort copy
constructor
GetVectorOfInputPorts().push_back(p_input_port); // add the
new port
}
for(it_port = (*p_vec_output_ports).begin(); it_port
!= (*p_vec_output_ports).end(); it_port++)
{
// Deref it_port to get ptr-to-CPort and then deref the
ptr-to-CPort to get CPort.
CPort *p_output_port = new CPort(**it_port, *this); // CPort copy
constructor
GetVectorOfOutputPorts().push_back(p_output_port); // add the
new port
}
}
The developer will have noticed the two public constant accessor methods to retrieve the numerator and
denominator coefficients as CString arguments, i.e., “m_strNumerCoeffs” and “m_strDenomCoeffs”.
Hence, add the accessor methods with the declarations and definitions provided as follows.
Finally, the public virtual CopyBlockData() method with the prototype, virtual CBlock
*CTransferFnBlock::CopyBlockData(void), may be added as shown:
CBlock* CTransferFnBlock::CopyBlockData()
{
// Create a new CTransferFnBlock
CBlock *pBlock = new CTransferFnBlock(*this);
return pBlock;
}
922 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 29.3 A copy and paste operation of a block diagram model of the state and output equations result-
ing in two identical models on the same document.
29.3.4 Testing the Cut, Copy, and Paste Operations for a Complete Model
Now that all copy constructors, constant accessor methods, and virtual CopyBlockData() func-
tions of the CBlock-derived classes have been added, the Cut, Copy, and Paste operations may be
tested for a complete system model. Figure 29.3 shows a block diagram model of the state and output
equations introduced in Chapters 22 and 23 for modeling second-order linear ordinary differential
equations: the whole diagram may be selected and copied onto the same document to result in two
identical diagrams.
FIGURE 29.4 (a) An evolving editing and successive saving of system models that form a list of system mod-
els (0–3): system model zero is intentionally empty, denoting the start of the editing procedure. (b) Undoing
two steps, making system model one in vogue: the other models remain in the list and are shown with a dashed
outline. (c) Redoing one step, making system model two in vogue: the other models remain in the list and are
shown with a dashed outline. (d) Undoing one step reverting to system model one, followed by adding another
connection and block and saving the changes into the next available position in the list (system model two): the
following models are erased from the list (struck out). (e) Undoing two steps, making the empty system model
zero, in vogue: models one and two still remain in the list and are shown with dashed outlines.
backtrack to a previous model state (Figure 29.4b), and redoing an action would advance the user
to the next saved state (Figure 29.4c): this is the case if no further editing and saving is performed,
but rather simply undoing or redoing a previous action. However, if the user backtracks, or undoes
successive actions to arrive at, e.g., system model one, and then makes a change to the diagram
followed by a saving of the content (Figure 29.4d), then the remaining system models in the list
are erased, and the newly saved diagram stored at the next available position in the list: i.e., system
model one is edited and saved as the new system model two. Subsequent undoing would take the
user back, e.g., to system model zero (Figure 29.4e). Then, the user would not be able to undo prior
to system model zero, nor redo beyond system model two, as expected, since no models exist or are
accessible beyond the bounds of the list.
924 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
To implement the Undo and Redo actions of the Edit menu, the following general steps are to be
taken, where specific details are provided in the ensuing sections:
1. Add the Undo and Redo event-handler functions to the CDiagramEngDoc class.
2. Add functionality to save system models to a list to the CDiagramEngDoc class.
3. Add a copy constructor to the CSystemModel class.
4. Add an assignment operator to the CSystemModel class.
5. Add functionality to update the user interface to the CDiagramEngView class.
6. Testing the editing actions.
Table 29.1 shows the menu entry objects, IDs, captions, prompts, settings, and event-handler func-
tions that are currently available in the DiagramEng application. The Edit/Undo entry should be
accompanied by the associated Edit/Redo entry to redo the previously undone action. Hence, add
the new Edit/Redo menu entry beneath the existing Edit/Undo menu entry with the properties and
settings shown in Table 29.2.
Incidentally, to allow the key combination specified next to the Edit/Redo menu entry in Table
29.2, the Accelerator key settings need to be set as follows: (1) navigate to the Accelerator menu under
the DiagramEng resources menu in the Resources pane, (2) double-click IDR_MAINFRAME,
(3) right-click the ID title and select New Accelerator to generate the dialog window shown in
Figure 29.5, and (4) enter ID_EDIT_REDO as the ID, “Y” as the key, “Ctrl” as the modifier, and
VirtKey as the type.
Now the event-handler functions associated with the menu entry IDs need to be added to the
project (using the ClassWizard), to perform the undoing and redoing editing actions. Both of
TABLE 29.2
Edit Menu Entry Objects, IDs, Captions, Prompts, Settings, and Event-Handler
Functions for the Undo/Redo Paired Editing Action
Object Property Setting Event-Handler Function
Edit/Undo ID ID_EDIT_UNDO OnEditUndo()
Caption &Undo\tCtrl+Z
Prompts Undo the last action\nUndo
Edit/Redo ID ID_EDIT_REDO OnEditRedo()
Caption &Redo\tCtrl+Y
Prompts Redo the previously undone action\nRedo
FIGURE 29.5 Accelerator key properties for the ID, ID_EDIT_REDO, corresponding to the Edit/Redo
menu entry.
Edit Menu 925
these functions are to be added to the CDiagramEngDoc class, since this class has as a member
variable, a CSystemModel “m_SystemModel” object, and will also contain a list of pointers-
to-CSystemModel, i.e., “list<CSystemModel*> m_lstSystemModel”, that will record the vari-
ous saved changes to the evolving editing of a system model. Hence, add the OnEditUndo()
event-handler function to the CDiagramEngDoc class for the ID_EDIT_UNDO object ID, and
edit it as shown.
void CDiagramEngDoc::OnEditUndo()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int i;
CSystemModel *pSystemModel = NULL;
list<CSystemModel*>::iterator it_sm;
// DiagramEng (end)
}
void CDiagramEngDoc::OnEditRedo()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int i;
int size;
CSystemModel *pSystemModel = NULL;
list<CSystemModel*>::iterator it_sm;
{
m_iIndexSystemModel++; // increment index to get next item in
list
// DiagramEng (end)
}
CDiagramEngDoc::CDiagramEngDoc()
{
// TODO: add one-time construction code here
// DiagramEng (start)
m_iEditRedoFlag = 0;
m_iEditUndoFlag = 0;
m_iFineMoveFlag = 0;
m_iIndexSystemModel = 0;
m_iKeyFlagTrack = 0;
m_iNSystemModels = 10;
m_iRubberBandCreated = 0;
m_iSelectAll = 0;
m_dDeltaLength = 50.0;
m_strFilePath = “”;
// DiagramEng (end)
}
Later, the CDiagramEngView class will need to access the “m_iEditUndoFlag” and
“m_iEditRedoFlag” variables to update the user interface: hence, add two public constant acces-
sor functions with the following prototypes to the CDiagramEngDoc class, and edit them as
shown:
void CDiagramEngDoc::DeleteSystemModelList()
{
list<CSystemModel*>::iterator it_sm;
The system model list-deletion function now needs to be called from the CDiagramEngDoc destruc-
tor as shown in bold in the following.
CDiagramEngDoc::∼CDiagramEngDoc()
{
// Delete the list of pointers-to-CSystemModel “m_lstSystemModel”
DeleteSystemModelList();
}
void CDiagramEngDoc::SaveSystemModelToList()
{
int i;
int lst_size;
list<CSystemModel*>::iterator it_sm;
list<CSystemModel*>::iterator it_sm_erase_start;
930 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Adjust index
lst_size = m_lstSystemModel.size();
m_iIndexSystemModel = lst_size - 1;
Initially (“m_iIndexSystemModel == 0”), a blank system model (Figure 29.4a system model zero)
is to be placed in the list of system models, “m_lstSystemModel”, since this allows the user to undo
editing actions until an empty palette occurs, implicitly signifying the start of the editing procedure.
If this were not the case, the user could only revert back to the original, nonempty model, which
would not feel intuitive. The copy constructor is used to make a copy of the existing model, and then
list deletion functions are called to empty the model.
Then, as explained using Figure 29.4, if a model is being saved (Figure 29.4d), all items in the list
following that specified by the index, “m_iIndexSystemModel”, are erased, where the address of the
new model is to be placed at the new end of the list. The iterator “it_sm” is incremented to deter-
mine the starting position of the erase action, which is performed using the list function erase().
However, prior to erasing list elements, delete is called to invoke the destructor to delete the system
model, the pointer to which is accessed by dereferencing the “it_sm” iterator.
The CSystemModel copy constructor is then used to make a copy of the current system model
and saves its address, denoted by “pSystemModel”, in the list of system models, “m_lstSystem-
Model”. A check is then made to determine whether the list size is greater than the maximum
allowed, “m_iNSystemModels”: if so, then the element at the front of the list is deleted, using delete
and then removed using pop _ front(). If delete is not called to destroy the system model object,
then a memory leak would occur. The interested reader may choose to experiment with this and run
a debug-build configuration of the application using the debugger, and upon exiting the application,
the leaks will be displayed in the Debug output window of the IDE.
Finally, the integer index, “m_iIndexSystemModel”, is incremented and denotes the position of
the last system model in the list, and the Edit/Undo and Edit/Redo flag-like variables are set.
list<CBlock*>::const_iterator it_blk;
list<CConnection*>::const_iterator it_con;
list<int*>::const_iterator it_tr;
// INTEGER DATA
m_iModelStatusFlag = sm.GetModelStatusFlag();
m_iNOutputBlocks = sm.GetNOutputBlocks();
m_iNSourceBlocks = sm.GetNSourceBlocks();
m_iNSourceLoopTours = sm.GetNSourceLoopTours();
m_iNSourceOutputTours = sm.GetNSourceOutputTours();
// DOUBLE DATA
m_dATOL = sm.GetATOL();
m_dRTOL = sm.GetRTOL();
m_dTimeStart = sm.GetTimeStart();
m_dTimeStepSize = sm.GetTimeStepSize();
m_dTimeStop = sm.GetTimeStop();
// CString DATA
m_strIntegrationMethod = sm.GetIntegrationMethod();
m_strModelName = sm.GetModelName();
m_strTimeStepType = sm.GetTimeStepType();
m_strWarning = sm.GetWarning();
// LIST DATA
// Copy Block List
const list<CBlock*> *plstBlock = sm.GetBlockListCopy();
Initially, local variables are declared, and then the copying of member data from the incoming
object, “sm”, to the object being constructed is performed. The assignment of integer, double, and
CString data involves public constant accessor functions as shown. The copying of the list-based
data is somewhat more involved and includes lists of pointers-to-CBlock, pointers-to-CConnection,
and pointers-to-int (integer arrays).
The block list is obtained by the constant accessor function GetBlockListCopy(), which
returns the address of the member list, “m_lstBlock”, as a pointer-to-a-constant list of pointers-to-
CBlock, i.e., “const list<CBlock*> *plstBlock”. This list is iterated over, and the derived runtime
type of the CBlock pointer is used to invoke the correct CopyBlockData() function (introduced
earlier in the project), to copy the block’s data, and the address of the new block is assigned to the
pointer, “pBlock”, which is then placed in the block list.
The copying of the connection list works in a similar fashion: the constant accessor function
GetConnectionListCopy() returns the address of the connection list, and this is assigned to the
pointer-to-a-constant list of pointers-to-CConnection, i.e., “const list<CConnection*> *plstConnection”.
Thereafter, the list is iterated over, and the iterator dereferenced, once to get the pointer-to-CConnection
and then a second time to get the CConnection object, which is then passed to the CConnection copy
constructor and the address of the new object placed in the list, “m_lstConnection”.
The source-to-loop-block and source-to-output-block node-to-node tours, determined at the
model validation stage, are recorded in integer arrays, and these arrays are then stored in the lists
“m_lstSourceLoopTour” and “m_lstSourceOutputTour”, respectively. The constant accessor func-
tions, GetSourceLoopTourListCopy() and GetSourceOutputTourListCopy(), are
used to return the addresses of the relevant lists as a pointer-to-a-constant list of pointers-to-int,
i.e., “const list<int*> *plst”, or equivalently a pointer-to-a-constant list of integer arrays.
Hence, the following public constant accessor methods, with the declarations and definitions
provided, are to be added to the CSystemModel class. The developer will recall that some of the
functions used in the copy constructor were introduced in the project at an earlier developmental
stage; hence, only new functions are provided here.
// INTEGER DATA
m_iModelStatusFlag = sm.GetModelStatusFlag();
m_iNOutputBlocks = sm.GetNOutputBlocks();
m_iNSourceBlocks = sm.GetNSourceBlocks();
m_iNSourceLoopTours = sm.GetNSourceLoopTours();
m_iNSourceOutputTours = sm.GetNSourceOutputTours();
// DOUBLE DATA
m_dATOL = sm.GetATOL();
m_dRTOL = sm.GetRTOL();
m_dTimeStart = sm.GetTimeStart();
m_dTimeStepSize = sm.GetTimeStepSize();
m_dTimeStop = sm.GetTimeStop();
// CString DATA
m_strIntegrationMethod = sm.GetIntegrationMethod();
m_strModelName = sm.GetModelName();
m_strTimeStepType = sm.GetTimeStepType();
m_strWarning = sm.GetWarning();
// LIST DATA
// Copy Block List
const list<CBlock*> *plstBlock = sm.GetBlockListCopy();
Initially, the overriding assignment operator function checks for self-assignment, by comparing
the address of the object upon which the function is called, stored in “this”, with the address of
the incoming argument, “sm”, and if so, returns the object upon which the function is called by
dereferencing the “this” pointer. If self-assignment is not being attempted, the rest of the function
body is executed: first the contained system model lists are deleted, including the list of pointers-
to-CBlock, list of pointers-to-CConnection, and lists of integer arrays, and then the assignment
of the incoming object’s data members is made to the underlying object upon which the function
is called. The member assignment part of the function is identical to that of the copy constructor
introduced earlier.
The software user may select the Undo or Redo entries under the Edit menu to undo and redo edit-
ing actions, respectively, as discussed. However, as presented in Figure 29.4, if the user undoes suc-
cessive editing actions to arrive at the front of the list (Figure 29.4e), or redoes actions to move to
the end of the list (Figure 29.4d), the Undo and Redo menu entries should be presented in a disabled
state, respectively, and enabled otherwise. To update the user interface with enabled or disabled Edit
938 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
menu entries, two functions need to be added for the UPDATE_COMMAND_UI event messages
for the corresponding menu entry IDs:
The OnUpdateEditUndo() function gets the state of the CDiagramEngDoc member variable,
“m_iEditUndoFlag”, retrieved by a call to GetEditUndoFlag() upon the document pointer, and
then uses the pointer-to-class-Command-User-Interface, “pCCmdUI”, to call Enable() with the
appropriate Boolean argument: FALSE and TRUE imply disabling and enabling, respectively [2].
Alternatively, a pointer to the main menu may be obtained and EnableMenuItem() called, pass-
ing the menu item ID and a flag combination.*
* Although CheckMenuItem() did work using “pMenu”, Enable() was called using (class Command-User-Interface)
pointer, “pCmdUI”.
Edit Menu 939
// DiagramEng (end)
}
FIGURE 29.6 Successive editing actions made, forming different system models (a–d) that can be undone
and redone, corresponding to Figure 29.4a.
940 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
(a) (b)
(c) (d)
(e) (f)
(g) (h)
(i) (j)
FIGURE 29.7 Moving a diagram model and then undoing the action showing element-by-element reversal
of movement: (a) selected diagram, (b) moved diagram, (c–e) undoing movement of bend points, and (f–j)
undoing movement of blocks. The connectors remain attached to bend points and block ports when they are
moved in the undoing action (as expected).
Edit Menu 941
29.5 SUMMARY
The Edit menu allows the software user to perform the following operations: Undo, Redo,
Cut, Copy, Paste, Delete Grouped Items, Select All, and Add Multiple Blocks. The selec-
tion of all diagram model content involves adding the OnEditSelectAll() method to the
CDiagramEngDoc class, in which the DetermineDiagramCoordinateExtrema() and
DetermineDiagramCoordinateMinima() methods of the CSystemModel class are called
to determine diagram-based extreme pseudo-CPoint values. The ResetSelectAll() method
of the CDiagramEngDoc class is called by either CDiagramEngView::OnLButtonDown() or
CDiagramEngDoc::OnInitTrackMultipleItems() to cancel the Select-All action.
The Cut, Copy, and Paste operations are implemented using the OnEditCut(), OnEditCopy(),
and OnEditPaste() event-handler functions of the CDiagramEngDoc class, where in the first
two methods, copies of diagram entities are made, and within the latter, block (list<CBlock*>)
and connection (list<CConnection*>) lists of a CClipboard object, “m_Clipboard”, residing in the
CDiagramEngDoc class, are merged with the underlying system model.
The copying of blocks is initiated through the use of a virtual CBlock function, CopyBlock
Data(), that, depending on the derived runtime type of the block concerned, the correct block copy
constructor is called. The copying of CConnection objects is done directly using the class copy
constructor. Copy constructors are added for the main DiagramEng classes, CBlock, CBlockShape,
CConnection, CPort, and the CBlock-derived block classes, and are in general of the form,
CTypeName::CTypeName(const CTypeName &obj), where “CTypeName” and “obj” refer
to the user-defined type and object, respectively, and member initialization should occur for a base
class if appropriate.
The undoing (Edit/Undo) and redoing (Edit/Redo) of editing actions, initiated from the
OnEditUndo() and OnEditRedo() event-handler functions of the CDiagramEngDoc class, involve
creating a list of system model pointers (“list<CSystemModel*> m_lstSystemModel”) and saving
or retrieving the addresses of system models to and from the list. The storing of models is performed
by overriding the virtual CDocument::SetModifiedFlag() function, which chains to the base
class version and also calls the SaveSystemModelToList() method, which uses a CSystemModel
copy constructor to copy models, the addresses of which are added to the list. An assignment opera-
tor of the form, CTypeName &CTypeName::operator = (const CTypeName &obj),
is also added to the CSystemModel class and is used in the undoing and redoing functions, where
the address retrieved from the system model list, “m_lstSystemModel”, is dereferenced to access
the system model, which is then assigned to the system model member object, “m_SystemModel”
(of CDiagramEngDoc). Finally, the CDiagramEngView user interface update functions,
OnUpdateEditUndo() and OnUpdateEditRedo(), are used to both enable and disable their
corresponding Edit menu items depending on the value of the list index, “m_iIndexSystemModel”.
REFERENCES
1. C++ Reference, http://www.cppreference.com/wiki/, (accessed 2010).
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
30 Annotations
30.1 INTRODUCTION
At the current stage in the project development, a user can draw a model diagram on the palette
consisting of blocks and adjoining connections, where the block graphics indicate the type of
block being used. However, it is difficult to discern what the underlying diagram represents
without annotations describing, e.g., the independent and dependent variables, signal forms,
mathematical expressions, feedback loops, and the inclusion of general notes to add clarifying
detail to the system being modeled. Hence, annotations are required and these should be made,
in their most general form, at the system model level, rather than be associated with specific
diagram entities, e.g., blocks or connections. Annotation objects stored in the CSystemModel
class may also make for easier data management, since then, changes need to be made to the
CSystemModel class methods rather than numerous other diagram-entity classes, e.g., the
CConnection and CBlock-based classes.
New classes, member methods, and variables need to be added to the project to allow the user
to enter information describing the model. A CAnnotation class working in conjunction with a
CAnnotationDialog class, to allow description-centric user input through the use of a dialog win-
dow, appears to be appropriate, and the CSystemModel class could then contain a list of pointers-
to-CAnnotation, i.e., “list<CAnnotation*> m_lstAnnotation”, to hold all the annotation instances
for the model. The topics that need to be addressed, which will ultimately involve making various
changes or additions to existing code, are listed as follows, where specific details are provided in
the ensuing sections:
943
944 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
“m_strColor”, “m_strFont”, and “m_strStyle”. In addition, add the public accessor methods
whose declarations and definitions are listed as follows.
#if !defined(AFX_ANNOTATION_H__83108C7E_DAD7_45E3_BB3E_2711F6679D78__
INCLUDED_)
#define AFX_ANNOTATION_H__83108C7E_DAD7_45E3_BB3E_2711F6679D78__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class CAnnotation
{
public:
void SetStyle(CString style);
CString GetStyle(void) const;
void SetStringFont(CString font);
CString GetStringFont(void) const;
void SetColor(CString color);
CString GetColor(void) const;
void SetStringAnnotation(CString an);
CString GetStringAnnotation(void) const;
void SetPosition(CPoint point);
CPoint GetPosition(void) const;
void SetSize(int size);
int GetSize(void) const;
CAnnotation();
virtual ∼CAnnotation();
private:
int m_iSize;
CPoint m_ptPosition;
CString m_strAnnotation;
CString m_strColor;
CString m_strFont;
CString m_strStyle;
};
Annotations 945
#endif // !defined(AFX_ANNOTATION_H__83108C7E_DAD7_45E3_
BB3E_2711F6679D78__INCLUDED_)
The accessor methods may be edited as follows, where the retrieval (“get”) methods are constant
(const) functions since they do not modify the underlying object.
int CAnnotation::GetSize() const
{
return m_iSize;
}
void CAnnotation::SetSize(int size)
{
m_iSize = size;
}
CPoint CAnnotation::GetPosition() const
{
return m_ptPosition;
}
void CAnnotation::SetPosition(CPoint point)
{
m_ptPosition = point;
}
CString CAnnotation::GetStringAnnotation() const
{
return m_strAnnotation;
}
void CAnnotation::SetStringAnnotation(CString an)
{
m_strAnnotation = an;
}
CString CAnnotation::GetColor() const
{
return m_strColor;
}
void CAnnotation::SetColor(CString color)
{
m_strColor = color;
}
CString CAnnotation::GetStringFont() const
{
return m_strFont;
}
void CAnnotation::SetStringFont(CString font)
{
m_strFont = font;
}
CString CAnnotation::GetStyle() const
{
return m_strStyle;
}
946 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Finally, the member variables may be initialized with default values as shown in the class constructor.
The class destructor takes no action at present as indicated by the comment.
CAnnotation::CAnnotation()
{
// Member init.
m_iSize = 10; // default size
m_ptPosition = CPoint(0,0); // origin location
m_strAnnotation = “Enter annotation here”; // initial string
m_strColor = “red”; // color
m_strFont = “Arial”; // font
m_strStyle = “Regular”; // style
}
CAnnotation::∼CAnnotation()
{
// Do nothing for now.
}
TABLE 30.1
Context Menu Settings: IDs, Captions, and Prompts, and the New Format
Annotation Entry
ID Caption Prompt: Status Bar and Tooltips
IDM_DELETE_ITEM &Delete Item Delete selected item\nDelete selected item
ID_EDIT_DELETE_GROUPED_ITEMS Delete &Grouped Items Delete items enclosed by rectangle\nDelete
grouped items
IDM_FINE_MOVE_ITEM &Fine Move Item Fine movement of item\nFine movement of item
IDM_FORMAT_ANNOTATION Format &Annotation Format annotation\nFormat annotation
IDM_INSERT_BEND_POINT &Insert Bend Point Insert bend point\nInsert bend point
IDM_REVERSE_BLOCK_DIREC &Reverse Block Reverse block direction\nReverse block direction
IDM_SET_OUTPUT_SIGNAL Set &Output Signal Set block output connection-based signal\nSet
Output Signal
IDM_SET_ITEM_PROPERTIES &Set Properties Set item properties\nSet item properties
Annotations 947
An event-handler function needs to be added for the new Context menu entry to allow the user to
either format an existing annotation or create a new one. Hence, invoke the ClassWizard, select the
class name to be CDiagramEngDoc, select the Object ID to be IDM_FORMAT_ANNOTATION,
and add a function to the Command message, naming it OnFormatAnnotation(); edit it as
shown.
void CDiagramEngDoc::OnFormatAnnotation()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int annotation_found = 0; // flag indicating whether
annotation found
bool flag = FALSE; // flag indicating whether “OK”
or “Cancel” was selected in FormatAnnotation()
double dist; // Euclidean dist bw. annotation
posn and point posn.
double length = 0.25*m_dDeltaLength; // reference length
CPoint annotation_posn; // annotation posn.
CPoint point; // local point var.
CString sMsg; // main msg string
list<CAnnotation*>::iterator it_an; // iterator
// Get the point at which the context menu was invoked
point = m_ptContextMenu; // init a local copy.
// Print msg.
//sMsg.Format(“\n CDiagramEngDoc::OnFormatAnnotation(), point (x,y)
= (%d,%d)\n”, point.x, point.y);
//AfxMessageBox(sMsg, MB_OK, 0);
// Get a copy of the annotation list in the CSystemModel class
list<CAnnotation*> &annotation_list = GetSystemModel();
GetAnnotationList();
// ITERATE THROUGH THE ANNOTATION LIST TO FIND SELECTED ANNOTATION
for(it_an = annotation_list.begin(); it_an != annotation_list.
end(); it_an++)
{
annotation_posn = (*it_an)->GetPosition();
dist = sqrt(pow( (annotation_posn.x - point.x),2)+
pow( (annotation_posn.y - point.y),2) );
if(dist <= length)
{
flag = (*it_an)->FormatAnnotation(point); // format the
annotation
if(flag == TRUE) // if the annotation was changed
{
SetModifiedFlag(TRUE); // set the doc. as modified to
prompt the user to save
UpdateAllViews(NULL); // redraw the doc since annotation
has changed
}
annotation_found = 1; // an annotation was found for
formatting
break; // exit the for loop
}
}
948 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// CREATE A NEW ANNOTATION AT THE POINT AT WHICH THE CONTEXT MENU WAS
INVOKED
if(annotation_found == 0)
{
// Create an annotation object
CAnnotation *pAn = new CAnnotation(); // default constructor
for CAnnotation object
flag = pAn->FormatAnnotation(point); // format the annotation
// If the “OK” rather than “Cancel” button was clicked, then add
the annotation.
if(flag == TRUE)
{
annotation_list.push_back(pAn); // add the new annotation
to the actual list (passed by ref.)
SetModifiedFlag(TRUE); // set the doc. as
modified to prompt the user to save
UpdateAllViews(NULL); // redraw the doc since
annotation has been added
}
else
{
delete pAn; // delete the recently created annotation
}
}
// DiagramEng (end)
}
The code given earlier uses a pointer-to-CAnnotation, hence including the “Annotation.h” header
file at the top of the “DiagramEngDoc.cpp” source file. In addition, the developer will have noticed
the call to FormatAnnotation() in two locations in the function given earlier; if the user clicks
upon an existing annotation, then it is formatted, and if not, a new annotation is created at the
point, “point” (equivalently “m_ptContextMenu”), and then the formatting function called upon the
CAnnotation pointer, “pAn”. However, if in the FormatAnnotation() function the Cancel but-
ton is selected, then for the existing annotation, the formatting action is aborted, and for the newly
created annotation through the use of the new operator, the annotation is deleted (delete) and not
added to the annotation list.
Now, add a new public member function to the CAnnotation class with the prototype,
bool CAnnotation::FormatAnnotation(CPoint point), and edit it as shown to return
a Boolean value indicating whether the OK or Cancel buttons were clicked on the to-be-added
annotation formatting dialog window.
// Set the dialog class vars using the CAnnotation class vars
sprintf(strSize, “%d”, m_iSize); // convert integer “m_iSize”
to string
oDlg.m_strSize = strSize; // size
oDlg.m_strAnnotation = m_strAnnotation; // annotation
Annotations 949
The FormatAnnotation() function first initializes the dialog object’s member variables with
those of the associated CAnnotation class. The first instance of the dialog object will have the
initial values set in the CAnnotation constructor; however, on subsequent invocation, the variable
values will reflect the current state of the CAnnotation class. If the OK button is selected, then
the dialog control values are updated to the CAnnotation class variables; if the Cancel button is
selected, then the updating of the variables does not occur and the Boolean flag FALSE is returned
to abort the editing action. The CreateAnnotationFont() call in the function given earlier,
where the member variable, “m_fAnnotationFont”, is actually passed by reference, will be added
later and is commented out for now. The dialog object is added in the following section.
In addition, a CSystemModel function to retrieve the list of annotations is required. Hence,
add a private member variable of type list<CAnnotation*> with name “m_lstAnnotation” to
the CSystemModel class. This will require including the “Annotation.h” header file at the top
of the “SystemModel.h” header file. Finally, add a (nonconstant) public member function to
the CSystemModel class with the prototype, list<CAnnotation*> &CSystemModel::
GetAnnotationList(void), and edit it as shown to return the actual member list,
“m_lstAnnotation”, by reference.
list<CAnnotation*> &CSystemModel::GetAnnotationList(void)
{
// Return the actual annotation list by reference (non constant fn).
return m_lstAnnotation;
}
memory leaks. Hence, add a public member function to the CSystemModel class with the prototype,
void CSystemModel::Delete AnnotationList(void), and edit it as shown.
void CSystemModel::DeleteAnnotationList()
{
list<CAnnotation*>::iterator it_an;
// Delete annotation list
for(it_an = m_lstAnnotation.begin(); it_an != m_lstAnnotation.
end(); it_an++)
{
delete (*it_an); // delete what it_an is pointing
to: i.e. deref the it_an ptr and delete the ptr-to-CAnnotation
}
m_lstAnnotation.clear();
}
30.3.2.1 Insert a New Dialog Window and Add All Necessary Controls
To insert a new dialog window, go to the ResourceView tab of the Workspace pane and right-click on
Dialog and insert a new dialog. A new dialog will appear in the Dialog directory. Rename it, by right-
clicking on the dialog window itself to activate the Dialog Properties window, and change the ID to
reflect the nature of the dialog window, e.g., IDD_ANNOTATION_DLG, for annotation-related dialog-
based user input: use the caption “AnnotationDialog”. Leave the OK and Cancel buttons on the dialog.
Add the controls shown in Table 30.2 and place them on the dialog window as shown in Figure 30.1.
Annotations 951
TABLE 30.2
Annotation Dialog Window Control Objects, Properties, and Settings
Object Property Settings
Group Box ID ID_ANNOTATION_DLG_GB
Caption Annotation Settings
Static Text ID ID_ANNOTATION_DLG_TXT_AN
Caption &Annotation:
Extended Styles Right aligned text
Edit Box ID ID_ANNOTATION_DLG_EB_AN
Multiline Checked
Horizontal scroll Checked
Vertical scroll Checked
Want return Checked
Static Text ID ID_ANNOTATION_DLG_TXT_FONT
Caption &Font:
Extended Styles Right aligned text
Combo Box ID ID_ANNOTATION_DLG_CB_FONT
Data (Leave empty.a)
Sort Checked
Static Text ID ID_ANNOTATION_DLG_TXT_STYLE
Caption &Style:
Extended Styles Right aligned text
Combo Box ID ID_ANNOTATION_DLG_CB_STYLE
Data Regular
Sort Italic
Bold
Bold Italic
Unchecked
Static Text ID ID_ANNOTATION_DLG_TXT_SIZE
Caption Si&ze:
Right aligned text Checked
Combo Box ID ID_ANNOTATION_DLG_CB_SIZE
Data 8; 9; 10; 11; 12; 14; 16; 18; 20; 22; 24; 26; 28; 36; 48; 72b
Sort Unchecked
Static Text ID ID_ANNOTATION_DLG_TXT_COLOR
Caption Co&lor:
Right aligned text Checked
Combo Box ID ID_ANNOTATION_DLG_CB_COLOR
Data Red
Sort Green
Blue
Black
Unchecked
Button ID ID_ANNOTATION_DLG_BTN_OK
Default button Unchecked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE 30.1 AnnotationDialog dialog window showing the controls as specified in Table 30.2.
Add maximize and minimize buttons and scroll bars to the dialog if desired. Specify the control
tab order by choosing Layout and Tab Order. Finally, to check for duplicate mnemonics, right-click
on the dialog window and select “Check Mnemonics”.
TABLE 30.3
Dialog Window Control Objects, Variable Name, Category,
and Type, for the IDD_ANNOTATION_DLG Dialog Resource
Control Object Variable Name Category Type
ID_ANNOTATION_DLG_CB_COLOR m_strColor Value CString
ID_ANNOTATION_DLG_CB_FONT m_ctlFontList Control CComboBox
m_strFont Value CString
ID_ANNOTATION_DLG_CB_SIZE m_strSize Value CString
ID_ANNOTATION_DLG_CB_STYLE m_strStyle Value CString
ID_ANNOTATION_DLG_EB_AN m_strAnnotation Value CString
Note that two variables are added for the control with ID ID_ANNOTATION_DLG_CB_FONT,
i.e., “m_cltFontList” and “m_strFont”.
Steps similar to those listed earlier were originally presented in Chapter 7, “Working with Text and Fonts,”
of Ref. [1] and are implemented here with slight modifications to suit the current AnnotationDialog dia-
log window. The interested reader may like to read the explanations describing the functionality under-
ling the font-listing code and experiment with the material in Ref. [1] before implementing the following
steps, in order to have a more complete understanding of code function and behavior.
#if !defined(AFX_ANNOTATIONDIALOG_H__26315A8C_66F3_40D8_A1F7_
D63C5FC40863__INCLUDED_)
#define AFX_ANNOTATIONDIALOG_H__26315A8C_66F3_40D8_A1F7_D63C5FC40863__
INCLUDED_
// DiagramEng (start)
// Callback fn to get the list of fonts
int CALLBACK EnumFontFamProc(LPENUMLOGFONT lpelf, LPNEWTEXTMETRIC lpntm,
DWORD nFontType, long lParam);
// DiagramEng (end)
/////////////////////////////////////////////////////////////////////////
// CAnnotationDialog dialog
// Dialog Data
//{{AFX_DATA(CAnnotationDialog)
enum { IDD = IDD_ANNOTATION_DLG };
CComboBox m_ctlFontList;
CString m_strAnnotation;
CString m_strFont;
CString m_strStyle;
CString m_strSize;
CString m_strColor;
//}}AFX_DATA
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAnnotationDialog)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately
before the previous line.
#endif // !defined(AFX_ANNOTATIONDIALOG_H__26315A8C_66F3_40D8_A1F7_
D63C5FC40863__INCLUDED_)
combo box control, “m_ctlFontList”, in which the font name string is added (AddString())
(see Ref. [1] for further details).
// DiagramEng (start)
// Callback fn to get the list of fonts
int CALLBACK EnumFontFamProc(LPENUMLOGFONT lpelf, LPNEWTEXTMETRIC lpntm,
DWORD nFontType, long lParam)
{
// Create a pointer to the dlg wnd.
CAnnotationDialog *pWnd = (CAnnotationDialog*)lParam;
return 1;
}
// DiagramEng (end)
void CAnnotationDialog::GenerateFonts()
{
int cnt;
int nFonts;
CString sNameCurrent;
CString sNamePrevious = "";
LOGFONT lf;
// Init. struct
lf.lfCharSet = DEFAULT_CHARSET;
strcpy(lf.lfFaceName, "");
// Device Context
CClientDC dc(this);
// Enumerate
::EnumFontFamiliesEx((HDC) dc, &lf, (FONTENUMPROC)
EnumFontFamProc, (LPARAM)this, 0);
// Check Duplicate
if(sNameCurrent == sNamePrevious)
{
m_ctlFontList.DeleteString((cnt - 1));
// delete font
}
// Update Font
sNamePrevious = sNameCurrent;
}
}
The GenerateFonts function calls the EnumFontFamiliesEx() function with the func-
tion pointer argument, EnumFontFamProc, to obtain a list of the available system fonts. The
loop iterates from the end of the font list and deletes any duplicate entries (as indicated in
Ref. [1]).
Now, the GenerateFonts function needs to be called from a dialog initialization function
since it cannot be called from within CAnnotation::FormatAnnotation(). Hence, invoke
the ClassWizard, choose the Message Maps tab with class name CAnnotationDialog and add an
event-handler function for the WM_INITDIALOG message, and edit the code as shown in bold.
BOOL CAnnotationDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// DiagramEng (start)
// Initialization that must be performed here and that cannot be done
in CAnnotation::FormatAnnotation()
// DiagramEng (end)
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
The private GenerateFonts() function is called to generate the fonts in the combo box control,
represented by the variable, “m_ctlFontList”, and then the underlying class variable values are
updated to the dialog window controls using UpdateData(FALSE) as reflected in Figure 30.2.
FIGURE 30.2 AnnotationDialog dialog window used to set up the annotation, showing the default variable
settings.
to the CAnnotation class and edit it as shown. The idea concerning the introduction of the private
CFont variable and CreateAnnotationFont() method follows the work of Chapman [1].
// Style
if(m_strStyle == “Regular”)
{
iItalic = 0;
iWeight = FW_REGULAR;
}
else if(m_strStyle == “Italic”)
{
iItalic = 1;
iWeight = FW_REGULAR;
}
else if(m_strStyle == “Bold”)
{
iItalic = 0;
iWeight = FW_BOLD;
}
else if(m_strStyle == “Bold Italic”)
958 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
{
iItalic = 1;
iWeight = FW_BOLD;
}
}
}
TABLE 30.4
Objects, IDs, Class, and Event-Handler Functions for the CAnnotationDialog Class
Object ID Class COMMAND Event-Handler
OK button ID_ANNOTATION_DLG_BTN_OK CAnnotationDialog OnAnnotationDlgBtnOk()
Cancel button IDCANCELa CAnnotationDialog OnCancel()
void CAnnotationDialog::OnAnnotationDlgBtnOk()
{
// TODO: Add your control notification handler code here
// DiagramEng (start)
// Check the value of all member vars
UpdateData(TRUE); // TRUE => dlg wnd control vals updated to vars.
// Check validity of font size arg.
int size = atoi(m_strSize);
if( (size >= 1) && (size <= 1638) )
{
// Print a CString msg.
CString sMsg;
CString sMsgTemp;
sMsgTemp.Format(“ CAnnotationDialog::OnAnnotationDlgBtnOk()\t \n\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“ m_strAnnotation\t = %s\n m_strFont\t = %s\n
m_strStyle\t = %s\n m_strSize\t = %s\n m_strColor\t = %s\n”,
m_strAnnotation, m_strFont, m_strStyle, m_strSize,
m_strColor);
sMsg += sMsgTemp;
AfxMessageBox(sMsg, MB_OK, 0);
// Chain to base class version of the fn.
CDialog::OnOK();
}
else
{
AfxMessageBox(“\n The font size should be in the interval
[1, 1638]. \n”, MB_OK, 0);
return;
}
// DiagramEng (end)
}
(In the event that an “undeclared dialog identifier” message appears upon compilation, the
“Resource.h” header file should be included at the top of the “AnnotationDialog.h” header file.
However, this was not necessary here.)
Now, upon running the application, the dialog window control values are updated to the underly-
ing variables using UpdateData(TRUE), and a message box is displayed (Figure 30.3), showing
the values of the relevant variables used to set the font properties; the AfxMessageBox() call
confirming the font settings may be commented out once the function works as expected. Finally,
if the user enters the correct input, the base class version of the function, CDialog::OnOK(), is
called to close the dialog window; otherwise, the user is prompted to change the input.
If the user invokes the Context menu from a particular point on the palette and formats an anno-
tation, and then subsequently clicks on the palette at exactly the same location, then the recently for-
matted annotation’s values will appear in the invoked dialog window, verifying the correct behavior
of the CDiagramEngDoc::OnFormatAnnotation() function; if the user clicks on a different
location, then a new annotation object is created and added to the list of annotations.
The DrawAnnotation() function called earlier now needs to be added to the CAnnotation class to
actually present each annotation in the list on the screen. Hence, add a public member function to the
CAnnotation class with the prototype, void CAnnotation::DrawAnnotation(CDC *pDC),
and edit the function as shown.
{
pDC->SetTextColor(RGB(0,0,255) );
}
else
{
pDC->SetTextColor(RGB(0,0,0) ); // m_strColor == “Black”
or otherwise.
}
The new font, “m_fAnnotationFont”, is used in the call to SelectObject() to select the object
into the device context [2]. The annotation color is then set using the member variable, “m_strColor”
and SetTextColor(). The annotation, “m_strAnnotation”, is then displayed using TextOut()
and positioned at the point, “m_ptPosition”, on the screen. Then the size of the output text output
is recorded in the CSize member variable, “m_sOutputTextSize” (to be used later). Finally, the old
font, “pOldFont”, is reselected into the device context. Now, when the user runs the application,
the annotation details entered through the dialog window may be viewed on the screen, as shown
in Figure 30.4.
The diagram represents the linear state equations used to model a second-order linear differential
equation representing a mechanical mass–spring–damper system (Example 3-3, p. 73 of Ref. [3])
where m, b, k, y(t), and u(t) are the mass, damping constant, spring constant, output mass displace-
ment from the equilibrium position, and external force input to the system, respectively.
The state and output equations in linear form are
FIGURE 30.4 The state and output equations model diagram augmented with mathematical annotations.
Annotations 963
where
x(t), u(t), and y(t) are the state, control, and output vectors, respectively
A(t), B(t), C(t), and D(t) are the state, control, output, and direct transmission matrices, respectively [3]
The user will have noticed the new CSize member variable, “m_sOutputTextSize”, used in the
DrawAnnotation() function given earlier. Hence, add this new private CSize member variable
to the CAnnotation class. In addition, add a public constant accessor function to the CAnnotation
class with prototype, CSize CAnnotation::GetOutputTextSize(void) const, and edit
it as shown to return the member variable.
CSize CAnnotation::GetOutputTextSize() const
{
return m_sOutputTextSize;
}
// Delete an annotation
item_deleted = DeleteAnnotation();
if(item_deleted == 1)
{
return;
}
// DiagramEng (end)
}
Now add the DeleteAnnotation() function to the CDiagramEngDoc class with the proto-
type, int CDiagramEngDoc::DeleteAnnotation(void), and edit the function as shown
to iterate through the list of annotations, “m_lstAnnotation”, and delete the appropriate proximal
annotation.
int CDiagramEngDoc::DeleteAnnotation()
{
int delete_an = 0; // annotation deletion flag
double dist; // Euclidean dist bw. annotation posn and point posn.
double length = 0.25*m_dDeltaLength; // reference length
CPoint an_posn; // annotation posn.
CPoint point; // local point var.
list<CAnnotation*>::iterator it_an; // iterator
list<CAnnotation*>::iterator it_er = NULL; // element to erase
void CDiagramEngDoc::OnEditDeleteGroupedItems()
{
// TODO: Add your command handler code here
int intersected = 0;
int intersected_head = 0;
int intersected_tail = 0;
double blk_width;
double delta = 0.25*m_dDeltaLength;
CPoint an_posn;
CPoint bend_pt;
CPoint blk_posn;
CPoint head_pt;
CPoint tail_pt;
CRectTracker temp_tracker;
list<CAnnotation*> &annotation_list = GetSystemModel().
GetAnnotationList();
list<CAnnotation*>::iterator it_an;
list<CBlock*> &blk_list = GetSystemModel().GetBlockList();
list<CBlock*>::iterator it_blk;
list<CConnection*>::iterator it_con;
list<CConnection*> &con_list = GetSystemModel().GetConnectionList();
list<CPoint>::iterator it_pt;
// -- IF A RUBBER BAND HAS BEEN CREATED
if(m_iRubberBandCreated == 1)
{
// Create a temp tracker since m_RectTracker will have its coords
updated when the rubber band is moved.
temp_tracker = m_RectTracker;
// -- ITERATE THROUGH BLOCKS
…
// -- ITERATE THROUGH ALL CONNECTIONS (of this model)
…
while(it_an != annotation_list.end() )
{
// Get annotation position
an_posn = (*it_an)->GetPosition();
// Determine if item lies within rubber band
intersected = DetermineCurrentAndIntersectRects(temp_tracker,
an_posn, delta);
if(intersected)
{
// Delete annotation
delete *it_an; // delete actual annotation pointed to
by it_an
it_an = annotation_list.erase(it_an); // delete element
at offset it_an in list
}
else // only increment the iterator if there were no
intersection
{
it_an++;
}
}// end for it_an
// Reset member vars.
m_iRubberBandCreated = 0; // end the rubber band state
SetKeyFlagTrack(0); // reset the key-based track flag
since tracking aborted
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews (NULL); // indicate that sys. should redraw.
}// end if m_iRubberBandCreated
}
The annotation deletion is very similar to block deletion; the position of the annotation is obtained
and used to determine whether the annotation lies within the bounding rubber-band rectangular
region, and if so, the annotation is deleted.
Add a public member function to the CDiagramEngDoc class with the following prototype, int
CDiagramEngDoc::FineMoveAnnotation(int key_flag), and edit it as shown to pro-
cess the initial and subsequent entries into the function and move the annotation according to the
key pressed.
if(GetFineMoveFlag() == 1)
{
// Get a copy of the annotation_list in the system model
list<CAnnotation*> &annotation_list = GetSystemModel().
GetAnnotationList();
// -- ITERATE THROUGH THE ANNOTATION LIST
for(it_an = annotation_list.begin(); it_an != annotation_list.end();
it_an++)
{
move_flag = 0;
an_posn = (*it_an)->GetPosition();
dist = sqrt(pow( (an_posn.x - point.x),2) +
pow( (an_posn.y - point.y),2) );
// -- IF USER CLICKED ON AN ANNOTATION
if(dist <= length)
{
// -- FILTER THE KEY/DIRECTION (screen (0,0) top left
cnr., +ve x to right, +ve y downward
if(key_flag == 37) // LEFT ARROW
{
// Update annotation posn.
an_posn.x = an_posn.x - delta_x;
(*it_an)->SetPosition(an_posn);
pAn = *it_an; // record address of annotation for
subsequent movt.
move_flag = 1;
}
else if(key_flag == 38) // UP ARROW
{
// Update annotation posn.
an_posn.y = an_posn.y - delta_y;
(*it_an)->SetPosition(an_posn);
pAn = *it_an; // record address of annotation for
subsequent movt.
move_flag = 1;
}
else if(key_flag == 39) // RIGHT ARROW
{
// Update annotation posn.
an_posn.x = an_posn.x + delta_x;
(*it_an)->SetPosition(an_posn);
pAn = *it_an; // record address of annotation for
subsequent movt.
move_flag = 1;
}
else if(key_flag == 40) // DOWN ARROW
{
// Update annotation posn.
an_posn.y = an_posn.y + delta_y;
(*it_an)->SetPosition(an_posn);
pAn = *it_an; // record address of
annotation for subsequent movt.
move_flag = 1;
}
Annotations 969
if(move_flag == 1)
{
// Set flag
m_iFineMoveFlag = 4;
if(move_flag == 1)
{
// Mark the document as changed
970 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
return move_flag;
}
// Track block
tracker_flag = TrackBlock(point, pWnd);
if(tracker_flag != 0)
{
GetSystemModel().NullifyNegativeCoordinates();
// CScrollView-related change
return tracker_flag; // return since an item was tracked
}
{
GetSystemModel().NullifyNegativeCoordinates();
// CScrollView-related change
return tracker_flag; // return since an item was tracked
}
// Track annotation
tracker_flag = TrackAnnotation(point, pWnd);
if(tracker_flag != 0)
{
GetSystemModel().NullifyNegativeCoordinates();
// CScrollView-related change
return tracker_flag; // return since an item was tracked
}
// Track multiple items
if(m_iKeyFlagTrack == 1)
{
tracker_flag = TrackMultipleItems(point, pWnd);
if(tracker_flag !=0)
{
GetSystemModel().NullifyNegativeCoordinates();
// CScrollView-related change
return tracker_flag; // return since an item was tracked
}
}
else
{
tracker_flag = 0;
}
// Return flag indicating if an item was tracked: 0 => not tracked,
1 => tracked.
return tracker_flag;
}
The code given earlier calls the TrackAnnotation() function passing in the (CPoint) point
at which the left-button-down event occurred. Hence, add a public member function to the
CDiagramEngDoc class with the prototype, int CDiagramEngDoc::TrackAnnotation
(CPoint point, CWnd *pWnd), and edit the code as shown.
int CDiagramEngDoc::TrackAnnotation(CPoint point, CWnd *pWnd)
{
int tracker_flag = 0;
double dist; // Euclidean distance
double length = 0.25*m_dDeltaLength; // reference length
CPoint an_posn; // annotation position
CSize an_size; // annotation output text size
CPoint bottom_right;
CPoint top_left;
list<CAnnotation*>::iterator it_an;
list<CAnnotation*> &annotation_list = GetSystemModel().
GetAnnotationList();
// CScrollView-related change
CPoint ptLogical = point; // local point to record the point in
logical units
972 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The developer will notice the similarities between TrackAnnotation() shown above and
TrackBlock() introduced earlier in the project. For blocks, the tracking rectangle is precisely
the outline of the block, and the final position of the block is the center point (CenterPoint())
of the tracking rectangle. For annotations, the top left corner of the tracking rectangle demarks
the annotation position, and the rectangle is set up such that it outlines the output text, the size of
which, “m_sOutputTextSize”, is retrieved by the call to GetOutputTextSize(). After move-
ment, the annotation position is thus the top left corner of the tracking rectangle, i.e., “an_posn =
m_RectTracker.m_rect.TopLeft()”.
Finally, in the TrackItem() method given earlier, after the TrackAnnotation() function
call, NullifyNegativeCoordinates() is invoked to nullify any coordinate values that may
have been negated through a tracking motion beyond the left or top edge of the document window.
Hence, edit the NullifyNegativeCoordinates() function as shown in bold in the following;
the ellipsis denotes omitted, unchanged code.
int CSystemModel::NullifyNegativeCoordinates()
{
CPoint ptAn; // annotation position
CPoint ptBlk; // block position
CPoint ptCon; // connection-based point location
list<CAnnotation*>::iterator it_an; // annotation iterator
list<CBlock*>::iterator it_blk; // block iterator
list<CConnection*>::iterator it_con; // connection iterator
list<CPoint>::iterator it_pt; // point iterator
// CHECK BLOCK COORDINATES
…
// CHECK CONNECTION COORDINATES
…
// CHECK ANNOTATION COORDINATES
for(it_an = m_lstAnnotation.begin(); it_an != m_lstAnnotation.end();
it_an++)
{
ptAn = (*it_an)->GetPosition();
if(ptAn.x < 0)
{
ptAn.x = 0;
}
if(ptAn.y < 0)
{
ptAn.y = 0;
}
(*it_an)->SetPosition(ptAn);
}
return 0;
}
974 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// CScrollView-related change
…
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
976 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The developer will notice the similarities between the tracking of multiple blocks and annotations.
The annotation position is first obtained and used to check whether the annotation lies within the
bound tracking rectangle; if so, the annotation position is updated by the change in position of the
tracking rectangle.
CPoint CSystemModel::DetermineDiagramCoordinateExtrema(void)
{
int pt_rad = 5; // radius of a bend point
(assume fixed and small)
double blk_height; // block width
double blk_width; // block height
CPoint ptAnPosn; // annotation position
CPoint ptBend; // bend point
CPoint ptBlkPosn; // block position
CPoint ptHead; // connection head point
CPoint ptMax; // pseudo extreme point
(extreme coords may be from diff. elements)
CPoint ptTail; // connection tail point
CPoint ptTemp; // temporary point
CSize an_text_size; // annotation text size
list<CAnnotation*>::iterator it_an; // annotation iterator
list<CBlock*>::iterator it_blk; // block iterator
Annotations 977
CPoint CSystemModel::DetermineDiagramCoordinateMinima()
{
int pt_rad = 5; // radius of a bend point
(assume fixed and small)
int int_max = 2147483647; // INT_MAX = 2147483647.
double blk_height; // block width
double blk_width; // block height
CPoint ptAnPosn; // annotation position
CPoint ptBend; // bend point
CPoint ptBlkPosn; // block position
CPoint ptHead; // connection head point
CPoint ptMin; // pseudo minimum point
(minimum coords may be from diff. elements)
CPoint ptTail; // connection tail point
CPoint ptTemp; // temporary point
list<CAnnotation*>::iterator it_an; // annotation iterator
978 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
return ptMin;
}
To check that the text size, recorded in “m_sOutputTextSize”, is in fact correct, the
user may place annotations on a diagram and choose the Select All button to select all
diagram content, as shown in Figure 30.5. The CDiagramEngDoc::OnEditSelect
All() function calls both the DetermineDiagramCoordinateMinima() and
FIGURE 30.5 Selection of all diagram content, indicating the pseudo extreme points and hence the text
sizes are correct.
Annotations 979
// Annotation position
ptAn = (*it_an)->GetPosition();
ptAn.x = scale_factor*ptAn.x;
ptAn.y = scale_factor*ptAn.y;
(*it_an)->SetPosition(ptAn);
}
}
980 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
The developer will notice that after the size is set, CreateAnnotationFont() is called, pass-
ing by reference, the member variable, “m_fAnnotationFont”, as the argument, such that a new
font can be created.
Add the accompanying accessor method with the prototype, void CSystemModel::
SetDrawAnnotationFlag(int draw), and edit it as shown.
CSystemModel::SetDrawAnnotationFlag(int draw)
{
m_iDrawAnnotationFlag = draw;
}
Now that the member variable and the associated accessor methods have been introduced to the
CSystemModel class, edit the CSystemModel::DrawSystemModel() function as shown
in bold in the following to draw annotations if applicable (the ellipsis (“…”) denotes omitted,
unchanged code).
Finally, both the CSystemModel copy constructor and assignment operator need to be augmented
given the introduction of the new integer member variable, “m_iDrawAnnotationFlag”. Hence,
make the additions to the CSystemModel::CSytemModel(const CSystemModel &sm)
982 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
copy constructor as shown in bold in the following; the rest of the function remains unchanged, as
indicated by the ellipsis (“…”).
// INTEGER DATA
m_iDrawAnnotationFlag = sm.GetDrawAnnotationFlag();
m_iModelStatusFlag = sm.GetModelStatusFlag();
…
}
// INTEGER DATA
m_iDrawAnnotationFlag = sm.GetDrawAnnotationFlag();
m_iModelStatusFlag = sm.GetModelStatusFlag();
…
void CDiagramEngDoc::OnFormatShowAnnotations()
{
// TODO: Add your command handler code here
// DiagramEng (start)
CSystemModel &system_model = GetSystemModel();
//AfxMessageBox(“\n CDiagramEngDoc::OnFormatShowAnnotations()\n”,
MB_OK, 0);
// DiagramEng (end)
}
// DiagramEng (start)
int draw_an = GetDocument()->GetSystemModel().
GetDrawAnnotationFlag(); // show annotation state
984 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// DiagramEng (end)
}
30.9.1.1 Add a Copy Constructor and Relevant Methods to the CAnnotation Class
A (public) copy constructor is to be added to the CAnnotation class with the prototype,
CAnnotation::CAnnotation(const CAnnotation &an), and edited as shown. The
CAnnotation class is not derived from a base class and hence does not need to call a base class
constructor through member initialization.
m_sOutputTextSize = an.GetOutputTextSize();
an.CreateAnnotationFont(m_fAnnotationFont); // pass underlying
member var by ref to create new font
m_iSize = an.GetSize();
m_ptPosition = an.GetPosition();
m_strAnnotation = an.GetStringAnnotation();
m_strColor = an.GetColor();
m_strFont = an.GetStringFont();
m_strStyle = an.GetStyle();
}
an.GetAnnotationFontCopy().GetLogFont(&LogFont);
m_fAnnotationFont.CreateFontIndirect(&LogFont);
986 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
However, this does not work, since although the constant GetAnnotationFontCopy() func-
tion may be of the form shown, the GetLogFont() function is in fact nonconstant, and hence, the
constant nature of the copy constructor would not be upheld.
30.9.1.2 Add CAnnotation Specific Variables and Methods to the CClipboard Class
The OnEditCut() and OnEditCopy() methods both copy selected diagram entities to the
appropriate lists of the CClipboard class; the OnEditPaste() method then merges these lists
with the corresponding lists in the CSystemModel class. Hence, add a private member variable,
named “m_lstAnnotation”, to the CClipboard class, with the type, list<CAnnotation*>. Add a pub-
lic accessor function with prototype, list<CAnnotation*> &GetAnnotationList(void),
to retrieve the list by reference and edit it as shown. This will require including the “Annotation.h”
header file at the top of the “Clipboard.cpp” source file.
list<CAnnotation*> &CClipboard::GetAnnotationList()
{
return m_lstAnnotation;
}
Now the CClipboard class destructor should be augmented to delete the list of CAnnotation pointers,
“m_lstAnnotation”, as shown in bold in the following.
CClipboard::∼CClipboard()
{
// Delete annotation list
DeleteAnnotationList();
Finally, add a public member function to the CClipboard class with the prototype,
void CClipboard::DeleteAnnotationList(void), and edit it as shown to first delete
(delete) the pointers-to-CAnnotation in the list and then clear (clear()) the list.
void CClipboard::DeleteAnnotationList()
{
list<CAnnotation*>::iterator it_an;
void CDiagramEngDoc::OnEditCut()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int intersected = 0;
int intersected_head = 0;
int intersected_tail = 0;
double blk_width;
double delta = 0.25*m_dDeltaLength;
CAnnotation *pAn = NULL;
CBlock *pBlock = NULL;
CConnection *pCon = NULL;
CPoint an_posn;
CPoint blk_posn;
CPoint head_pt;
CPoint tail_pt;
CRectTracker temp_tracker;
list<CAnnotation*> &an_list = m_SystemModel.GetAnnotationList();
list<CAnnotation*> &an_list_cb = m_Clipboard.GetAnnotationList();
list<CAnnotation*>::iterator it_an;
list<CBlock*> &blk_list = m_SystemModel.GetBlockList();
list<CBlock*> &blk_list_cb = m_Clipboard.GetBlockList();
list<CBlock*>::iterator it_blk;
list<CConnection*> &con_list = m_SystemModel.GetConnectionList();
list<CConnection*> &con_list_cb = m_Clipboard.GetConnectionList();
list<CConnection*>::iterator it_con;
{
// Get annotation properties
an_posn = (*it_an)->GetPosition();
// Determine if item lies within rubber band
intersected = DetermineCurrentAndIntersectRects(temp_tracker,
an_posn, delta);
if(intersected)
{
// COPY ANNOTATION TO CLIPBOARD BLOCK LIST
pAn = new CAnnotation(**it_an);
an_list_cb.push_back(pAn);
// Delete annotation
delete *it_an; // delete annotation pointed to by it_an
it_an = an_list.erase(it_an); // delete element at
offset it_an in list (that held the annotation)
}
else // only increment the iterator if there were
no intersection
{
it_an++;
}
}// end for it_an
// Reset member vars.
m_iRubberBandCreated = 0; // end the rubber band state
SetKeyFlagTrack(0); // reset the key-based track flag
since tracking aborted
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been modified
to prompt user to save
UpdateAllViews (NULL); // indicate that sys. should redraw.
}// end if m_iRubberBandCreated
// DiagramEng (end)
}
Initially the CSystemModel and CClipboard annotation lists, “an_list” and “an_list_cb”, respec-
tively, are retrieved, and then the CSystemModel list is iterated over and new CAnnotation objects
created; the “it_an” iterator is dereferenced once to obtain the pointer-to-CAnnotation, and then
again, to access the object, which is used by the copy constructor to create a new CAnnotation
instance. Thereafter, the pointer is added to the CClipboard-based list, “an_list_cb”, and the
annotation deleted and erased from the CSystemModel-based list, “an_list” or, equivalently,
“m_lstAnnotation”; the deletion is necessary for the cut operation.
Edit the CDiagramEngDoc::OnEditCopy() function in a similar manner to the
OnEditCut() function presented earlier, as shown in bold in the following; here, no deletion
or erasing is performed on the CSystemModel-based annotation list, “an_list” or, equivalently,
“m_lstAnnotation”, and hence, a for rather than a while loop is used to iterate over the list.
void CDiagramEngDoc::OnEditCopy()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int intersected = 0;
Annotations 989
int intersected_head = 0;
int intersected_tail = 0;
double blk_width;
double delta = 0.25*m_dDeltaLength;
CAnnotation *pAn = NULL;
CBlock *pBlock = NULL;
CConnection *pCon = NULL;
CPoint an_posn;
CPoint blk_posn;
CPoint head_pt;
CPoint tail_pt;
CRectTracker temp_tracker;
list<CAnnotation*> &an_list = m_SystemModel.GetAnnotationList();
list<CAnnotation*> &an_list_cb = m_Clipboard.GetAnnotationList();
list<CAnnotation*>::iterator it_an;
list<CBlock*> &blk_list = m_SystemModel.GetBlockList();
list<CBlock*> &blk_list_cb = m_Clipboard.GetBlockList();
list<CBlock*>::iterator it_blk;
list<CConnection*> &con_list = m_SystemModel.GetConnectionList();
list<CConnection*> &con_list_cb = m_Clipboard.GetConnectionList();
list<CConnection*>::iterator it_con;
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been
modified to prompt user to save
UpdateAllViews (NULL); // indicate that sys. should redraw.
// DiagramEng (end)
}
void CDiagramEngDoc::OnEditPaste()
{
// TODO: Add your command handler code here
// DiagramEng (start)
list<CAnnotation*> &an_list = m_SystemModel.GetAnnotationList();
list<CAnnotation*> &an_list_cb = m_Clipboard.GetAnnotationList();
list<CAnnotation*>::iterator it_an;
list<CBlock*> &blk_list = m_SystemModel.GetBlockList();
list<CBlock*> &blk_list_cb = m_Clipboard.GetBlockList();
list<CBlock*>::iterator it_blk;
list<CConnection*> &con_list = m_SystemModel.GetConnectionList();
list<CConnection*> &con_list_cb = m_Clipboard.GetConnectionList();
list<CConnection*>::iterator it_con;
// Set flags
SetModifiedFlag(TRUE); // set the doc. as having been modified to
prompt user to save
UpdateAllViews (NULL); // indicate that sys. should redraw.
// DiagramEng (end)
}
Annotations 991
1. CDiagramEngDoc::SaveSystemModelToList()
2. CSystemModel::CSystemModel(const CSystemModel &sm)
3. CSystemModel::operator=(const CSystemModel &sm)
void CDiagramEngDoc::SaveSystemModelToList()
{
int i;
int lst_size;
list<CSystemModel*>::iterator it_sm;
list<CSystemModel*>::iterator it_sm_erase_start;
CSystemModel *pSystemModel = NULL;
pSystemModel->DeleteBlockList();
pSystemModel->DeleteConnectionList();
pSystemModel->DeleteSourceOutputLoopLists();
pSystemModel->DeleteAnnotationList();
m_lstSystemModel.push_back(pSystemModel);
}
// Delete unwanted elements and then erase the portion of the list
…
992 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
// Create a new system model using the class member m_SystemModel and
the copy constructor
pSystemModel = new CSystemModel(m_SystemModel);
// Put address of the newly created system model at the back of
m_lstSystemModel
m_lstSystemModel.push_back(pSystemModel);
// If list too big delete the first element and take it off the list
…
// Adjust index
…
// Adjust Undo/Redo flags
…
}
// CString DATA
…
// LIST DATA
// Copy Block List
…
// Copy Connection List
…
The developer will have noticed the call to GetAnnotationListCopy() in the copy construc-
tor given earlier; this accessor function obtains the address of the annotation list which is then
assigned to the pointer-to-a-constant list of CAnnotation pointers, i.e., “const list<CAnnotation*>
*plstAnnotation”. Hence, add a public member function to the CSystemModel class with the pro-
totype, const list<CAnnotation*> *CSystemModel::GetAnnotationListCopy()
const, and edit it as shown.
The function is necessarily constant, in order to satisfy the constant nature of the incoming “sm” argu-
ment to the copy constructor, and hence the address of the annotation list returned, “&m_lstAnnotation”
must also be constant, as reflected by the accessor method’s return type: “const list<CAnnotation*> *”.
The retrieved annotation list is iterated over and new CAnnotation objects created using the
CAnnotation copy constructor; the iterator, “it_an”, is dereferenced once to access the pointer-to-
CAnnotation, and then a second time to access the CAnnotation object, which is ultimately passed
as a reference-to-a-constant-CAnnotation object to the copy constructor. The address of the new
annotation object, stored in “pAn”, is then added to the member list, “m_lstAnnotation”, of the
CSystemModel object under construction.
// INTEGER DATA
…
// DOUBLE DATA
…
// CString DATA
…
// LIST DATA
…
The developer will notice that first the underlying annotation list is deleted through the call to
DeleteAnnotationList(). Then, the annotation list of the incoming system model object,
“sm”, is retrieved and iterated over, and new CAnnotation objects created and added to the underly-
ing system model object’s annotation list, “m_lstAnnotation”.
TABLE 30.5
CAnnotation Serialization Data: Data Type, Member
Variable, and Description
Data Type Member Variable Description
int m_iSize Size of text
int m_ptPosition.x Annotation position x-coordinate
int m_ptPosition.y Annotation position y-coordinate
int m_sOutputTextSize.cx Width of output text
int m_sOutputTextSize.cy Height of output text
CFont m_fAnnotationFont Annotation font
CString m_strAnnotation Annotation string identifier
CString m_strColor Color: Red, Green, Blue, Black
CString m_strFont Font string identifier
CString m_strStyle Style: Regular, Italic, Bold, Bold Italic
CAnnotation class has been added to the project and a list of annotations, “m_lstAnnotation”, is
stored in the CSystemModel class, the serialization-centric code needs to be augmented in various
locations. The implementation topics are as follows:
30.10.1.1 CAnnotation::WriteAnnotationDataToFile()
The responsibility of the CAnnotation::WriteAnnotationDataToFile() method is to
write the serialization data to the output file stream, “ofstream”, object (denoted “fout” in the fol-
lowing); this works in a similar way to existing data-writing functions by first writing the identifier,
“annotation”, followed by the member variable values.
1. Add a public member function to the CAnnotation class with the prototype, void
CAnnotation::WriteAnnotationDataToFile(ofstream &fout), and edit it
as shown.
void CAnnotation::WriteAnnotationDataToFile(ofstream &fout)
{
char strVar[L_FILE_IO_STR]; // char array used with fout
strcpy(strVar, m_strAnnotation);
fout << strVar << endl;
strcpy(strVar, m_strColor);
fout << strVar << endl;
strcpy(strVar, m_strFont);
fout << strVar << endl;
strcpy(strVar, m_strStyle);
fout << strVar << endl;
}
The developer will notice the use of the character array, “strVar”; this is required since the ofstream
object, “fout”, with the operator “<<” is not designed to write out CString data, but rather a char-
acter array.
2. Include the file-stream header file, “fstream.h”, (#include <fstream>) prior to the class
declaration in the “Annotation.h” header file since it declares an “ofstream” object, “fout”.
3. In addition, add “using namespace std” beneath the included header file to cater for the
“ofstream” identifier as shown in bold in the header file in the following.
4. Finally, include the “Block.h” header file at the top of the “Annotation.cpp” source file since
the length of a string used in file input/output, L_FILE_IO_STR, is defined in this file.
#if !defined(AFX_ANNOTATION_H__DE65EA5D_AEE5_456C_97DD_9BEA04E3ACE2__
INCLUDED_)
#define AFX_ANNOTATION_H__DE65EA5D_AEE5_456C_97DD_9BEA04E3ACE2__INCLUDED_
class CAnnotation
{
void WriteAnnotationDataToFile(ofstream &fout);
…
};
#endif // !defined(AFX_ANNOTATION_H__DE65EA5D_
AEE5_456C_97DD_9BEA04E3ACE2__INCLUDED_)
30.10.1.2 CAnnotation::ReadAnnotationDataFromFile()
The annotation data may be read from a model-data file through the use of a data-reading function
and an input file stream, “ifstream”, object (denoted “fin” in the following). Hence, add a public
Annotations 997
member function to the CAnnotation class with the prototype, void CAnnotation::Read
AnnotationDataFromFile(ifstream &fin), and edit it as shown. The reading and writing
actions are required to be in the same order for consistency.
// Annotation string
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If string not empty then assume string on line is correct
if(strcmp(strVar, “”) != 0)
{
m_strAnnotation = strVar;
break;
}
}
// Annotation color
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If string not empty then assume string on line is correct
if(strcmp(strVar, “”) != 0)
{
m_strColor = strVar;
break;
}
}
// Font string
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If string not empty then assume string on line
is correct
if(strcmp(strVar, “”) != 0)
{
m_strFont = strVar;
break;
}
}
// Style string
while(fin.getline(strVar, L_FILE_IO_STR, ‘\n’) )
{
// If string not empty then assume string on line is
correct
998 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
if(strcmp(strVar, “”) != 0)
{
m_strStyle = strVar;
break;
}
}
The developer will notice the call to CreateAnnotationFont() after the CAnnotation data have
been read in from a file. This is required since the CFont member variable, “m_fAnnotationFont”,
is not serialized, but needs to be created using the other member variables (see the definition of the
CAnnotation::CreateAnnotationFont() method provided in Section 30.3.2.4).
TABLE 30.6
CSystemModel Serialization Data: Data Type, Member Variable,
and Description
Data Type Member Variable Description
int m_iDrawAnnotationFlag Draw annotation flag
int m_iModelStatusFlag Model status flag
int m_iNOutputBlocks No. of model output blocks
int m_iNSourceBlocks No. of model source blocks
int m_iNSourceLoopTours No. of source-to-loop-repeated-block tours
int m_iNSourceOutputTours No. of source-to-output-block tours
double m_dATOL Absolute error tolerance parameter
double m_dRTOL Relative error tolerance parameter
double m_dTimeStart Simulation start time
double m_dTimeStepSize Time-step size
double m_dTimeStop Simulation stop time
CString m_strIntegrationMethod Integration method: Euler, Runge–Kutta
CString m_strModelName Name of current system model
CString m_strTimeStepType Time-step type: fixed-step or variable-step
CString m_strWarning Diagnostic warning messages
list<int*> m_lstSourceLoopTour List of arrays of source-loop-block tours
list<int*> m_lstSourceOutputTour List of arrays of source-output-block tours
list<CAnnotation*> m_lstAnnotation List of annotations
list<CBlock*> m_lstBlock List of blocks
list<CConnection*> m_lstConnection List of connections
list<CConnection*> m_lstConnectionError List of disconnected connections
CAnnotation objects, “m_lstAnnotation”. The data-writing function simply iterates through the list
of annotations and calls the CAnnotation::WriteAnnotationDataToFile() method upon
the pointer-to-CAnnotation, as shown in bold in the following.
and whose data are to be read in from a file. Here, the function is augmented as shown in bold in the
following to filter a CAnnotation object, using the string identifier, “annotation”; thereafter, a new
CAnnotation object is constructed and its data read using the ReadAnnotationDataFromFile()
method (the ellipsis (“…”) denotes omitted and unchanged code).
The developer will recognize that as the model data file, e.g., “model_data.txt”, uses identifiers denot-
ing objects to be reconstructed, e.g., “system_model”, “connection”, etc., and whose data are subse-
quently read, no change needs to be made to old model files that were saved with a previous version of
the code. If, upon reading an old file, the recently introduced identifier reflecting a new class instance
is not detected, then the corresponding object is simply not constructed. However, if the identifier is
present, as it may be in the event that a new class instance is used, here e.g., a CAnnotation object,
then the newly introduced filter, e.g., “if(strcmp(strLine, “annotation”) == 0)”, will
detect the object to be reconstructed.
void CDiagramEngDoc::OnFileOpen()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int btnSel; // button selection
…
// Create a new document
if( (m_SystemModel.GetBlockList().size() != 0) || (m_SystemModel.
GetConnectionList().size() != 0) || (m_SystemModel.
GetAnnotationList().size() != 0) )
{
sMsg.Format(“ A system model already exists. \n Do you want to
erase it? \n”);
btnSel = AfxMessageBox(sMsg, MB_YESNOCANCEL, 0);
if(btnSel == IDYES)
{
// Delete System Model Lists
m_SystemModel.DeleteBlockList();
m_SystemModel.DeleteConnectionList();
1002 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
m_SystemModel.DeleteSourceOutputLoopLists();
m_SystemModel.DeleteAnnotationList();
…
}
else if(btnSel == IDNO)
{
…
}
else if(btnSel == IDCANCEL)
{
return
}
}
…
// DiagramEng (end)
}
void CDiagramEngApp::OnFileRecentFileOpen(UINT i)
{
int btnSel; // button selection
…
// CHECK FOR THE EXISTENCE OF A MODEL ON THE DOC: GET THE SYSTEM
MODEL BY REFERENCE!
CSystemModel &system_model = pDoc->GetSystemModel();
if( (system_model.GetBlockList().size() != 0) || (system_model.
GetConnectionList().size() != 0) || (system_model.
GetAnnotationList().size() != 0) )
{
sMsg.Format(“ A system model already exists. \n Do you want to
erase it? \n”);
btnSel = AfxMessageBox(sMsg, MB_YESNOCANCEL, 0);
if(btnSel == IDYES)
{
// Delete System Model Lists
system_model.DeleteBlockList();
system_model.DeleteConnectionList();
system_model.DeleteSourceOutputLoopLists();
system_model.DeleteAnnotationList();
// Set flags and redraw the doc
pDoc->SetModifiedFlag(TRUE); // set the doc. as having
been modified to prompt user to save
pDoc->UpdateAllViews(NULL); // indicate that sys. should
redraw.
}
else if(btnSel == IDNO) // Create a new document for the model
{
…
}
else if(btnSel == IDCANCEL)
{
return;
}
}
…
}
Annotations 1003
FIGURE 30.6 The diagrammatic representation of the Lorenz equations showing mathematical annotations.
The developer will notice that the only modification in both functions are to extend a condi-
tional check and call DeleteAnnotationList() on the CSystemModel object, “m_SystemModel”;
otherwise, the functions remain unchanged.
Now the user can save and restore a model diagram with annotation information describing
model entities, as shown in the diagrammatic representation of the Lorenz equations, in Figure 30.6.
The system of equations, representing a simplified model of convection rolls in the atmosphere,
repeated for convenience here is
where
x(t), y(t), and z(t) are spatial coordinates that are functions of time t
σ, r, and b are the Prandtl number, Rayleigh number, and a quantity related to the height of the
fluid being modeled, respectively: σ, r, b > 0 [4]
30.11 SUMMARY
The introduction of annotations to the DiagramEng project required the addition of two new classes
that work together in the FormatAnnotation() function: CAnnotation, which manages the
annotation object variables and methods, and CAnnotationDialog, used to present a dialog window
and obtain user-specified annotation settings.
1004 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
Functionality was added to the project to generate a font list, through a GenerateFonts() method
that calls the EnumFontFamiliesEx() function to request a list of the available system fonts,
where the EnumFontFamProc() callback function, passed as an argument, receives data describ-
ing the fonts [2]. The creation of the actual font is performed in the CreateAnnotationFont()
method of the CAnnotation class, where, typically, the class member variable, “m_fAnnotationFont”,
is passed as an argument by reference; the function is called from within FormatAnnotation(),
the CAnnotation copy constructor, the ReadAnnotationDataFromFile() function and the
ScaleSystemModel() method.
Annotations are drawn via the DrawSystemModel() and DrawAnnotation() functions,
where the latter calls TextOut() to display the text on the screen: GetOutputTextExtent()
is also called to get the width and height of the annotation. Annotations are deleted
through a call to OnDeleteItem() followed by DeleteAnnotation(), or by invoking
OnEditDeleteGroupedItems(). The movement of annotations is performed by one of three
mechanisms: (1) FineMoveItem() and FineMoveAnnotation(), (2) TrackItem() and
TrackAnnotation(), or (3) TrackMultipleItems().
The View menu functionality was augmented to cater for the existence of annota-
tions and included the Auto Fit Diagram, Zoom In/Out, and Reset Diagram actions. The
Auto Fit Diagram action uses the DetermineDiagramCoordinateExtrema() and
DetermineDiagramCoordinateMinima() functions, which where both extended to include
retrieval of the extreme points of annotations. The Zoom In/Out actions involved extending the
ScaleSystemModel() function to rescale and reposition the annotation. The resetting of the
initial diagram geometry involves calling the ScaleSystemModel() function.
The Format menu was extended through the use of the OnFormatShowAnnotations() and
OnUpdateFormatShowAnnotations() functions to allow the user to show or hide diagram
annotations, and changes were made to the DrawSystemModel() method, the CSystemModel
copy constructor, and the assignment operator.
The Edit menu–based actions, Cut, Copy, and Paste, required the introduction of a CAnnotation
copy constructor and augmenting the CDiagramEngDoc, OnEditCut(), OnEditCopy(), and
OnEditPaste() methods to work with the list of annotations, “m_lstAnnotation”. The Undo
and Redo actions required changes to the SaveSystemModelToList() function and the
CSystemModel copy constructor and the assignment operator.
The serialization of the CAnnotation class data involved the introduction of the
ReadAnnotationDataFromFile() and WriteAnnotationDataToFile() member
functions, wherein the former the CreateAnnotationFont() function is called to cre-
ate the annotation font from member variable values. The CSystemModel reading and writ-
ing methods were also augmented to cater for annotations. Finally, the file open methods,
CDiagramEngDoc::OnFileOpen() and CDiagramEngApp::OnFileRecentFileOpen(),
where altered to call DeleteAnnotationList() in the event that the user chooses to over-
write an existing model.
REFERENCES
1. Chapman, D., Teach Yourself Visual C++ 6 in 21 Days, SAMS Publishing, Indianapolis, IN, 1998.
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
3. Ogata, K., Modern Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.
4. Strogatz, S. H., Nonlinear Dynamics and Chaos: With Applications to Physics, Biology, Chemistry, and
Engineering, Addison-Wesley, Reading MA, 1994.
31 Tools Menu
31.1 INTRODUCTION
At present, the user can perform basic modeling and simulation activities for time-based linear
and nonlinear dynamical systems represented by differential equations that may be numerically
integrated to yield generalized coordinates and their time derivatives for insightful time periods.
However, it is important for the user to know more detail about the system and process memory
usage, e.g., the working set size and pagefile usage.
1. Download the “psapi.h”, “psapi.dll”, and “psapi.lib” files from an appropriate website.
2. Add all three files to the DiagramEng directory containing all project files.
3. Include the “psapi.h” header file (#include “psapi.h”) at the top of the “DiagramEngDoc.
cpp” source file, in which the CDiagramEngDoc::OnToolsDiagnosticInfo()
function is defined.
4. Add the “psapi.lib” module to the project by selecting Project then Settings, choose the
Link tab, and in the “Object/library modules” Edit box, add “psapi.lib” to the end of the
list of libraries, as shown in Figure 31.1. This must also be done for the Win32 Release
configuration of the application.
In addition, the “windows.h” header file is required for various type and function definitions used
for memory-information-based functions. Hence, include “windows.h” (#include <windows.h>) at
the top of the “DiagramEngDoc.cpp” source file prior to the inclusion of the “psapi.h” header file.
1005
1006 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 31.1 Project Settings dialog window showing the inclusion of the (highlighted) “psapi.lib” module.
The function GetCurrentProcess() will be used to get a pseudo handle to the current process,
which is then passed to GetProcessMemoryInfo() to obtain information about memory usage
of the specified process, which is recorded in the PROCESS_MEMORY_COUNTERS structure [1].
The idea for this work follows that presented in an article by Microsoft Corp. [3].
The MEMORYSTATUS structure, available from Ref. [1] shown in the following, is used to dis-
play information about the state of physical and virtual memory. The “dwLength” field will not be
used on the dialog window as it is the size of the structure itself: the memory load, “dwMemoryLoad”,
is a percentage value, and the remaining values denote amounts in bytes.
Tools Menu 1007
The function GlobalMemoryStatus() obtains information about the computer system’s current
usage of both physical and virtual memory and stores this information in the MEMORYSTATUS
structure [1].
31.2.3.1 Insert a New Dialog Window and Add All Necessary Controls
Insert a new dialog resource and set the ID of the dialog to IDD_DIAGNOSTIC_INFO_DLG and
the caption to DiagnosticInformationDialog. Leave the OK and Cancel buttons on the dialog. Add
controls as shown in Table 31.1 and place them on the dialog window as shown in Figure 31.2: the
developer will notice that static text strings are right-justified, and borders have been placed around
the empty strings to contain the memory-related information.
TABLE 31.1
DiagnosticInformationDialog Dialog Window Controls:
Objects, Properties, and Settings
Object Property Setting
Group Box ID ID_DIAGNOSTIC_INFO_DLG_GB_PROCMEM
Caption Process Memory Data (Kilobytes)
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_PFC
Caption Page Fault Count:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_PFC
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_PWSS
Caption Peak Working Set Size:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_PWSS
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_WSS
Caption Working Set Size:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_WSS
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_QPPPU
Caption Quota Peak Paged Pool Usage:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_QPPPU
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_QPPU
Caption Quota Paged Pool Usage:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_QPPU
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_QPNPPU
Caption Quota Peak Non Paged Pool Usage:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_QPNPPU
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_QNPPU
Caption Quota Non Paged Pool Usage:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_QNPPU
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_PFU
Caption Pagefile Usage:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_PFU
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_PPFU
Caption Peak Pagefile Usage:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_PPFU
Caption Empty string
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_PMUP
Caption Physical Memory Used by Process:
Static Text ID ID_DIAGNOSTIC_INFO_DLG_TXT_PMUP
Caption Empty string
Group Box ID ID_DIAGNOSTIC_INFO_DLG_GB_SYSMEM
Caption System Memory Data (Kilobytes)
Static Text ID ID_DIAGNOSTIC_INFO_DLG_STXT_ML
Caption Memory Load:
Tools Menu 1009
FIGURE 31.2 DiagnosticInformationDialog dialog window showing the controls as specified in Table 31.1.
TABLE 31.2
Dialog Window Controls, Variable Names, Categories, and Types,
for the IDD_DIAGNOSTIC_INFO_DLG (Dialog Window) Resource
Control Variable Name Category Type
ID_DIAGNOSTIC_INFO_DLG_TXT_PFC m_strPageFaultCount Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_PWSS m_strPeakWorkingSetSize Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_WSS m_strWorkingSetSize Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_QPPPU m_strQuotaPeakPagedPoolUsage Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_QPPU m_strQuotaPagedPoolUsage Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_QPNPPU m_strQuotaPeakNonPagedPoolUsage Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_QNPPU m_strQuotaNonPagedPoolUsage Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_PFU m_strPagefileUsage Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_PPFU m_strPeakPagefileUsage Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_PMUP m_strPhysicalMemoryUsedProcess Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_ML m_strMemoryLoad Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_TPM m_strTotalyPhysicalMemory Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_APM m_strAvailPhysicalMemory Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_TPFM m_strTotalPagefileMemory Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_APFM m_strAvailPagefileMemory Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_TVMU m_strTotalVirtualMemoryUser Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_AVMU m_strAvailVirtualMemoryUser Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_TVM m_strTotalVirtualMemory Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_VMU m_strVirtualMemoryUsed Value CString
ID_DIAGNOSTIC_INFO_DLG_TXT_PMU m_strPhysicalMemoryUsed Value CString
TABLE 31.3
Objects, IDs, Class, and Event-Handler Functions
for the CDiagnosticInfoDialog Class
Object ID Class COMMAND Event Handler
OK button IDOK (default) CDiagnosticInfoDialog OnOK()
Cancel button IDCANCEL (default) CDiagnosticInfoDialog OnCancel()
However, if the developer desires functions for potential future use, then OnOK() and OnCancel()
may be added as shown in Table 31.3, and within which the base class functions CDialog::OnOK()
and CDialog::OnCancel() are called, respectively.
void CDiagramEngDoc::OnToolsDiagnosticInfo()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int bik = 1024; // no. of bytes in 1 kilobyte
char strVar[50]; // string var used in sprintf() statements
CString sMsg = “”; // msg string
CString sMsgTemp = “”; // temp msg string
DWORD physMemUsed; // physical memory used
DWORD virtualMemUsed; // virtual memory used
HANDLE hProcess; // handle to the process
PROCESS_MEMORY_COUNTERS pmc; // structure containing memory
statistics for a process
MEMORYSTATUS msMemStatus; // structure containing info about
current state of physical and virtual memory
// AfxMessageBox(“\n CDiagramEngDoc::OnToolsDiagnosticInfo()\n”,
MB_OK, 0);
1012 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 31.3 Process memory statistics and system physical and virtual memory details: memory is quoted
in kilobytes.
The interested reader should consult an article titled “How to determine CPU and memory con-
sumption from inside a process?” published on Stackoverflow [4] for details concerning process-
based CPU and memory usage: some ideas presented here follow this work. Of note, are the
following details: (1) the physical memory used by a process is in fact the “working set size”
(“pmc.WorkingSetSize”), (2) the total virtual memory is the total pagefile size (“msMemStatus.
dwTotalPageFile”), (3) the virtual memory used is the difference between the total pagefile and
available pagefile sizes (“virtualMemUsed = msMemStatus.dwTotalPageFile-msMemStatus.
dwAvailPageFile”), and (4) the physical memory used is the difference between the total physi-
cal memory and the available physical memory (“physMemUsed = msMemStatus.dwTotalPhys -
msMemStatus.dwAvailPhys”) [4].
Finally, upon running the DiagramEng application and choosing Diagnostic Info under the Tools
menu, the memory statistics for the current application process, and the system physical and virtual
memory details, are displayed as shown in Figure 31.3. The interested developer may compare
the memory statistics presented in the dialog window with those of Task Manager or an equivalent
application that presents information about applications and processes and common performance
measures.
The reader will notice that all memory-based values are presented in kilobytes (KB) for
convenience: if a field has the value “0”, this indicates that less that 1 KB is in use rather than 0 bytes
in total. The page fault count is a DWORD value, which is a 32-bit unsigned integer or the address of
a segment and its associated offset [1]: here, it can be considered to be an integer value. The memory
load is a DWORD value that represents the percentage value of memory use.
Further to the earlier discussion, from Figure 31.3, the memory-conscious developer will notice
that the “working set size” and the “physical memory used by the process” are the same value (they
share the same variable, “pmc.WorkingSetSize”). In addition, the “total virtual memory” is the
same as the “total pagefile memory” (“msMemStatus.dwTotalPageFile”): both of these fields are
displayed for clarity and convenience.
31.3 SUMMARY
The Diagnostic Information entry under the Tools menu invokes the CDiagramEngDoc::
OnToolsDiagnosticInfo() method within which two structures are used to store memory
usage information: (1) PROCESS_MEMORY_COUNTERS contains memory statistics for a process
and (2) MEMORYSTATUS retains information about the current state of the system physical and
Tools Menu 1015
REFERENCES
1. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
2. Microsoft Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
3. Microsoft Developer Network, Collecting memory usage information for a process (Windows),
http://msdn.microsoft.com/en-us/library/ms682050(VS.85).aspx, 2008.
4. Stackoverflow, How to determine CPU and memory consumption from inside a process?,
http://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-
inside-a-process, (accessed August 2010).
32 Help Menu
32.1 INTRODUCTION
The Child frame–based Help menu has two entries, About DiagramEng and Using DiagramEng:
the former displays a dialog window with program, version number, and copyright information,
and the latter needs to present the instructions on how to use the DiagramEng application to the
user, complete with a discussion of all menu and toolbar-based functionality and typical real-
world engineering examples. The instructions should be presented as a Portable Document Format
(PDF) document and hence would require the user to have Adobe Reader [1] installed on the
client machine. The particular sections of the Using DiagramEng topics are Introduction, Menus,
Toolbars, Examples, Output, and Non-functional items. Finally, the Using DiagramEng instructions
are presented to the user by calling CreateProcess() to create a process invoking the Adobe
“Acrobat.exe” executable and opening the PDF document.
1017
1018 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
BOOL CreateProcess(
LPCTSTR lpApplicationName, // pointer to name of
executable module
LPTSTR lpCommandLine, // pointer to command
line string
LPSECURITY_ATTRIBUTES lpProcessAttributes, // process security
attributes
LPSECURITY_ATTRIBUTES lpThreadAttributes, // thread security
attributes
BOOL bInheritedHandles, // handle inheritance
flag
DWORD dwCreationFlags, // creation flags
LPVOID lpEnvironment, // pointer to new
environment block
LPCTSTR lpCurrentDirectory, // pointer to current
directory name
LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO
LPPROCESS_INFORMATION lpProcessInformation // pointer to
PROCESS_INFORMATION
);
The application name pointer, “lpApplicationName”, is set to NULL, since the command line
argument, “lpCommandLine”, is a concatenation of the executable name “Acrobat.exe”, the direc-
tory path denoting the location of the “UsingDiagramEng.pdf” and the PDF file (parameter)
itself. The pointer-to-LPSECURITY_ATTRIBUTES-type arguments, “lpProcessAttributes” and
“lpThreadAttributes”, are both NULL, indicating that the returned handle cannot be inherited by
child processes. The handle inheritance flag, “bInheritedHandles”, is set to FALSE, implying that
the new process does not inherit handles from the calling process. The DWORD, “dwCreatingFlags”,
argument specifies flags that control the priority class and the creation of the process: here, this
is set to zero, indicating no creation flags are used. The “lpEnvironment” argument is a pointer
to an environment block for the new process: here, it is NULL, specifying that the new process
uses the environment of the calling process. The “lpCurrentDirectory” argument is a pointer to a
string specifying the drive and directory for the child process: NULL implies that the new process
is created with the same drive and directory as the calling process. The STARTUPINFO struc-
ture, “lpStartupInfo”, is used to specify the appearance of the main window for the new process:
in the following code, the address of the structure-type variable is passed as an argument. The
PROCESS_INFORMATION structure, “lpProcessInformation”, is used to receive identification
information about the new process: the address of this structure-type variable is passed as an argu-
ment as shown in the following.
void CDiagramEngDoc::OnHelpUsingDiagramEng()
{
// TODO: Add your command handler code here
// DiagramEng (start)
int lenBuffer = 0; // length of buffer to get dir name
char *appName = NULL; // app name (not used since cmd
line used instead)
char cmdLine[1024] = “Acrobat.exe “; // cmd line, starting with the
app name
char dirPath[1024] = “”; // dir path
char dirPathExe[1024] = “”; // dir path including exe file name
CString sMsg = “”; // msg string
CString sMsgTemp = “”; // temp msg string
DWORD retval = 0; // return val of
GetCurrentDirectory()
PROCESS_INFORMATION pi; // PROCESS_INFORMATION structure
STARTUPINFO si; // STARTUPINFO structure
//AfxMessageBox(”\n CDiagramEngDoc::OnHelpUsingDiagramEng()\n”,
MB_OK, 0);
// DiagramEng (end)
}
The command line argument, “cmdLine”, is a character array that holds the complete statement
to invoke an executable file (here, “Acrobat.exe”), whose location is specified in the Path environ-
ment variable, where the parameter to the executable (here, “UsingDiagramEng.pdf”) is appended
to the directory path denoting its location. The help file, “UsingDiagramEng.pdf”, is to reside in
the same location as the “DiagramEng.exe” executable file; hence, the GetModuleFileName()
function may be called to determine the “full path and filename for the executable file contain-
ing the specified module” [2]. The arguments to the latter are (1) the handle to the module whose
executable filename is being sought, which is returned by AfxGetInstanceHandle(), (2) a
string pointer to contain the path of the executable (“DiagramEng.exe”), including its name, and
(3) the length of the character array buffer to hold the path, here, “dirPathExe”. However, since
“DiagramEng.exe” is part of the path contained by “dirPathExe”, the characters “DiagramEng.
exe” need to be removed when specifying the directory path for the PDF document: this is done
using strncpy().
Thereafter, to complete the command line, the individual strings are concatenated. However,
if the directory path does not contain white space, then there is no need to place quotes around
it, but if it does, then the quotes are necessary. The string construction is performed as follows:
(1) the initial characters are “Acrobat.exe ” (note the white space following “exe”), (2) “Acrobat.
exe” is then adjoined to an open quote, “, required for the directory path containing white spaces,
(3) “Acrobat.exe”, is then adjoined to the directory path, e.g., C:\Name\C++\Work\Diagram Eng
Project\DiagramEng, and (4) finally the PDF file is appended to the directory path, to result,
e.g., in the string:
Acrobat.exe “C:\Name\C++\WorkDiagram Eng Project\DiagramEng\UsingDiagramEng.pdf”
1022 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
FIGURE 32.1 The “UsingDiagramEng.pdf” document displayed when selecting Using DiagramEng from
the Help menu.
The ZeroMemory() function is called to fill a block of memory with zeros [2], and this should
be done after the declaration of the structures, “si” and “pi”, to make sure that known (nonspurious)
values reside in the structures.
Thereafter, the CreateProcess() function is called with the arguments discussed earlier: if
the Boolean return value is FALSE, then an error message is displayed informing the user to check
the location of the “UsingDiagramEng.pdf” help file and to make sure that the Path environment
variable for the “Acrobat.exe” executable is set correctly, with instructions on how to do so.
The WaitForSingleObject() function call is used to check the state of the specified object:
if the state is nonsignaled, the calling thread enters an efficient wait state [2]. Here, no waiting
is really required, and hence, the DWORD, “dwMilliseconds”, argument is set to 0. The call is
included here should this need to change in the future.
Finally, CloseHandle() is called to close the open handle of the newly created process (“pi.
hProcess”) and the primary thread of the newly created process (“pi.hThread”). This should be done
once the object is no longer required. MSDN indicated that “CloseHandle() invalidates the spec-
ified object handle, decrements the object’s handle count, and performs object retention checks” [2].
Now upon running the DiagramEng application and choosing Using DiagramEng from the Help
menu, the PDF document appears as shown in Figure 32.1.
32.4 SUMMARY
The selection of the Using DiagramEng entry under the Help menu invokes the CDiagramEngDoc::
OnHelpUsingDiagramEng() method that calls CreateProcess() to execute the “Acrobat.
exe” application with the “UsingDiagramEng.pdf” file as a parameter. The directory path of the
file is determined through the use of GetModuleFileName(), and this is concatenated with the
“Acrobat.exe” executable and the target file name “UsingDiagramEng.pdf” to form the command
line argument.
Help Menu 1023
REFERENCES
1. Adobe: http://www.adobe.com/products/acrobat/
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
3. Microsoft Developer Network, Creating processes, http://msdn.microsoft.com/en-us/library/
ms682512(VS.85).aspx, (accessed September, 2010).
33 Finalizing the Project
33.1 INTRODUCTION
The purpose of the development of the DiagramEng demonstration software application is to build
a foundation upon which extensions can be made to allow the efficient modeling and simulation of
mathematical and control-engineering-based problems. Most of the necessary key modules have
been implemented. However, some features are not present in this first version of the software and
include the implementation of the Subsystem, Subsystem In, Subsystem Out, and Transfer Function
blocks and development of the fourth-order Runge–Kutta numerical integration and variable-order
schemes with the associated tolerance parameter-based error-checking features, as shown in Table
33.1. The nonfunctional elements need to be either disabled or automatically invoke alternative
default behavior.
int CSystemModel::ValidateModelBlocks()
{
int error = 0; // error in model
int n_source = 0; // no. of model source blocks
int n_output = 0; // no. of model output blocks
int screen_out = 0; // flag to screen out non-functional blocks
CString blk_name; // blk name
CString sMsg; // string to be displayed
CString sMsgTemp; // temp msg.
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
1025
1026 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 33.1
Incomplete Application Items: Blocks, Menu Entries, and Dialog Window Fields
Item Status
Blocks Subsystem, Subsystem In, Subsystem Out, and Transfer Function blocks currently
do not perform data operations.
Model Menu The Build Subsystem entry does not function as subsystem blocks are not implemented.
Numerical Solver The absolute and relative error tolerance parameters are not currently used, the
Time-step Type is “Fixed-step” and the Integration Method is “Euler (1st Order).”
// CHECK THE NO. OF SOURCE BLOCKS AND OUTPUT BLOCKS IN THE MODEL
for(it_blk = m_lstBlock.begin(); it_blk != m_lstBlock.end(); it_blk++)
{
…
}
// Delete block
delete *it_blk; // delete actual block pointed to by it_blk
it_blk = m_lstBlock.erase(it_blk); // delete element at
offset it_blk in list (that held the block)
}
Finalizing the Project 1027
Now upon running the application and placing any of the nonfunctional blocks on the palette, these
are removed at the model validation stage accompanied by a message to the user. Alternatively, the
developer can disable the Common Blocks toolbar buttons corresponding to the nonfunctional blocks,
by invoking the Class Wizard, selecting the CDiagramEngDoc class and the appropriate toolbar but-
ton ID, and then deleting the event-handler function associated with the Command message.
void CDiagramEngDoc::OnModelBuildSubsystem()
{
// TODO: Add your command handler code here
// DiagramEng (start)
CString sMsg;
CString sMsgTemp;
// DiagramEng (end)
}
Alternatively, the developer can invoke the Class Wizard and select CDiagramEngDoc as the
class and ID_MODEL_BUILD_SUBSYSTEM as the ID, and delete the event-handler function,
CDiagramEngDoc::OnModelBuildSubsystem(), associated with the Command event message.
(a) (b)
FIGURE 33.1 The NumericalSolverDialog dialog window: (a) all fields enabled and (b) fields not used
shown disabled.
[4th Order]) fields show their default settings that are used regardless of the user selection. The
developer will recall that in the CIntegratorBlock::OperateOnData() function, a check
is made of the choice of the integration method and a message displayed informing the user of the
default fixed-step Euler integration scheme being used.
Go (F5). After using the application to perform modeling and simulation activities and then exit-
ing, the output in the Debug output window may be checked for the presence of memory leaks.
Usually, a message appears giving the developer an indication as to the type of leak that is present:
leaks commonly arise by not matching a delete operation to a corresponding new operation where
the two may not be of the same code locality, i.e., they may be present in different functions and
the intended control path was not executed. For more information about detecting memory leaks
involving the creation of a CMemoryState object and using the Checkpoint() member function,
the interested reader should consult the Detecting Memory Leaks topic available under the Help
menu of the Visual C++ IDE [1]. For information on the usage of the IDE-based Debugging tool,
the reader should consult Appendix D.
FIGURE 33.2 Project Settings dialog window showing the inclusion of the (highlighted) “psapi.lib” module.
1030 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
TABLE 33.2
Sample Directory Structure Showing Files
for Porting the Release Version of DiagramEng
Directory Subdirectory Files
DiagramEng Executable “DiagramEng.exe”
“UsingDiagramEng.pdf”
Data “model_data (lotka-volterra).txt”
“model_data (state equs).txt”
TABLE 33.3
DiagramEng Class Listing
Classes Classes Continued Classes Continued
CAboutDlg CDivideBlock CSignal
CAnnotation CDivideBlockDialog CSignalGeneratorBlock
CAnnotationDialog CGainBlock CSignalGeneratorBlockDialog
CBlock CGainBlockDialog CSubsystemBlock
CBlockLibDlg CIntegratorBlock CSubsystemBlockDialog
CBlockShape CIntegratorBlockDialog CSubsystemInBlock
CChildFrame CLinearFnBlock CSubsystemInBlockDialog
CClipboard CLinearFnBlockDialog CSubsystemOutBlock
CConnection CMainFrame CSubsystemOutBlockDialog
CConstantBlock CNewtonMethod CSumBlock
CConstantBlockDialog CNumericalSolverDialog CSumBlockDialog
CDerivativeBlock COutputBlock CSystemModel
CDerivativeBlockDialog COutputBlockDialog CTransferFnBlock
CDiagnosticInfoDialog COutputBlockView CTransferFnBlockDialog
CDiagramEngApp COutputSignalDialog CTreeDialog
CDiagramEngDoc CPort Globals
CDiagramEngView CPortPropertiesDialog
Finalizing the Project 1031
TABLE 33.4
Sample Directory Structure for the DiagramEng Source Code Showing File Extensions
Directory Subdirectory File Extensions
DiagramEng aps, clw, cpp, dll, dsp, dsw, h, lib, ncb, opt, pdf, plg,
rc, reg, txt
Data txt
Debug bsc, exe, idb, ilk, obj, pch, pdb, pdf, res, sbr
Release bsc, exe, idb, obj, pch, pdf, res, sbr
res bmp, ico, rc2
TABLE 33.5
Properties of the Computer System, Operating System, and
Software Used to Develop the DiagramEng Application
Computer System Operating System Software
Intel Core™ 2 Duo CPU
® Microsoft Windows XP Microsoft Visual C++® 6.0
E4400 @ 2.00 GHz Professional Microsoft Visual Studio 6.0
2.00 GHz, 1.00 GB of RAM Version 2002 Professional Edition
Physical Address Extension Service Pack 2 MSDN Library Visual Studio 6.0
sufficient to run exploratory simulations for which the DiagramEng application was designed. The
DiagramEng application was developed on a computer system and using an operating system and
software with the properties listed in Table 33.5.
33.6 SUMMARY
The finalizing of the DiagramEng project involves the prevention of usage or disabling of nonfunc-
tional elements, e.g., blocks, menu items, and dialog window fields, or providing alternative default
behavior. The preparation of the source and executable files involves a check for memory leaks
using a Debug-build configuration of the application and the running of the debugger and observing
the messages in the Debug output window. Alternatively, a CMemoryState object can be created and
the Checkpoint() member function used for memory leak detection. Any required modules need
to be included using the “Object/library modules” edit box of the Link pane of the Project Setting
dialog window. Finally, sample directory structures are provided to organize the application files.
REFERENCES
1. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
2. Microsoft Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
34 Conclusion
34.1 INTRODUCTION
At the beginning of the text, the contents of the project were presented, which included an
introduction that described the developmental details of all the chapters in the book separated
into three main parts: User Interaction, Model Computation and Data, and Refinement. In addi-
tion, at the end of each chapter, a summary was provided that reviewed the specific design
and implementation details, including various classes and methods that were added to build func-
tionality into the DiagramEng project. Here, a brief review is made of the development process,
summarizing the implementation steps and providing suggestions for improving the software for
future extension.
1033
1034 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
controls, (2) creation of a class for the dialog, (3) attachment of variables to the dialog controls,
(4) attachment of functionality to the dialog buttons, (5) initialization of class member variables,
and (6) the addition of a parameter input function to create an instance of the appropriate block
dialog window. These six steps were used repeatedly throughout the project when a dialog resource
was added and data required to be updated its associated underlying class. A conversion of string
input to double data was also required for various dialog resources since the application operates on
numerical data that are initially entered by the user as a string.
The moving of multiple items was implemented through a sequence of actions that involved the
construction of a CRectTracker object used to circumscribe diagram entities within a rectangular
rubber-band-like region: initially the region is defined, and then upon selecting the interior of the
region, the entities may be moved with the mouse.
A Tree View control was added to the project by creating a dialog resource, attaching a class to
the dialog, attaching a variable to the dialog to display Tree View items, performing initialization,
and inserting icons to be displayed for the Tree View leaves. The Tree dialog window was then
docked to the Main frame manually, since the CTreeDialog class is inherited from the CDialog base
class, rather than the CDialogBar class.
The Context menu was extended to allow the setting of the properties of the numerical solver,
blocks and ports, where the appropriate dialog window was invoked based upon the (contextual)
location of the point at which the right-mouse-button-down event occurred. In addition, the dele-
tion of blocks and ports via the Context menu was implemented. The drawing of ports and port
signs was added and the port icons conditionally displayed, depending on the attachment of a
connection.
Finally, key-based item movement and the reversing of block direction allows the user to present
a diagram with horizontal and vertical connections and feed-forward and feedback control paths
such that typical control-engineering-like diagrams may be drawn, the computation of which is
pursued in the following section.
A model diagram possessing a feedback loop was computed using Newton’s method that involved
the construction of, in general, a system of nonlinear equations of the form, F(x(t)) = x(t) − f(x(t)) = 0,
where F(x(t)) is the generalized system vector, x(t) is the generalized output signal vector, and
f(x(t)) is the generalized block operation function vector, and a solution or root, xr(t), to the system
is sought, which is a vector of system signals at the current time-step. Examples of real-world prob-
lems were then computed and involved linear and nonlinear differential equations from the domains
of mechanical engineering and nonlinear dynamics.
An Edit box was placed on the Common Operations toolbar to display both the simulation time
during a running experiment and the total execution time at the end of the simulation: the updating
of the time values is initiated from the two main signal propagation functions.
The final development activity of the Model Computation and Data phase involved serialization,
i.e., the writing and reading of class member data, to and from a file, respectively. Event-handler
functions were added for the Main frame and Child frame based window menus to initiate the
serialization process, where all system-model-based data and contained class data are recorded. In
addition, the initial output signal of a Divide block and the recorded data of an Output block were
also serialized.
34.2.3 Refinement
The Refinement phase of the project involved adding typical features found in Windows-based
applications that make for a more intuitive, consistent, and complete modeling and simulation user
experience. Printing and print previewing functionality was implemented by adding code to three
key CView-based methods: OnBeginPrinting(), to set the maximum number of pages to be
printed, OnPrepareDC(), to prepare the device context and set mapping modes, and OnDraw(),
to perform a transformation and scaling between the window and viewport rectangles before draw-
ing the model diagram.
The implementation of a scrolling view was required since a model diagram may take up more
than the physical viewing area. The original CView-based class was then derived from CScrollView,
and numerous changes were required throughout the code to convert between device points and
logical points, given changes in the scroll position. Functionality to automatically fit the viewport
scale to the physical window, to zoom in and out of a view, and to reset the original diagram geom-
etry was also added.
The Edit menu was then augmented with the typical entries found in Windows-based
applications, i.e., Undo, Redo, Cut, Copy, Paste, and Select All. A clipboard object was used
to make copies of blocks and connections of a system model through the relevant class copy
constructors. The Undo and Redo actions involved creating a list of system model pointers to
record the addresses of system models. A copy constructor was introduced to make a copy of the
system model, “m_SystemModel”, of the CDiagramEngDoc class, to be stored in the list, and
an assignment operator used to assign a stored list-based system model to the member object,
“m_SystemModel”.
Functionality was then added to allow the user to annotate a model diagram through the use of
an annotation class and a dialog window to set key member attributes: a list of the available system
fonts is displayed and the font created based on user-selected properties. The annotations are stored
in the system model class, and hence, editing and serialization actions were augmented to cater for
the new data structure.
The Tools menu was then completed with a diagnostic information entry used to display pro-
cess, physical and virtual memory usage information. The Help menu was finalized with a Using
DiagramEng entry that upon selection invokes a process to display application instructions to the
user. Finally, the project was completed by providing default settings for certain controls, disabling
of nonfunctional items, checking for memory leaks using a Debug-build configuration of the appli-
cation, and organizing the source and executable code.
1036 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
34.3 IMPROVEMENTS
At the design stage of the project, not all implementation complexities can be fully appreciated, and as
the development progressed, various problems arose suggesting an improvement could be made in proj-
ect structure and software function. The following ideas indicate how the software can be improved.
The developer can see from the previous list that the method of object construction with the
new pointer variable is naturally more complicated and the changes to existing code would
be numerous and possibly problematic at a time after the design stage. However, a carefully
designed class hierarchy before coding commences will end up saving more development time
than that used to make the necessary modifications to implement a structural change at a later
stage in the project.
Common Blocks toolbar, e.g., it could be used to choose the type of connection objects used to link
blocks together: straight lines, spline curves, or a connection with integrated bend points.
In addition, certain controls are not operational, others result in default functionality, and four blocks
currently do not have working OperateOnData() functions. These nonfunctional items could be
deleted, disabled, or completed with at least a functional purpose (time permitting). Various enumerated
type values are also redundant and could possibly be removed, e.g., EBlockDirection has two fields,
“e_up” and “e_down”, which are currently not in use but could possibly be used at a later stage.
TABLE 34.1
Naming Convention for Class Member
Variables for Common Types and an Example
of Its Use
Type Convention Example Used
Boolean m_b m_bChkBoxConstBlock
Control m_ctl m_ctlFontList
CPoint m_pt m_ptPosition
CString m_str m_strConstValue
Double m_d m_dConstMatrix
Enumerated m_e m_eBlockDirection
Floata m_f m_fValue
Integer m_i m_iNrows
List m_lst m_lstBlock
Object or instance m_o or m_ m_SystemModel
Pointer m_p m_pParentSystemModel
Reference m_r m_rRefToBlock
Vector m_vec m_vecInputPorts
Window m_wnd m_wndToolBar
modify the variable value: where this may not be true is when the underlying object is retrieved by
reference with the intention to modify its value. In some cases, the suffix “Copy” can be added to
the function name to explicitly indicate that a copy is being retrieved: however, the function proto-
type should suffice. In addition, accessor methods should be added at the time when the member
variable itself is being added to a class. The constant nature of member functions can make it easier
to write copy constructors, since the latter is declared to be constant and hence any methods that it
calls should also be constant.
make the conversion from the original CView class. In hindsight, using the CView class to begin
with may actually have been the right choice, since then the complexity of dealing with device
and logical point conversion and accommodating for the change in scroll position would not be a
concern. Hence, the developer needs to make a decision between introducing possibly unwanted
complexity earlier in the project, but having the convenience of the appropriate base class, or
making a conversion later in the project to the appropriate base class, after initial functionality
is shown to work.
34.3.9 Serialization
The serialization of class member variables should be performed in a structured manner such that
the addition of more classes to the project as it evolves should not affect the existing data reading
and writing functions of other classes. This allows serialization to be performed at a relatively early
stage in the project, for class member data that need to be saved and then later restored to perform
various activities that would take too long to manually reinstate. For example, the Lorenz system in
diagrammatic form is time consuming to redraw, and if software testing is to be conducted at the
end of each developmental stage, then being able to save and restore this diagram would be more
convenient. However, in general, serialization should be left till a later stage in the development
after the class structure is more stable or complete.
Time Period
Project Activity 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Object Oriented Analysis & Design 1
Initial Graphical User Interface 2
Placing an Edit Box on a Toolbar 24
Constructing Blocks 3
Constructing Block Ports 4
Constructing Connections 5
Automatic Block Placement 7
Connection-Based Bend Points 8
Block Dialog Windows 9
Conversion of String Input to Double Data 10
Addition of a Tree View Control 12
Moving Blocks & Connections 6
Moving Multiple Items 11
Key-Based Item Movement 16
Block Operations 21
Context Menu Extension 14
Setting Port Properties 15
Reversing Block Direction 17
Model Validation 18
Non-Feedback-Based Signal Propagation 19
Graph Drawing 20
Preparation for Feedback-Based Signal 22
Propagation
Printing & Print Preview 27
Implementing a Scroll View 28
Feedback-Based Signal Propagation 23
Annotations 30
Tools Menu 31
Edit Menu 29
Serialization 25
Help Menu 32
Finalizing the Project 33
FIGURE 34.1 A possible Gantt chart showing the order and concurrency of software development activities.
34.4 SUMMARY
A review of the DiagramEng software prototype development project was made that summarized
all the development activities of the three main phases: User Interaction, Model Computation and
Data, and Refinement. Suggested improvements included: (1) a more thorough object-oriented
analysis and design stage employing ADTs and hierarchy-navigating pointers used to retrieve the
correct document address, (2) the usage of a common development environment and adding classes
carefully observing code locality, (3) the disabling of incomplete features, (4) a rigorous naming
convention, (5) the usage of constant functions where possible, (6) consideration of program state,
(7) observing the power of generic containers, (8) careful selection of base classes, (9) serialization
to be performed toward the end of the project, and, finally, (10) organizing the project carefully to
perform activities concurrently whenever possible.
1042 Software Application Development: A Visual C++ ®, MFC, and STL Tutorial
REFERENCES
1. Chapman, D., Teach Yourself Visual C++ 6 in 21 Days, Sams Publishing, Indianapolis, IN, 1998.
2. Press, W. H., Teukolsky, S. A., Vetterling, W. T., and Flannery, B. P., Numerical Recipes in C: The Art of
Scientific Computing, 2nd edn., Cambridge University Press, Cambridge, MA, 2002.
3. Koenig, A. and Moo, B. E., Accelerated C++: Practical Programming by Example, Addison-Wesley,
Boston, MA, 2009.
4. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
Appendix A: ControlEng: Win32
Console Application
A.1 INTRODUCTION
Chapter 1 introduced a preliminary class structure for a Win32 Console Application project, titled
ControlEng. Table A.1 lists the header files and the contained classes in the order of their appear-
ance. The actual source (*.cpp) and header (*.h) files are provided in alphabetical order in the fol-
lowing (header files are shown first). The application was developed using the Microsoft Visual
C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System (integrated development environ-
ment [IDE]) [1].
The developer can port this code in an application titled, e.g., ControlEng, denoting “control
engineering,” and compile and run the executable to see simple constructor and destructor state-
ments being displayed in an output console window as shown in Figure A.1. The developer will
notice that objects of the following types are constructed (listed in the order of their construction):
CSystemModel, CBlockShape, CBlock, CPort, CConnection, and CSignal.
// Title: Block.h
#ifndef BLOCK_H
#define BLOCK_H // inclusion guard
1043
1044 Appendix A: ControlEng: Win32 Console Application
TABLE A.1
Win32 Console Application Header Files and Contained
Classes in Order of Appearance in the Actual Header Files
Block.h ControlEng.h Signal.h SystemModel.h
CBlockShape No class definition CSignal CSystemModel
CPort CDoubleSignal
CBlock CMatrixSignal
CConstantBlock CVectorSignal
CDerivativeBlock CConnection
CDivideBlock
CGainBlock
CIntegratorBlock
CLinearFnBlock
COutputBlock
CSignalGeneratorBlock
CSubsystemBlock
CSubsystemInBlock
CSubsystemOutBlock
CSumBlock
CTransferFnBlock
FIGURE A.1 Console-based output concerning basic object construction and destruction for the Win32
Console Application, ControlEng.
Appendix A: ControlEng: Win32 Console Application 1045
// BlockShape
class CBlockShape
{
public:
CBlockShape(void);
∼CBlockShape(void);
private:
double geometry[3]; // square (l,w,thick), circle (r,d,thick),
triangle (l,direc,thick)
EBlockShape m_eBlockShape;
};
// Port
class CBlock; // predefining CBlock as it’s rqd. by CPort,
additionally CBlock rqes. CPort
class CPort
{
public:
CPort(CBlock &block_ref);
∼CPort(void);
// Accessor methods
char *GetName(void);
private:
char *port_name; // name of either input or output port
double port_position[2]; // ordered pair denoting (x,y)
location of port w.r.t. block CofM
CBlock &m_rRefToBlock; // reference to its parent/own block
upon which the port resides
EPortID e_port_id; // ‘>’ arrow pts away/towards block
center for output/input (left, up, right, down)
};
// Block
class CSystemModel; // predefining CSystemModel as it’s rqd. by
CBlock, additionally CSystemModel rqes. CBlock
class CBlock
{
public:
CBlock(void);
∼CBlock(void);
// Accessor methods
char* GetName(void);
private:
char *block_name; // block name
double block_position[2]; // ordered pair denoting (x,y)
location of CofM of block
1046 Appendix A: ControlEng: Win32 Console Application
// DerivativeBlock
class CDerivativeBlock : public CBlock
{
public:
CDerivativeBlock(void);
∼CDerivativeBlock(void);
private:
int derivative_method; // 3-pt method, 5-pt method
double t_step_size_h; // time step size ‘h’ used in the defn
of the numerical derivative
};
// DivideBlock
class CDivideBlock : public CBlock
{
public:
CDivideBlock(void);
∼CDivideBlock(void);
private:
int m_iNMultiplyInputs;
int m_iNDivideInputs;
EMultAction e_mult_action; // if divide used for a matrix,
then matrix inverse is rqd.
};
// GainBlock
class CGainBlock : public CBlock
{
public:
CGainBlock(void);
∼CGainBlock(void);
private:
double scalar_gain;
double *vector_gain;
Appendix A: ControlEng: Win32 Console Application 1047
double **matrix_gain;
EMultAction e_mult_action;
};
// IntegratorBlock
class CIntegratorBlock : public CBlock
{
public:
CIntegratorBlock(void);
∼CIntegratorBlock(void);
private:
double t_step_size_h; // time step size “h”
double *ic_vector; // initial condition vector
EIntegrationMethod e_integration_method;
};
// LinearFnBlock
class CLinearFnBlock : public CBlock
{
public:
CLinearFnBlock(void);
∼CLinearFnBlock(void);
private:
double fn_derivative; // slope of linear fn or “curve”
double signal_init_val; // init val of signal (at time t0)
double t_signal_start; // start time of signal
};
// OutputBlock
class COutputBlock : public CBlock
{
public:
COutputBlock(void);
∼COutputBlock(void);
private:
char *file_name;
double t_start;
double t_stop;
EOutputDataFormat e_data_format;
};
// SignalGeneratorBlock
class CSignalGeneratorBlock : public CBlock
{
public:
CSignalGeneratorBlock(void);
∼CSignalGeneratorBlock(void);
private: // externally generated signal is being ignored
for now.
double amplitude;
double frequency;
EUnits e_units;
ESignalForm e_signal_form;
};
1048 Appendix A: ControlEng: Win32 Console Application
// SubsystemBlock
class CSubsystemBlock : public CBlock
{
public:
CSubsystemBlock(void);
∼CSubsystemBlock(void);
private:
int port_number_label_1; // number to appear as the port label
int port_number_label_2; // number to appear as the port label
char *port_signal_label_1; // signal identifier (name) to appear
as the port label
char *port_signal_label_2; // signal identifier (name) to appear
as the port label
};
// SubsystemInBlock
class CSubsystemInBlock : public CBlock
{
public:
CSubsystemInBlock(void);
∼CSubsystemInBlock(void);
private:
int port_number_label; // number to appear as the port label
char *port_signal_label; // signal identifier (name) to appear
as the port label
};
// SubsystemOutBlock
class CSubsystemOutBlock : public CBlock
{
public:
CSubsystemOutBlock(void);
∼CSubsystemOutBlock(void);
private:
int port_number_label; // number to appear as the port label
char *port_signal_label; // signal identifier (name) to appear
as the port label
};
// SumBlock
class CSumBlock : public CBlock
{
public:
CSumBlock(void);
∼CSumBlock(void);
private:
int no_of_plus_inputs;
int no_of_minus_inputs;
};
// TransferFnBlock
class CTransferFnBlock : public CBlock
{
public:
CTransferFnBlock(void);
∼CTransferFnBlock(void);
Appendix A: ControlEng: Win32 Console Application 1049
private:
double *numerator_coeffs_vec; // vector of numerator coeffs
double *denominator_coeffs_vec; // vector of denominator
coeffs
};
#endif
// eof
// Title: ControlEng.h
#ifndef CONTROL_ENG_H
#define CONTROL_ENG_H // inclusion guard
#endif
// eof
// Title: Signal.h
#include “Block.h”
#ifndef SIGNAL_H
#define SIGNAL_H // inclusion guard
// Signal
class CSignal
{
public:
CSignal(void); // creates a Signal obj. based on the datatype
of the evolved (due to block ops.) signal
∼CSignal(void);
// Accessor methods
char *GetName(void);
private:
char *signal_name; // name of the current Signal
};
// DoubleSignal
class CDoubleSignal : public CSignal
{
public:
CDoubleSignal(void);
∼CDoubleSignal(void);
private:
double var; // each signal will have some numerical val
};
1050 Appendix A: ControlEng: Win32 Console Application
// MatrixSignal
class CMatrixSignal : public CSignal
{
public:
CMatrixSignal(void);
∼CMatrixSignal(void);
private:
int nrows;
int ncols;
double **matrix_signal; // matrix of signal vals
};
// VectorSignal
class CVectorSignal : public CSignal
{
public:
CVectorSignal(void);
∼CVectorSignal(void);
private:
int length;
double *vector_signal; // vector of signal vals
};
// Connection
class CConnection
{
public:
CConnection(void);
∼CConnection(void);
// Accessor methods
CSignal *GetSignal(void); // gets the private member var
signal of type ptr-to-CSignal
private:
double start_pt[2]; // start pt. (x,y) ordered pair
double end_pt[2]; // end pt. (x,y) ordered pair
CPort *m_pRefFromPort; // singular, since 1 connection has only
1 from-port (ptr used since block ports not know at this stage)
CPort *m_pRefToPort; // singular, since 1 connection has only 1
to-port (ptr used since block ports not know at this stage)
CSignal *m_pSignal; // singular, since 1 signal (like a
current) travels down 1 connection (like a wire)
};
#endif
// eof
// Title: SystemModel.h
#include “Block.h”
#include “Signal.h”
#ifndef SYSTEM_MODEL_H
#define SYSTEM_MODEL_H // inclusion guard
Appendix A: ControlEng: Win32 Console Application 1051
// SystemModel
class CSystemModel
{
public:
CSystemModel(void);
∼CSystemModel(void);
ComputeModelSignals(); // computes all Signals across all
Connections according to math ops.
char *GetName(void); // gets model name
ReadInputFromFile(); // reads: time, states
WriteOutputToFile(); // writes: time, states, output, final
states
WriteNthTimeStepToFile(); // writes: every n-th time step to file
ValidateConnectivity(); // validates Blocks are connected by
Connections, warns of alg. loops
private:
char *model_name; // name of the current
SystemModel
double t_start; // simulation start time
double t_stop; // simulation stop time
list<CBlock*> m_lstBlock; // list of ptrs to the
Blocks of the current SystemModel
list<CConnection*> m_lstConnection; // list of ptrs to the
Connections of the current SystemModel
};
#endif
// eof
// Title: Block.cpp
// Purpose: Contains all Block related code.
#include <iostream>
#include <string.h>
#include <vector>
#include <list>
using namespace std;
#include “Block.h”
#include “SystemModel.h” // rqd. since Block() destr potentially
deletes a ptr-to-CSystemModel (m_pSubSystemModel)
// CBlockShape
CBlockShape::CBlockShape(void)
{
cout << “CBlockShape::CBlockShape()\n”;
1052 Appendix A: ControlEng: Win32 Console Application
// Init
int i;
double d = 1.0;
m_eBlockShape = e_rectangle;
CBlockShape::∼CBlockShape(void)
{
// CPort
CPort::CPort(CBlock &block_ref):
m_rRefToBlock(block_ref) // must init the m_rRefToBlock rather than
assign on CPort obj. creation
{
cout << “CPort::CPort()\n”;
// Init
port_name = new char[32]; // allocate space for port_name
strcpy(port_name, “actual_port_name”); // copy actual port name into
the var port_name
port_position[0] = 1.0;
port_position[1] = 0.0;
e_port_id = e_right_arrow;
}
CPort::∼CPort(void)
{
cout << “CPort::∼CPort()\n”;
}
char *CPort::GetName(void)
{
cout << “CPort::GetName()\n”;
return port_name;
}
// CBlock
CBlock::CBlock(void)
{
cout << “CBlock::CBlock()\n”;
// Init
block_name = new char[32];
strcpy(block_name, “actual_block_name”);
block_position[0] = 1.0;
block_position[1] = 1.0;
Appendix A: ControlEng: Win32 Console Application 1053
CBlock::∼CBlock(void)
{
cout << “CBlock::∼CBlock()\n”;
// MEMORY DELETE
if(block_name != NULL)
{
delete [] block_name;
}
if(m_pSubSystemModel != NULL)
{
delete m_pSubSystemModel;
m_pSubSystemModel = NULL;
}
}
char *CBlock::GetName(void)
{
cout << “CBlock::GetName()\n”;
return block_name;
}
// CConstantBlock
CConstantBlock::CConstantBlock(void)
{
cout << “CConstantBlock::CConstantBlock()\n”;
}
CConstantBlock::∼CConstantBlock(void)
{
cout << “CConstantBlock::∼CConstantBlock()\n”;
}
// CDerivativeBlock
CDerivativeBlock::CDerivativeBlock(void)
{
cout << “CDerivativeBlock::CDerivativeBlock()\n”;
}
CDerivativeBlock::∼CDerivativeBlock(void)
{
cout << “CDerivativeBlock::∼CDerivativeBlock()\n”;
}
// CDivideBlock
CDivideBlock::CDivideBlock(void)
{
cout << “CDivideBlock::CDivideBlock()\n”;
}
Appendix A: ControlEng: Win32 Console Application 1055
CDivideBlock::∼CDivideBlock(void)
{
cout << “CDivideBlock::∼CDivideBlock()\n”;
}
// CGainBlock
CGainBlock::CGainBlock(void)
{
cout << “CGainBlock::CGainBlock()\n”;
}
CGainBlock::∼CGainBlock(void)
{
cout << “CGainBlock::∼CGainBlock()\n”;
}
// CIntegratorBlock
CIntegratorBlock::CIntegratorBlock(void)
{
cout << “CIntegratorBlock::CIntegratorBlock()\n”;
}
CIntegratorBlock::∼CIntegratorBlock(void)
{
cout << “CIntegratorBlock::∼CIntegratorBlock()\n”;
}
// CLinearFnBlock
CLinearFnBlock::CLinearFnBlock(void)
{
cout << “CLinearFnBlock::CLinearFnBlock()\n”;
}
CLinearFnBlock::∼CLinearFnBlock(void)
{
cout << “CLinearFnBlock::∼CLinearFnBlock()\n”;
}
// COutputBlock
COutputBlock::COutputBlock(void)
{
cout << “COutputBlock::COutputBlock()\n”;
}
COutputBlock::∼COutputBlock(void)
{
cout << “COutputBlock::∼COutputBlock()\n”;
}
// CSignalGeneratorBlock
CSignalGeneratorBlock::CSignalGeneratorBlock(void)
{
cout << “CSignalGeneratorBlock::CSignalGeneratorBlock()\n”;
}
1056 Appendix A: ControlEng: Win32 Console Application
CSignalGeneratorBlock::∼CSignalGeneratorBlock(void)
{
cout << “CSignalGeneratorBlock::∼CSignalGeneratorBlock()\n”;
}
// CSubsystemBlock
CSubsystemBlock::CSubsystemBlock(void)
{
cout << “CSubsystemBlock::CSubsystemBlock()\n”;
}
CSubsystemBlock::∼CSubsystemBlock(void)
{
cout << “CSubsystemBlock::∼CSubsystemBlock()\n”;
}
// CSubsystemInBlock
CSubsystemInBlock::CSubsystemInBlock(void)
{
cout << “CSubsystemInBlock::CSubsystemInBlock()\n”;
}
CSubsystemInBlock::∼CSubsystemInBlock(void)
{
cout << “CSubsystemInBlock::∼CSubsystemInBlock()\n”;
}
// CSubsystemOutBlock
CSubsystemOutBlock::CSubsystemOutBlock(void)
{
cout << “CSubsystemOutBlock::CSubsystemOutBlock()\n”;
}
CSubsystemOutBlock::∼CSubsystemOutBlock(void)
{
cout << “CSubsystemOutBlock::∼CSubsystemOutBlock()\n”;
}
// CSumBlock
CSumBlock::CSumBlock(void)
{
cout << “CSumBlock::CSumBlock()\n”;
}
CSumBlock::∼CSumBlock(void)
{
cout << “CSumBlock::∼CSumBlock()\n”;
}
// CTransferFnBlock
CTransferFnBlock::CTransferFnBlock(void)
{
cout << “CTransferFnBlock::CTransferFnBlock()\n”;
}
Appendix A: ControlEng: Win32 Console Application 1057
CTransferFnBlock::∼CTransferFnBlock(void)
{
cout << “CTransferFnBlock::∼CTransferFnBlock()\n”;
}
// eof
// Title: ControlEng.cpp
// Purpose: Contains all ControlEng related code: main line of project.
#include <iostream>
#include <string.h>
#include <vector>
#include <list>
using namespace std;
#include “ControlEng.h”
#include “SystemModel.h”
int main(int argc, char **argv)
{
// input: arg count int, and an array of ptrs-to-char, a 2-dim array
of chars
cout << “\n main()\n”;
PrepareControlEng();
cout << endl;
return 0;
}
int PrepareControlEng(void)
{
cout << “PrepareControlEng()\n”;
CSystemModel system_model;
return 0;
}
// eof
// Title: Signal.cpp
// Purpose: Contains all Signal related code.
#include <iostream>
#include <string.h>
#include <vector>
#include <list>
using namespace std;
#include “Signal.h”
// CSignal
CSignal::CSignal(void)
{
cout << “CSignal::CSignal()\n”;
signal_name = new char[32]; // allocate space for signal_name
strcpy(signal_name, “actual_signal_name”); // copy actual signal
name into the var signal_name
}
1058 Appendix A: ControlEng: Win32 Console Application
CSignal::∼CSignal(void)
{
cout << “CSignal::∼CSignal()\n”;
}
char *CSignal::GetName(void)
{
cout << “CSignal::GetName()\n”;
return signal_name;
}
// CDoubleSignal
CDoubleSignal::CDoubleSignal(void)
{
cout << “CDoubleSignal::CDoubleSignal()\n”;
CDoubleSignal::∼CDoubleSignal(void)
{
cout << “CDoubleSignal::∼CDoubleSignal()\n”;
}
// CMatrixSignal
CMatrixSignal::CMatrixSignal(void)
{
cout << “CMatrixSignal::CMatrixSignal()\n”;
nrows = 0;
ncols = 0;
matrix_signal = NULL; // init matrix_signal ptr to NULL (allocate
memory for this elsewhere)
}
CMatrixSignal::∼CMatrixSignal(void)
{
cout << “CMatrixSignal::∼CMatrixSignal()\n”;
}
// CVectorSignal
CVectorSignal::CVectorSignal(void)
{
cout << “CVectorSignal::CVectorSignal()\n”;
length = 0;
vector_signal = NULL; // init vector_signal ptr to NULL (allocate
memory for this elsewhere)
}
CVectorSignal::∼CVectorSignal(void)
{
cout << “CVectorSignal::∼CVectorSignal()\n”;
}
Appendix A: ControlEng: Win32 Console Application 1059
// CConnection
CConnection::CConnection(void)
{
cout << “CConnection::CConnection()\n”;
A.3 SUMMARY
A preliminary Win32 Console Application, named ControlEng, is used to implement the initial
class hierarchical association relationships of Chapter 1 and shows basic construction and destruc-
tion of objects of the following key user-defined types: CSystemModel, CBlockShape, CBlock,
CPort, CConnection, and CSignal.
REFERENCE
1. Microsoft Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
Appendix B: Constructing
Connections: An Exploration
B.1 INTRODUCTION
An exploration is made into the construction and drawing of connection lines with arrowheads
attached that indicate the direction in which they are oriented. These connections are then used in
the main DiagramEng project to connect blocks using their input and output ports, where the direc-
tion of the connection, specified by the arrowhead, indicates the direction of signal flow, i.e., data
transfer, from one block to another.
The work here follows closely the material presented in Chapter 10 of Ref. [1] that introduces
the drawing of lines and saving them into a document object. Here, the Day10 project, pp. 202–213,
of Ref. [1] is revisited in the form of a separate exercise named Exp10, and alterations are made to
reflect, in part, the DiagramEng structure and to facilitate the actual implementation of connections
in the DiagramEng project, covered in Chapter 5.
1063
1064 Appendix B: Constructing Connections: An Exploration
1. Add two private CPoint member variables to the CConnection class with the names “m_ptFrom”
and “m_ptTo”, representing the tail and head points of the connection, respectively.
2. Add a new public constructor to the CConnection class with the prototype,
CConnection::CConnection(CPoint ptFrom, CPoint ptTo), and edit the
function as shown in the following to initialize the member variables.
// Print msg.
sMsg.Format(“\n CExp10Doc::AddConnection(), from (%d,%d)
to (%d,%d)\n”, ptFrom.x, ptFrom.y, ptTo.x, ptTo.y);
// AfxMessageBox(sMsg, nType, nIDhelp);
list<CConnection*> &CExp10Doc::GetConnectionList()
{
return m_lstConnection;
}
// Save the pt that the cursor is at, upon left-btn-down, i.e. init
member var with incoming CPoint var.
m_ptPrevPos = point; // Init the prev. pt, to be used in
OnMouseMove(), to the current point
m_ptOrigin = point; // Init origin of ensuing line, to be drawn in
OnMouseMove(), to be the starting pt.
// DiagramEng (end)
CView::OnLButtonDown(nFlags, point);
}
The developer will notice that this function uses the member variables “m_ptPrevPos” and
“m_ptOrigin” to record the previous and origin points to be used in the following OnMouseMove()
and OnLButtonUp() methods. Hence, add these two private CPoint member variables to the
CExp10View class.
// DiagramEng (start)
// Reverse the pixel color from the original pt to the prev pt.
// SetROP2() sets the current foreground mix mode.
dc.SetROP2(R2_NOT); // R2_NOT => pixel is the inverse of the
screen color
dc.MoveTo(m_ptOrigin);
dc.LineTo(m_ptPrevPos);
// DiagramEng (end)
CView::OnMouseMove(nFlags, point);
}
The OnMouseMove() function draws a line from the origin point, “m_ptOrigin”, to the mouse cur-
sor point, “point”; this is achieved using MoveTo(m_ptOrigin) followed by LineTo(point).
However, firstly the previous line drawn needs to be erased; otherwise it would remain on the screen:
this is performed by reversing the color, using SetROP2(R2_NOT) and overdrawing the previous
line using MoveTo(m_ptOrigin) followed by LineTo(m_ptPrevPos). Finally, the current
mouse cursor point, “point”, is assigned to “m_ptPrevPos”, to make a record of what will become
the previous point, to be subsequently used when overdrawing the line on the next entry into the
OnMouseMove() function.
// DiagramEng (start)
// DiagramEng (end)
CView::OnLButtonUp(nFlags, point);
}
The developer will notice that the AddConnection() function is called upon the pointer-
to-CExp10Doc, retrieved by the call to GetDocument() of the CExp10View class. The
AddConnection() function creates the connection using the CConnection constructor tak-
ing two CPoint arguments and adds it to the list of connections, “m_lstConnection”, as shown
earlier.
// DiagramEng (start)
pDoc->DrawConnections(pDC);
// DiagramEng (end)
}
The call to DrawConnections() (note the plural) is used to draw the connections stored in the con-
nection list, “m_lstConnection”, residing in the CExp10Doc class. Hence, add a public member function
to the CExp10Doc class with the prototype void CExp10Doc::DrawConnections(CDC *pDC)
and edit it as follows. Note how the list of connections is iterated over and DrawConnection() is
called on the pointer-to-CConnection object.
1068 Appendix B: Constructing Connections: An Exploration
// –– Print msg.
sMsg.Format(“\n CExp10Doc::DrawConnections()\n”);
// AfxMessageBox(sMsg, nType, nIDhelp);
// DiagramEng (end)
}
Now the actual function to draw the connection object from the starting point, “m_ptFrom”, to the
ending point, “m_ptTo”, is required. Hence, add a public member function to draw a single connec-
tion, with the prototype, void CConnection::DrawConnection(CDC *pDC), and edit the
function as shown in the following to use the MoveTo() and LineTo() functions of the Device
Context class (CDC).
#if !defined(AFX_EXP10DOC_H__B651D73A_C544_43B2_80B7_8149922FC9AC__
INCLUDED_)
#define AFX_EXP10DOC_H__B651D73A_C544_43B2_80B7_8149922FC9AC__INCLUDED_
// DiagramEng (start)
#include “Connection.h”
#include <list>
using namespace std;
// DiagramEng (end)
…
#endif // !defined(AFX_EXP10DOC_H__B651D73A_C544_43B2_80B7_8149922FC9AC__
INCLUDED_)
Individual lines may be added to the diagram as indicated in Figure B.1. Each line is drawn from the
origin point, “m_ptOrigin”, upon a left-mouse-button-down event, to the final point, “point”, upon
the left-mouse-button-up event. On moving the mouse, the line is simply redrawn to the position of
the mouse cursor, “point”. The connection line is added to the connection list, “m_lstConnection”,
upon the left-mouse-button-up event.
FIGURE B.1 Lines are drawn on the palette using left-button-down, mouse-move, and left-button-up events.
1070 Appendix B: Constructing Connections: An Exploration
where
Ri is the position vector locating the center of mass of body i, i.e., vertex B
up is the local position vector of point pi (here, vertices Ai, Bi, and Ci) with respect to the origin
i
(vertex Bi) of the local coordinate system (xi, yi) of connection i
G(θi) is the rotation matrix specifying body rotation, by an angle θi, in the clockwise sense about
the z axis oriented positively downward (into the page), i.e.,
⎡cos(θi ) −sin(θi )⎤
G(θi ) = ⎢ (B.2)
⎣ sin(θi ) cos(θi ) ⎥⎦
xi
yi Arrowhead of connection (body i)
θi uCi
Bi Ci
uAi
Y
Ri
Ai
rAi
Tail end of connection (body i)
(0,0)
X
Global coordinate system
FIGURE B.2 Global screen coordinate system (X, Y) and local body coordinate system (xi, yi) of the arrow-
head of the ith connection, indicating the positive clockwise direction of rotation (θi).
Appendix B: Constructing Connections: An Exploration 1071
Ai
π
6
l/2 h
l π/6 Bi
d h Block direction
Ci
FIGURE B.3 A triangle block representing the arrowhead of the ith connection of side length l, where h is
the hypotenuse of a subtriangle, with opposite side length d and base length l/2.
This general geometrical method to denote the position vector of a point on a rotating body follows
the work of Shabana [3].
Consider Figure B.3, showing an arrowhead represented by an equilateral triangle, with base
length l that is composed of subtriangles with a hypotenuse h and opposite side length d, such that
the bisector of a side of the equilateral triangle is of length h + d. This geometry is used here to
determine the global position vectors of the vertices Ai, Bi, and Ci of the equilateral arrowhead tri-
angle on the head-end of a connection.
The global position vectors of the vertices for the ith connection’s arrowhead are
⎡rB , x ⎤ ⎡ Ri, x ⎤
rBi = ⎢ i ⎥ = ⎢ ⎥ (B.4)
⎣rBi , y ⎦ ⎣ Ri, y ⎦
and
where Ri = [Ri,x, Ri,y]T = [m_ptTo.x,m_ptTo.y]T is the position vector of the end point of the connec-
tion at which the arrowhead is to be drawn, and uBi = [0, 0]T since the center of mass is considered
to be located at point Bi (for simplicity).
Now the polygon triangle representing the arrowhead needs to be filled in the DrawArrowHead()
function. The steps involved are as follows: create a new brush, declare a pointer-to-CBrush, declare an
array of points that form the polygon, add the triangle vertices to the array, select the new brush, fill the
polygon, and reset the old brush (see the following code). However, as mathematical functions, i.e., sin()
and cos(), are used, the math header file, “math.h”, should be included, i.e., #include <math.h>, at the top
of the source file, “Connection.cpp”. Edit the DrawArrowHead() function as shown.
void CConnection::DrawArrowHead(CDC *pDC)
{
int pen_color = RGB(0,0,0); // pen color: White = RGB(255,255,255),
Black = RBF(0,0,0)
int pen_width = 1; // pen width
double d; // opp. side length of subtriangle with
hypotenuse h and base side length length/2.
double dDeltaLength = 50; // std ref delta length
double h; // hypotenuse of subtriangle with opp.
side length d and base side length length/2.
double length; // a fraction of dDeltaLength
double length_u; // length of vector u
double length_v; // length of vector v
double theta; // angle of ith body rotated positively
clockwise w.r.t. the global screen coord sys. X axis.
double u[2]; // one of two vectors used to determined angle theta
double v[2]; // one of two vectors used to determined angle theta:
the unit vector [1,0]
CBrush *pBrush = NULL; // a ptr to brush
CPoint A; // vertex in anticlock direc from pointing
vertex B of arrowhead
CPoint B; // vertex of arrowhead pointing in direc of
arrow (m_ptTo)
CPoint C; // vertex in clock direc from pointing vertex
B of arrowhead
CPoint vertices[3]; // vertices array to hold triangle vertices
CString sMsg; // main msg string
UINT nType = MB_OK; // style of msg. box
UINT nIDhelp = 0; // help context ID for the msg.
// Assign lengths to arrowhead (triangle) paras.
length = 0.5*dDeltaLength;
d = 0.2887*length;
h = 0.5773*length;
// Print msg.
sMsg.Format(“\n CConnection::DrawArrowHead(), d = %lf, h = %lf\n”, d, h);
// AfxMessageBox(sMsg, nType, nIDhelp);
Appendix B: Constructing Connections: An Exploration 1073
// Draw arrowhead
pDC->MoveTo(A);
pDC->LineTo(B);
pDC->LineTo(C);
pDC->LineTo(A);
vertices[0] = A;
vertices[1] = B;
vertices[2] = C;
The developer will notice that the previous code simply implements the mathematical derivation
provided earlier. This geometrical method of determining the arrowhead vertices to draw the arrow-
head is required in order that the direction of the connection be accurately represented.
To solve these problems, only small changes need to be made to the existing code, without the need
for elaborate global functions, where class code would otherwise be extracted away from a class, or
the obtaining of document pointers followed by cumbersome invocation. The steps to solve these
problems are as follows:
1. OnLButtonUp() should be edited with the code Invalidate(TRUE) to make the docu-
ment redraw itself.
2. OnMouseMove() should be edited to declare local objects of class CConnection, and
DrawConnection() should be called on those objects, i.e., comment out the existing
explicit drawing mechanism and make the calls as shown in the following.
FIGURE B.4 Connection objects may be drawn on the palette, with the arrowhead indicating the intended
direction.
Appendix B: Constructing Connections: An Exploration 1075
{
// Get the device context
CClientDC dc(this);
// Reverse the pixel color from the original pt to the prev pt.
// SetROP2() sets the current foreground mix mode.
dc.SetROP2(R2_NOT); // R2_NOT => pixel is the inverse of the
screen color
// Declare a CConnection obj in order to call DrawConnection()
CConnection con_obj1(m_ptOrigin, m_ptPrevPos);
con_obj1.DrawConnection(&dc);
// dc.MoveTo(m_ptOrigin);
// dc.LineTo(m_ptPrevPos);
// Declare a CConnection obj in order to call DrawConnection()
// Draw the current stretch of line (but don’t save it until
OnLButtonUp().
CConnection con_obj2(m_ptOrigin, point);
con_obj2.DrawConnection(&dc);
// dc.MoveTo(m_ptOrigin);
// dc.LineTo(point);
// Save the current pt (point) as the prev. pt (m_ptPrevPos)
of the CView class.
m_ptPrevPos = point;
}
}
// DiagramEng (end)
CView::OnMouseMove(nFlags, point);
}
Finally, upon building and running the Exp10 application, connection objects with arrowheads
attached may be drawn vertically, horizontally, and at various angles, as shown in Figure B.4.
B.4 SUMMARY
An exploration was made, based upon material found in Ref. [1], to draw connection objects with
arrowheads attached, indicating their direction. A CConnection class was added to define a con-
nection, and a list of pointers-to-CConnection, “m_lstConnection”, was added to the Document
class to store the connections. Event-handler functions were added to the CView-derived class
to set the points of the connection (OnLButtonDown()), to explicitly draw the connection
(OnMouseMove()), and to add the connection to the list (OnLButtonUp()). The more general
drawing of connections is initiated from the CView-derived class’ OnDraw() method, which calls
DrawConnections(), to iterate over the list of connections, calling DrawConnection().
Extensions to the project involved a mathematical discussion of the geometry of an arrowhead,
drawing of arrowheads on the end of connections and dynamically drawing a connection with an
arrowhead to the mouse cursor point.
REFERENCES
1. Chapman, D., Teach Yourself Visual C++ 6 in 21 Days, Sams Publishing, Indianapolis, IN, 1998.
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
3. Shabana, A. A., Computational Dynamics, 2nd edn., John Wiley & Sons, New York, 2001.
Appendix C: NodeArcConnectivity:
Win32 Console Application
C.1 INTRODUCTION
The presence of an algebraic or equivalently a feedback loop in a model diagram needs to be
detected to determine the appropriate method of model computation; if a loop does not exist, then
a direct signal propagation method is used, and if a loop does exist, then a simultaneous equation-
based approach is made. A node-arc binary matrix is used to denote the block-to-block connectivity
of a model diagram, and this matrix is analyzed to determine the various tours that may be made
through the diagram that start at a source block and end at either an Output block or loop-repeated
block, signifying the presence of an algebraic loop.
A Win32 Console Application, titled NodeArcConnectivity, is presented here to allow the devel-
oper to explore the flow of control of the tour-building process, performed by a function named
BuildTour(). Print statements clearly show the evolution of the process, and the interested devel-
oper should step through the application with a debugger to pursue the flow of control and observe
the change in program state indicated in the Variables and Watch windows.
1077
1078 Appendix C: NodeArcConnectivity: Win32 Console Application
G6
E4 H7 F5 C2 I8 D3 J9
B1 A0
FIGURE C.1 General representation of a model with two feedback loops, where the letters and numbers
represent the order in which the blocks were placed on the palette.
TABLE C.1
Block-to-Block Connectivity Matrix
A0 B1 C2 D3 E4 F5 G6 H7 I8 J9
A0
B1 1
C2 1 1
D3 1 1
E4 1
F5 1 1
G6 1
H7 1
I8 1
J9
TABLE C.2
Tours through Block Diagram
Tours Ending at an Output Block Tours Ending in a Loop-Repeated Block
E→H→F→C→A E→H→F→B→H
E→H→F→C→I→D→J E→H→F→C→I→D→G→C
// NodeArcCon.h
#ifndef NODE_ARC_CON_H
#define NODE_ARC_CON_H
// Static vars
const int EXAMPLE = 3; // example type used for setting up problem
// Printing fns
int PrintMatrix(int **M, int nrows, int ncols);
int PrintVector(int *v, int ncols);
#endif
// eof
// Title: NodeArcCon.cpp
// Purpose: Experiments with node-arc connectivity matrices and
recording node tours.
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <math.h>
#include <time.h>
#include “NodeArcCon.h”
using namespace std;
NodeArcConnectivity();
int NodeArcConnectivity(void)
{
int i;
int n_nodes; // number of initial nodes in node_vec, i.e., source
nodes only, NOT ALL NODES.
int ncols; // number of cols of node-arc matrix (number of nodes)
int nrows; // number of rows of node-arc matrix (number of nodes)
int *node_vec = NULL; // node vector that contains only the source
node(s)
int *tour_vec = NULL; // vec that holds node-to-node tours, of
size total number of nodes = ncols.
int **A = NULL; // nod-arc matrix
// Get the number of rows and cols for the partic. example type.
SetProblemDimensions(nrows, ncols);
// MEMORY ALLOC
node_vec = new int[ncols]; // allocate enough space for all nodes,
but only emplace source nodes.
tour_vec = new int[ncols]; // allocate enough space for all nodes,
since max tour length contains all nodes
A = new int *[nrows];
for(i=0; i<nrows; i++)
{
A[i] = new int[ncols]; // allocate an array of doubles, return
& to matrix[i]
}
Appendix C: NodeArcConnectivity: Win32 Console Application 1081
SetupInitialTourVector(tour_vec, ncols);
PrintVector(tour_vec, ncols);
// MEMORY DELETE
delete [] node_vec;
delete [] tour_vec;
if(A != NULL)
{
for(i=0; i<nrows; i++)
{
delete [] A[i];
}
delete [] A;
}
return 0;
}
switch(example)
{
case 0: // Example 0: feedback case with multiple paths
nrows = 5;
ncols = nrows;
break;
case 1: // Example 1: feed forward case with multiple paths
nrows = 6;
ncols = nrows;
break;
case 2: // Example 2: feedback case with multiple source nodes
nrows = 7;
ncols = nrows;
break;
case 3: // Example 3: feedback case with multiple feedback loops
nrows = 10;
ncols = nrows;
break;
case 4: // Example 4: feed forward case with only two blocks
nrows = 2;
ncols = nrows;
break;
case 5: // Example 5: simple feedback loop without intervening
block
1082 Appendix C: NodeArcConnectivity: Win32 Console Application
nrows = 4;
ncols = nrows;
break;
case 6: // Example 6: simple feedback loop with and without
intervening block
nrows = 5;
ncols = nrows;
break;
default:
printf(“\n SetProblemDimensions(), select a feasible example
number.\n”);
break;
}
return 0;
}
// Nullify matrix
for(i=0; i<nrows; i++)
{
for(j=0; j<ncols; j++)
{
matrix[i][j] = 0;
}
}
switch(example)
{
case 0: // Example 0: feedback case with multiple paths
matrix[0][4] = 1;
matrix[2][4] = 1;
matrix[3][1] = 1;
matrix[3][2] = 1;
matrix[4][3] = 1;
break;
case 1: // Example 1: feed forward case with multiple paths
matrix[0][5] = 1;
matrix[1][3] = 1;
matrix[2][5] = 1;
matrix[4][0] = 1;
matrix[4][2] = 1;
matrix[4][5] = 1;
matrix[5][1] = 1;
break;
Appendix C: NodeArcConnectivity: Win32 Console Application 1083
return 0;
}
printf(“\n DetermineInitialNodeVector()\n”);
// A node on row i, is connected to a node on col j.
// So if there is a col of zeros, then that col number represents a
source node,
// since no node is connected to it in the forward-direction sense.
// Assign initial negative values
for(i=0; i<ncols; i++)
{
node_vec[i] = −1;
}
// Iterate across the cols and down the rows
for(j=0; j<ncols; j++)
{
col_sum = 0;
for(i=0; i<nrows; i++)
{
col_sum = col_sum + A[i][j]; // col sum
}
if(col_sum == 0)
{
node_vec[offset] = j; // place source node in node_vec
offset++;
}
}
// Assign the number of source nodes in the node_vec
n_nodes = offset;
// Return a flag indicating whether a row of zeros was found
if(offset == 0)
{
return −1; // error since no col of zeros, and hence no
initial source node
}
else
{
return 0; // a col of zeros was found, hence there exists a
source node
}
}
int SetupInitialTourVector(int *tour_vec, int ncols)
{
int i;
printf(“\n SetupInitialTourVector()\n”);
// Assign unique initial negative values to the node entries of the
tour vec indicating no tour
for(i=0; i<ncols; i++)
{
tour_vec[i] = −(i+1);
}
return 0;
}
Appendix C: NodeArcConnectivity: Win32 Console Application 1085
int BuildTour(int **A, int nrows, int ncols, int *node_vec, int n_nodes,
int *in_tour_vec)
{
int i;
int j;
int posn; // posn of nodes in tour vector
int n_branch_nodes; // number of branch nodes present on a partic. row
int node; // node number = row number = col number
int tour_end = 0; // flag identifying whether current tour has
been found
int *branch_node_vec = NULL; // vec of branch nodes to which current
node can progress
int *tour_vec = NULL; // vec of nodes comprising the tour
time_t seconds; // time var used to aid user when
pressing return bw. iterations of for-node loop
char t_string[20]; // time string
// -- MEMORY ALLOC
tour_vec = new int[ncols];
// Copy the incoming tour vec into the local tour vec
for(j=0; j<ncols; j++)
{
tour_vec[j] = in_tour_vec[j];
}
printf(“\n tour_vec:\n”);
PrintVector(tour_vec, ncols);
printf(“\n node_vec:\n”);
PrintVector(node_vec, ncols);
// -- ITERATE OVER ONLY THE NODES IN THE NODE VECTOR FROM WHICH
CONNECTIONS TO OTHER NODES ARE PREMISSABLE
for(node=0; node<n_nodes; node++)
{
// Get the offset position in the tour vector into which the next
node should be placed
posn = FindCurrentTourPosition(in_tour_vec, ncols);
printf(“\n For node = %d < %d, posn = %d\n”, node, n_nodes, posn);
// Get starting node, i.e. a node in the node vector (of source
nodes or branch nodes)
i = node_vec[node];
tour_vec[posn] = i;
tour_end = 0;
// Check for repeated entries here, after the new node addition
to the tour_vec: if there is a repeat, end tour.
if(CheckRepeatedEntries(tour_vec, ncols) > 0)
{
tour_end = 1;
1086 Appendix C: NodeArcConnectivity: Win32 Console Application
// MEMORY DELETE
delete [] tour_vec;
return 0;
}
//printf(“\n FindCurrentTourPosition()\n”);
// If no -ve value found then return the size of the array “ncols”
return ncols;
}
//printf(“\n FindBranchNodes()\n”);
// Iterate over the cols of A for the current row (i), recording
those with one’s (1), indicating node connections.
for(j=0; j<ncols; j++)
{
if(A[i][j] == 1)
{
branch_node_vec[cnt] = j;
cnt++;
}
}
n_branch_nodes = cnt; // update the number of connecting/branch
nodes
Appendix C: NodeArcConnectivity: Win32 Console Application 1089
return 0;
}
//printf(“\n NumberOfOnes()\n”);
return n_ones;
}
//printf(“\n CheckRepeatedEntries()\n”);
// Check tour vector for repeated entries (implying a loop)
for(i=0; i<(ncols−1); i++)
{
for(j=(i+1); j<ncols; j++)
{
if(tour[j] == tour[i])
{
n_repeats++;
}
}
}
return n_repeats;
}
// WARNING! This fn should save all tours, but doesn’t here since
nothing is done with them.
// This could be performed in the following ways:
// 1) Run NodeArcConnectivity() to determine the exact n_tours, then
allocate enough memory, the rerun to obtain the tours.
// 2) Run NodeArcConnectivity() and simply add each tour to a growing
static int 2-D array that is large enough.
//printf(“\n CheckRepeatedEntries()\n”);
PrintVector(tour_vec, ncols);
return;
}
// -- PRINTING FNS
// Print Matrix
cout << endl;
Appendix C: NodeArcConnectivity: Win32 Console Application 1091
return 0;
}
// Print Vector
printf(“\n”);
for(i=0; i<ncols; i++)
{
printf(“ %d”, v[i]);
}
printf(“\n\n”);
return 0;
}
//eof
C.4 SUMMARY
A Win32 Console Application titled NodeArcConnectivity, with header and source files,
“NodeArcCon.h” and “NodeArcCon.cpp”, respectively, is presented that computes the block-to-
block tours through a node-arc graph representing a block diagram. The main BuildTour() func-
tion is called recursively to determine all possible tours in a graph, from the starting source node,
through branching nodes, and finally to Output nodes or loop-repeated nodes, where a repeated
node signifies a feedback or algebraic loop in the block diagram.
REFERENCE
1. Kelley, A. and Pohl, I., A Book On C: Programming in C, 2nd edn., Benjamin Cummings, Redwood
City, CA, 1990.
Appendix D: Debugging:
An Introduction
D.1 INTRODUCTION
Debugging is the process of interactively examining the flow of control and the changing state of a
program during execution to gain insight into the cause and effect behavior of logical and compu-
tational expressions, with an aim to rectify erroneous actions or unintended side effects. Usually,
when a program does not work as intended, the developer may have an idea as to what is wrong,
but may not know where to start examining the code. Sometimes print statements or message boxes
(AfxMessageBox()) are used to provide some sort of information about the program state, but
these are cumbersome to use and may not provide the amount of information required to solve a
problem effectively going to the specific line of code that is of concern. The efficient use of a debug-
ging tool, e.g., that provided with the Microsoft Visual C++® 6.0 Visual Studio™ 6.0 Development
System (integrated development environment [IDE])* [1], allows the developer to quickly and easily
find the cause of an error and spend valuable time solving a problem rather than waste time feeling
helpless and not know where to begin. The current appendix firstly briefly introduces the features of
the debugger provided with the Microsoft Visual C++® 6.0 IDE [1] and then shows how the debug-
ger is used in the context of an easy-to-understand dialog-based application built specifically to
examine program state, flow of control, errors, and memory leaks.
* Microsoft Visual C++® 6.0 Visual Studio™ 6.0 Development System is abbreviated here as Microsoft Visual C++® 6.0
IDE.
† The notation “/” used herein denotes menu and menu entry association, e.g., “menu/menu entry”.
1093
1094 Appendix D: Debugging: An Introduction
TABLE D.1
Brief Definitions of Menu Entry–Invoked Debugging-Related Actions
Menu Entry and Key Combination Explanation
Edit/Breakpoints (Alt + F9) Displays the Breakpoints dialog to set Location, Data, and Message-
related information
View/Debug Windows/Watch (Alt + 3) Displays the Watch window used to view variable values or expressions
View/Debug Windows/Call Stack (Alt + 7) Displays the Call Stack window showing a list of active procedures or
stack frames for the executing thread
View/Debug Windows/Memory (Alt + 6) Displays the Memory window to view memory contents at or to specify
an expression for a memory location
View/Debug Windows/Variables (Alt + 4) Displays the variable properties in the current context using three tabbed
panes: Auto, Locals, and This
View/Debug Windows/Registers (Alt + 5) Displays the contents of the CPU registers, flags, and floating-point stack
View/Debug Windows/Disassembly (Alt + 8) Displays disassembled code with source-code annotations and symbols in
the Disassembly window
Build/Start Debug/Go (F5) Executes code from the current statement until a breakpoint, pause for
user input, or the end of the program
Build/Start Debug/Step Into (F11) Single-steps through program instructions entering invoked functions
Build/Start Debug/Run to Cursor (Ctrl + F10) Executes the program up to the line containing the insertion point,
equivalent to a temporary breakpoint
Build/Start Debug/Attach to Process … Attaches the debugger to an active running process that is running outside
of Visual Studio
Debug/Go (F5) Executes code from the current statement until a breakpoint, pause for
user input, or the end of the program
Debug/Restart (Ctrl + Shift + F5) Terminates a debugging session, rebuilds and then reruns the application
from the start
Debug/Stop Debugging (Shift + F5) Terminates a debugging session and returns to a normal editing session
Debug/Break Temporarily stops execution of all processes in a debugging session
Debug/Apply Code Changes (Alt + F10) Applies code changes while the program is being debugged using the Edit
and Continue feature
Debug/Step Into (F11) Single-steps through program instructions entering invoked functions
Debug/Step Over (F10) Single-steps through program instructions executing a function call
without stepping through it
Debug/Step Out (Shift + F11) Executes the remaining lines of a function in which the execution point lies
Debug/Run to Cursor (Ctrl + F10) Executes the program up to the line containing the insertion point,
equivalent to a temporary breakpoint
Debug/Step Into Specific Function Single-steps through program instructions and enters the specified
function call
Debug/Exceptions … Displays the Exceptions dialog to specify how the debugger is to handle
program exceptions
Debug/Threads … Displays the Threads dialog to suspend, resume, or set focus to program
threads
Debug/Modules … Displays the Modules dialog to view the module, its address, path, and
loading order
Debug/Show Next Statement (Alt + Num*) Shows the next statement in the program code or in the Disassembly
window if code is unavailable
Debug/Quick Watch … (Shift + F9) Displays the Quick Watch window in which expressions and variable
values are examined
Source: Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development System,
Microsoft Corporation, 1998.
Appendix D: Debugging: An Introduction 1095
FIGURE D.1 The Debug menu entries visible after starting the debugger and the Debug toolbar shown to
the right of the Build Minibar.
TABLE D.2
DebugEx Dialog Window Controls: Objects, Properties,
and Settings
Object Property Setting
Group Box ID ID_DEBUGEX_DLG_GB_DEBUG
Caption Debugging Concepts
Static Text ID ID_DEBUGEX_DLG_TXT_MSG
Caption Select Button:
Right aligned text Checked
Static Text ID ID_DEBUGEX_DLG_TXT_OUTPUT
Caption Output:
Right aligned text Checked
Button ID ID_DEBUGEX_DLG_BTN_DATA
Caption &Data
Button ID ID_DEBUGEX_DLG_BTN_MEMORY
Caption &Memory
Button ID ID_DEBUGEX_DLG_BTN_OVERWRITE
Caption O&verwrite
Static Text ID ID_DEBUGEX_DLG_TXT_DATA
Caption Empty string
Border Checked
Center vertically Checked
Static Text ID ID_DEBUGEX_DLG_TXT_MEMORY
Caption Empty string
Border Checked
Center vertically Checked
Static Text ID ID_DEBUGEX_DLG_TXT_OVERWRITE
Caption Empty string
Border Checked
Center vertically Checked
Button ID IDOK
Default button Checked
Caption &OK
Button ID IDCANCEL
Caption &Cancel
FIGURE D.2 The DebugEx dialog window with controls specified in Table D.2.
Appendix D: Debugging: An Introduction 1097
TABLE D.3
Dialog Window Controls, Variable Names, Categories,
and Types for the Dialog (IDD_DEBUGEX_DLG) Resource
Control Variable Name Category Type
ID_DEBUGEX_DLG_TXT_DATA m_strData Value CString
ID_DEBUGEX_DLG_TXT_MEMORY m_strMemory Value CString
ID_DEBUGEX_DLG_TXT_OVERWRITE m_strOverwrite Value CString
ClassWizard, select the Member Variables tab and CDebugExDlg as the class, and add the three
member variables for the dialog controls as specified in Table D.3.
In addition, add three private integer member variables to the CDebugExDlg class, named
“m_iCount”, “m_iDelete”, and “m_iSize”, denoting the number of times the Data button is pressed,
the number of times memory is deleted, and the size of an array, respectively; these will be used
with the CString values to display output data to the user. Initialize all member variable values in the
CDebugExDlg::OnInitDialog() function as shown in bold in the following. The ellipsis, “…”,
denotes code automatically added by the AppWizard and is not of concern here.
BOOL CDebugExDlg::OnInitDialog()
{
CDialog::OnInitDialog();
…
// UPDATE DLG WND: FALSE => init, TRUE => retrieve vals
UpdateData(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
}
TABLE D.4
Objects, IDs, Class, and Event-Handler Functions for the CDebugExDlg Class
Object ID Class COMMAND Event Handler
Data button ID_DEBUGEX_DLG_BTN_DATA CDebugExDlg OnDebugExDlgBtnData()
Memory button ID_DEBUGEX_DLG_BTN_MEMORY CDebugExDlg OnDebugExDlgBtnMemory()
Overwrite button ID_DEBUGEX_DLG_BTN_OVERWRITE CDebugExDlg OnDebugExDlgBtnOverwrite()
OK button IDOK CDebugExDlg OnOK()
Cancel button IDCANCEL CDebugExDlg OnCancel()
void CDebugExDlg::OnCancel()
{
// TODO: Add extra cleanup here
CDialog::OnCancel();
}
void CDebugExDlg::OnOK()
{
CDialog::OnOK();
}
// DebugEx (start)
// Update dlg wnd: FALSE => init, TRUE => retrieve vals
UpdateData(FALSE);
// DebugEx (end)
}
Now when running the application, the initial dialog window appears as shown in Figure D.3a, and
upon successively clicking the Data button, the integer variable is incremented as shown in Figure D.3b.
D.3.3.2 Memory Button Functionality
The Memory button, when clicked, invokes the CDebugExDlg::OnDebugExDlgBtnMemory()
function, within which memory is allocated with operator new, but then is either (1) not deleted,
Appendix D: Debugging: An Introduction 1099
(a) (b)
FIGURE D.3 The DebugEx dialog window showing (a) the initial state and (b) the incremented state of the
“m_iCount” variable, displayed using the “m_strData” variable.
generating a memory leak (“m_iDelete = 0”); (2) deleted once using operator delete (“m_iDelete = 1”);
or (3) deleted, erroneously, twice, causing a crash (“m_iDelete = 2”). Edit the CDebugExDlg::
OnDebugExDlgBtnMemory() method as shown in the following with the three memory
deletion options.
void CDebugExDlg::OnDebugExDlgBtnMemory()
{
// TODO: Add your control notification handler code here
// DebugEx (start)
double *v = NULL; // vector
// Msg
AfxMessageBox(“\n CDebugExDlg::OnDebugExDlgBtnMemory() \n”, MB_OK, 0);
// MEMORY NEW
v = new double[m_iSize];
// MEMORY DELETE
if(m_iDelete == 0)
{
// No deletion, causing a memory leak.
}
else if(m_iDelete == 1)
{
// Delete once
delete [] v;
}
else if(m_iDelete == 2)
{
// Delete twice, erroneously, causing a crash.
delete [] v;
delete [] v;
}
// DebugEx (end)
}
The text box beneath the Memory button displays the value of the integer member variable,
“m_iDelete”, denoting the number of times memory is deleted, using the CString variable,
“m_strMemory”.
1100 Appendix D: Debugging: An Introduction
void CDebugExDlg::OnDebugExDlgBtnOverwrite()
{
// TODO: Add your control notification handler code here
// DebugEx (start)
int i;
double *v = NULL;
// Msg
AfxMessageBox(“\n CDebugExDlg::OnDebugExDlgBtnOverwrite()\n”,
MB_OK, 0);
// MEMORY NEW
v = new double[m_iSize];
PrintVector(v, m_iSize);
// MEMORY DELETE
delete [] v;
// DebugEx (end)
}
In addition, there is a PrintVector() function that has been used earlier, if execution reaches it,
to print the contents of the vector. Hence, add a public member function to the CDebugExDlg class
with the prototype, void CDebugExDlg::PrintVector(double *v, int n), and edit it
as shown in the following to print the pointer-to-double argument, equivalently the array of double
values, in a message box (AfxMessageBox()).
// Format output
sMsgTemp.Format(“\n\n CDebugExDlg::PrintVector() \n\n”);
sMsg += sMsgTemp;
sMsgTemp.Format(“ vector = [”);
sMsg += sMsgTemp;
Appendix D: Debugging: An Introduction 1101
(a)
(b)
FIGURE D.4 (a) A breakpoint set on line 203 of “DebugExDlg.cpp”, where “m_iCount” changes. (b) The
Breakpoints dialog window showing an existing breakpoint on line 203 of “DebugExDlg.cpp”.
To gain information quickly about a variable, the mouse cursor may be placed over the vari-
able of interest, and the DataTips window displays its value. In addition, if the user right-clicks
the mouse upon a variable or expression, thereby invoking the Context menu, and chooses Quick
Watch, the Quick Watch dialog window appears showing the name and value of the variable or
expression, as shown in Figure D.5e.
If the user clicks Go (F5) again, the DebugEx dialog window will reappear showing the newly
incremented value of “m_iCount” equal to “2”. Clicking OK or Cancel will end program execution.
Appendix D: Debugging: An Introduction 1103
(a)
(b)
FIGURE D.5 (a) Program execution pausing at the breakpoint: “m_iCount” is “0” in both the Variables
window (left) and Watch window (right). (b) The DebugEx dialog window showing the updated value of
“m_iCount” after being incremented from “0” to “1”.
(continued)
1104 Appendix D: Debugging: An Introduction
(c)
(d)
FIGURE D.5 (continued) (c) Program execution pausing at the breakpoint: “m_iCount” is “1”. (d) The
“this” pointer holding the address of the current object with all CDebugExDlg integer and CString member
variables visible.
Appendix D: Debugging: An Introduction 1105
(e)
FIGURE D.5 (continued) (e) The QuickWatch dialog window showing the name and value of the “m_iCount”
variable of interest.
void CDebugExDlg::OnDebugExDlgBtnOverwrite()
{
// TODO: Add your control notification handler code here
// DebugEx (start)
int i;
double *v = NULL;
// Msg
AfxMessageBox(“\n CDebugExDlg::OnDebugExDlgBtnOverwrite()\n”, MB_OK, 0);
// MEMORY NEW
v = new double[m_iSize];
PrintVector(v, m_iSize);
// MEMORY DELETE
delete [] v;
// DebugEx (end)
}
Upon rebuilding and then rerunning the DebugEx application, the familiar dialog (Figure D.3a)
appears, and then clicking the Overwrite button results in the previous function being called. In
fact, the PrintVector() function is also called to print out the contents of the vector, but soon
after, the following message is displayed: “DAMAGE: after Normal block (#80) at 0x004211A0”
(Figure D.6).
If the developer chooses not to insert a breakpoint, but rather run the code again, but this
time in the debugger, and then click the Retry button (Figure D.6) to debug the application, then
the call stack in Figure D.7 may be observed; clicking on the recognizable CDebugExDlg::
OnDebugExDlgBtnOverwrite() function takes the developer to the line at which the problem
is detected (but not necessarily caused) indicating that there is a problem with the “v” data structure.
1106 Appendix D: Debugging: An Introduction
FIGURE D.6 Error message displayed when running the erroneous code, where the address “0x004211A0”
is of concern.
Incidentally, the address of “v”, “0x004211a0”, highlighted in the Variables and Watch windows, is
that displayed in the previous error dialog (Figure D.6).
Now, knowing there is a problem with the “v” vector, the developer may place a breakpoint on
the line where memory is allocated and then step over (F10) consecutive lines of code, checking
the number of loop iterations versus the size of the array. Figure D.8 indicates the problem; after
consecutive steps, the loop counter “i” has value “10”, as shown in both the Variables and Watch
windows, but the size of the array, “m_iSize”, is “10”; hence, the loop has incremented past the last
element of the array, “v[9]”. Here, the problem is obvious, but in more complicated code, the value
of the index used to specify the array offset may be evaluated using an expression that, under certain
circumstances, may be outside the bounds of the array.
However, if the developer were impatient and did not want to iterate through the entire loop, but
rather skip to the line after the closing brace, then the mouse cursor may be placed on this line, and
Appendix D: Debugging: An Introduction 1107
FIGURE D.8 Consecutive stepping over (F10) lines of code revealing the array offset problem.
“Run to Cursor” (Ctrl + F10) selected (from the Debug menu), where the cursor would represent a tem-
porary breakpoint. The (yellow)* arrow indicates the line at which execution has paused, as shown in
Figure D.9, and the values of “m_iSize” and “i”, i.e., the size of the array and the final loop index value,
respectively, may be checked for consistency. Here, it is clear that the loop has overwritten the bounds
of the array since in the Watch window, “i” is “20”, yet “m_iSize” in the Variables window is “10”.
The developer could also check the CDebugExDlg::PrintVector() function to make sure
that the vector “v” were not being altered erroneously. In Figure D.9, the (yellow) arrow denoting the
current line to be executed contains the call to PrintVector(), and stepping into (F11) the member
method would place the arrow on the first line of the invoked function. There, the user could either
step through the function using step over (F10) or, upon realizing that the code does not change the
contents of the “v” array, could step out (Shift + F11) of the function, causing the rest of the func-
tion to execute resulting in the PrintVector()-based AfxMessageBox() being displayed, then
returning the user to the next executable line following the function call in the calling environment,
i.e., in CDebugExDlg::OnDebugExDlgBtnOverwrite(), at the “delete [] v;” statement.
* The colors placed in brackets in Appendix D are those observable when using the debugger and a color monitor, and serve
to clarify the items being referred to.
1108 Appendix D: Debugging: An Introduction
FIGURE D.9 Placing the mouse cursor beneath the loop and selecting “Run to Cursor” (Ctrl + F10) to obtain
the values of “m_iSize” and “i” at the end of the loop, revealing the offset error.
(a) (b)
FIGURE D.10 Setting the “Edit and Continue” option: (a) the Debug tab of the Options dialog and (b) the
C/C++ tab of the Project Settings dialog, with relevant fields selected.
changes to any of the following: (1) a header file, (2) a C++ class definition, (3) a function prototype,
or (4) a global function or static member function.
To make sure that the “Edit and Continue” option is enabled, Gregory [3] indicates that the user
should choose Tools/Options, followed by the Debug tab, and then select the lower right check box
named “Debug commands invoke Edit and Continue”, as shown in Figure D.10a. Then, upon choos-
ing Project/Settings, followed by the C/C++ tab, the “Debug info” item should have the “Program
Database for Edit and Continue” selected as shown in Figure D.10b.
Figure D.11a shows a breakpoint set on the line of the for loop, “for(i=0; i<(m_iSize+10); i++)”,
where the current iteration index “i” has value “5”. However, at this point, the developer may realize
Appendix D: Debugging: An Introduction 1109
(a)
(b)
FIGURE D.11 (a) A breakpoint set on the line of an erroneous for loop where the iteration index “i” is “5”.
(b) The code edited and debugging continued: the breakpoint is updated to the new for loop statement where
the correct upper bound prevents overwriting the end of the array.
1110 Appendix D: Debugging: An Introduction
the mistake in the code and comment out the erroneous line and in its place, use the alternative loop
condition, “for(i=0; i<m_iSize; i++)”, as shown in Figure D.11b, and continue stepping over code
until the end of the loop. It may be verified that the final value of “i” is “10”, which does not satisfy
the condition, “i<m_iSize”, and hence, the loop ends without overwriting the end of the array, “v”.
In addition, in the (lower) Build output pane in Figure D.11b, the developer will notice the “Edit
and Continue – 0 error(s), 0 warning(s)” statement, indicating that editing has been performed and
debugging continued.
FIGURE D.12 Memory leaks reported in the Debug Output window of the IDE, and upon selection, the
(blue) arrow indicates the offending variable in the editor area.
Appendix D: Debugging: An Introduction 1111
FIGURE D.13 Debug Assertion Failed message concerning memory on the heap.
The developer can prevent the leak by setting “m_iDelete = 1”, in which case no memory leaks are
present or reported.
If the developer sets “m_iDelete = 2” to experiment with the erroneous double deletion of mem-
ory and runs the code with the debugger, then the Debug Assertion Failed error message presented
in the dialog window (Figure D.13) indicates a problem with memory on the heap.
If the developer clicks Retry to debug the DebugEx application, the following screen is presented
(Figure D.14a), and the Call Stack window shows the flow of control that led to the crash. In par-
ticular, the function CDebugExDlg::OnDebugExDlgBtnMemory() calls operator delete, and
(a)
FIGURE D.14 (a) The line of code showing the assertion failure after the user clicks Retry on the Microsoft
Visual C++ Debug Library dialog box (Figure D.13).
(continued)
1112 Appendix D: Debugging: An Introduction
(b)
FIGURE D.14 (continued) (b) The offending line indicated by the (green) arrow as a result of double-clicking
the CDebugExDlg::OnDebugExDlgBtnMemory() function in the Call Stack window.
if the user double-clicks on this member method, then the (green) arrow appears on the line of the
offending code as shown in Figure D.14b.
In addition, the developer will notice that the address of the vector “v” displayed in the Variables
window is “0x004211a0” and that the call to operator delete in the call stack uses an input argument
with the same address, confirming that the erroneous deletion involves this data structure.
The previous example presents a simplified memory-related problem that is clear and straight-
forward to solve. However, in practice, this may not be the case, and the developer may in fact have
to search for the address of the object that is the subject of a memory leak or memory overwrite.
Other tools exist to detect a memory leak, e.g., using CMemoryState objects and the
Checkpoint() and Difference() methods to determine the difference in the state of memory
usage, and these may have to be experimented with to obtain more specific information about
the nature of a memory-related problem. The interested reader should look up the “Using MFC
Debugging Support” topic in the MSDN Library Visual Studio 6.0 [2], which contains information
on the following: MFC debugging support, diagnostic features, using the ASSERT_VALID macro,
tracking memory allocations, detecting memory leaks, using object dumps, and viewing the call
stack after an MFC assert.
D.5 SUMMARY
The Microsoft Visual C++ 6.0 IDE [1] debugger has numerous features to assist the developer in
debugging applications, including the setting of breakpoints; viewing of output in the Variables,
Watch, and Quick Watch windows; viewing the call stack in the Call Stack window; and incremen-
tally exploring code using the Step Into, Step Over, Step Out, and Run to Cursor options on the
Debug menu.
Appendix D: Debugging: An Introduction 1113
A simple dialog-based application with MFC support was built to explore various scenarios
invoked through event-handler functions attached to the buttons on the dialog, i.e., changing class
member data, overwriting the end of an array, and allocation and deallocation of memory. The
application was then used to explore five key debugging features: (1) the setting of breakpoints,
(2) the monitoring of variable values and expressions using the Variables and Watch windows,
(3) the examination of the flow of control using the Call Stack window, (4) the use of Edit and
Continue to make changes to a program while in the process of debugging without rebuilding,
and (5) the detection and resolution of memory leaks by observing output automatically presented
in the Debug Output window when running a Debug-build configuration of the application with
the debugger.
REFERENCES
1. Microsoft Visual C++® 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
2. Microsoft Developer Network Library Visual Studio 6.0, Microsoft® Visual Studio™ 6.0 Development
System, Microsoft Corporation, 1998.
3. Gregory, K., Using Visual C++ 6: Special Edition, Que Publishing, Indianapolis, IN, 1998.
Appendix E: MatrixInversion: Win32
Console Application
E.1 INTRODUCTION
Two common tasks often encountered in computational linear algebra are the computation of
sets of linear equations and the determination of the inverse of a square matrix. The Win32
Console Application titled MatrixInversion, presented here, is used to compute the inverse A−1 of
a matrix A and the solution vector x of a system, Ax = b. The work of Press et al. [1] is closely
followed, where the “Gauss–Jordan elimination with full pivoting” method is used. The only main
alteration made to the code of Press et al. was to use an epsilon value to test for essentially zero
values on the diagonal, i.e., if “Akk ∈ [−ɛ, ɛ]” for ɛ = 10 −6, then the matrix is deemed to be sin-
gular: this was used in place of the original test for “Akk = 0”. Various other syntactic changes
were made for ease of reading. The developer should consult the work of Press et al. [1] for the
original version of the code.
where
A is a n × n leading matrix
xi are the vectors of unknowns, for i ∈ [1, m] of a total of m simultaneous systems, of the form
Axi = bi (E.2)
⇒ xi = A−1bi (E.3)
AY = I (E.4)
⇒ Y = A−1 I (E.5)
is used where Y holds the inverse A−1 of the leading matrix A at the end of system computation.
The function prototype used, i.e., void GaussJordanEliminationFullPivoting
(double **A, int n, double **B, int m), involves two matrices, A and B, that are
passed into the function: on entry A is the leading matrix and B an n × m matrix composed of
the m right-hand-side vectors bi, and on return, A holds its own inverse A−1 (E.5) and B holds the
1115
1116 Appendix E: MatrixInversion: Win32 Console Application
m solution vectors xi (E.3). If only the inverse of a matrix is desired and no solution xi sought, then
a “dummy” right-hand-side vector can be used, as it is in the functions presented in the following
that actually call GaussJordanEliminationFullPivoting().
// MatrixInverse.h
#ifndef MATRIX_INVERSE_H
#define MATRIX_INVERSE_H
// -- defined vars
#define NROWS 4
#define NCOLS 6
// -- static vars
static int prec = 15;
// static int nrows = 4; // NOTE: can’t use static ints for
array dimensions.
// static int ncols = 5;
// Matrix-based functions
void GaussJordanEliminationFullPivoting(double **A, int n, double
**B, int m);
void MatrixMult(int nrowsA, int ncolsA, int ncolsB, double **A,
double **B, double **C);
double MatrixNorm(int nrows, int ncols, double **M);
int SetupMatrix(void);
// Misc. fns
void Swap(double &a, double &b);
// Print fns
int PrintMatrix(double **M, int nrows, int ncols);
int PrintVector(double *v, int ncols);
// -- classes
// none yet.
#endif
// eof
Appendix E: MatrixInversion: Win32 Console Application 1117
// Title: MatrixInverse.cpp
// Purpose: Performs matrix inversion using Gauss-Jordan elimination with
full pivoting.
// Source: Numerical Recipes in C: The Art of Scientific Computing
(2nd Ed.), sections 2.0 − 2.1.
#include <iostream>
#include <iomanip>
#include <math.h>
#include <time.h>
#include “MatrixInverse.h”
using namespace std;
int SetupMatrix(void)
{
cout << “ SetupMatrix()\n”;
int i;
int j;
int cnt = 0;
int nrows = NROWS;
int ncols = NCOLS;
double *vector = NULL; // vector is a ptr to double.
double **matrix = NULL; // matrix
// MEMORY NEW
// Allocate an array of doubles, and return the & to vector: hence
vector is of type ptr.
vector = new double[ncols];
1118 Appendix E: MatrixInversion: Win32 Console Application
// SETUP MATRIX
cout << endl;
for(i=0;i<nrows;i++)
{
for(j=0;j<ncols;j++)
{
matrix[i][j] = cnt;
cnt++;
}
}
// SETUP VECTOR
for(i=0;i<ncols;i++)
{
vector[i] = i+3;
}
PrintVector(vector, ncols);
// MEMORY DELETE
delete [] vector;
for(i=0;i<nrows;i++)
{
delete [] matrix[i];
}
delete [] matrix;
return 0;
}
int PrepareMatrixMult()
{
int i;
int j;
int cnt = 0;
int nrowsA = 2;
int ncolsA = 3;
int nrowsB = ncolsA;
int ncolsB = 4;
Appendix E: MatrixInversion: Win32 Console Application 1119
// MEMORY ALLOC
A = new double *[nrowsA]; // allocate an array of ptrs-to-double
for(i=0;i<nrowsA;i++)
{
A[i] = new double[ncolsA]; // allocate an array of doubles,
return & to matrix[i]
}
B = new double *[nrowsB]; // allocate an array of ptrs-to-double
for(i=0;i<nrowsB;i++)
{
B[i] = new double[ncolsB]; // allocate an array of doubles,
return & to matrix[i]
}
// SETUP MATRICES
for(i=0; i<nrowsA; i++)
{
for(j=0; j<ncolsA; j++)
{
cnt++;
A[i][j] = cnt;
}
}
cnt = −1;
for(i=0; i<nrowsB; i++)
{
for(j=0; j<ncolsB; j++)
{
cnt++;
B[i][j] = cnt;
}
}
// Matrix mult
MatrixMult(nrowsA, ncolsA, ncolsB, A, B, C);
PrintMatrix(A, nrowsA, ncolsA);
PrintMatrix(B, nrowsB, ncolsB);
PrintMatrix(C, nrowsC, ncolsC);
// -- FREM MEM
// Delete A
1120 Appendix E: MatrixInversion: Win32 Console Application
for(i=0;i<nrowsA;i++)
{
delete [] A[i];
}
delete [] A;
// Delete B
for(i=0;i<nrowsB;i++)
{
delete [] B[i];
}
delete [] B;
// Delete C
for(i=0;i<nrowsC;i++)
{
delete [] C[i];
}
delete [] C;
return 0;
}
// Matrix multiplication
for(k=0; k<ncolsB; k++)
{
for(i=0; i<nrowsA; i++)
{
for(j=0; j<ncolsA; j++)
{
C[i][k] = C[i][k] + A[i][j]*B[j][k];
//printf(“ C[%d][%d] = %lf\n”, i, k, C[i][k]);
}
}
}
Appendix E: MatrixInversion: Win32 Console Application 1121
return;
}
int PrepareSwap(void)
{
double a = 1;
double b = 2;
Swap(a,b);
return 0;
}
void Swap(double &a, double &b)
{
// NOTE: pass by reference uses syntactically clean pass-by-reference
mechanism, i.e. without requiring
// client of the fn to pass an address, which would then be reflected
by a ptr-to-double in the arg. list.
double temp;
// Swap a and b
temp = a;
a = b;
b = temp;
}
int PrepareMatrixInversion(void)
{
int i;
int j;
int nrowsA = 3;
int ncolsA = 3;
int nrowsB = 3;
int ncolsB = 1;
double norm; // matrix Euclidean norm of matrix difference
double sum = 0.0; // sum of squares of error
double **A = NULL; // leading matrix in A.x = b
double **A_copy = NULL; // copy of leading matrix in A.x = b
double **B = NULL; // r.h.s. vector in A.x = b
double **B_copy = NULL; // copy of r.h.s. vector in A.x = b
double **C = NULL; // matrix used to check that A.A∧−1 = I
double **D = NULL; // matrix used to check that A.A∧−1.b = b
double **E = NULL; // Error matrix = B_copy − D. Here D =
A.A∧−1.b
cout << “\n PrepareMatrixInversion()\n”;
// MEMORY ALLOC
A = new double *[nrowsA]; // allocate an array of ptrs-to-double
for(i=0;i<nrowsA;i++)
{
A[i] = new double[ncolsA]; // allocate an array of doubles,
return & to matrix[i]
}
1122 Appendix E: MatrixInversion: Win32 Console Application
// Setup A and B
A[0][0] = 1.0;
A[1][0] = 2.0;
A[2][0] = 1.0;
A[0][1] = 2.0;
A[1][1] = 5.0;
A[2][1] = 0.0;
A[0][2] = 3.0;
A[1][2] = 3.0;
A[2][2] = 8.0;
B[0][0] = 1.0;
B[1][0] = 1.0;
B[2][0] = 1.0;
Appendix E: MatrixInversion: Win32 Console Application 1123
// Make a copy of A
for(i=0; i<nrowsA; i++)
{
for(j=0; j<ncolsA; j++)
{
A_copy[i][j] = A[i][j];
}
}
// Make a copy of B
for(i=0; i<nrowsB; i++)
{
for(j=0; j<ncolsB; j++)
{
B_copy[i][j] = B[i][j];
}
}
// Print A and B
// Check the matrix norm of the difference bw. original and recovered
B matrices
norm = MatrixNorm(nrowsB, ncolsB, E);
//printf(“\n norm = %7.4lf\n”, norm);
// MEMORY DELETE
// Delete A
for(i=0;i<nrowsA;i++)
{
delete [] A[i];
}
delete [] A;
// Delete A_copy
for(i=0;i<nrowsA;i++)
{
delete [] A_copy[i];
}
delete [] A_copy;
// Delete B
for(i=0;i<nrowsB;i++)
{
delete [] B[i];
}
delete [] B;
// Delete B_copy
for(i=0;i<nrowsB;i++)
{
delete [] B_copy[i];
}
delete [] B_copy;
// Delete C
for(i=0;i<nrowsA;i++)
{
delete [] C[i];
}
delete [] C;
// Delete D
for(i=0;i<nrowsB;i++)
{
delete [] D[i];
}
delete [] D;
// Delete E
for(i=0;i<nrowsB;i++)
{
delete [] E[i];
}
delete [] E;
return 0;
}
Appendix E: MatrixInversion: Win32 Console Application 1125
int PrepareMatrixInversionLargeSystem(void)
{
int i;
int j;
int nrowsA = 100;
int ncolsA = 100;
int nrowsB = 100;
int ncolsB = 1;
double max = 0.0;
double min = 0.0;
double norm; // matrix Euclidean norm of matrix difference
double random_no; // random no.
double sum = 0.0; // sum of squares of error
double uniformly_rand; // uniformly random no.
double **A = NULL; // leading matrix in A.x = b
double **A_copy = NULL; // copy of leading matrix A
double **B = NULL; // r.h.s. vector in A.x = b
double **C = NULL; // product of A.A∧−1 = I
double **E = NULL; // error matrix
// MEMORY ALLOC
A = new double *[nrowsA]; // allocate an array of ptrs-to-double
for(i=0;i<nrowsA;i++)
{
A[i] = new double[ncolsA]; // allocate an array of
doubles, return & to matrix[i]
}
// Seed the random no. generator with time value to get truly
random nos.
srand( (unsigned)time(NULL) );
// Setup A and B
for(i=0; i<nrowsA; i++)
{
for(j=0; j<ncolsA; j++)
{
random_no = rand();
uniformly_rand = double(random_no)/double(RAND_MAX);
A[i][j] = uniformly_rand;
A_copy[i][j] = A[i][j];
}
}
// Print A and B
cout << “\n A:\n”;
PrintMatrix(A, nrowsA, ncolsA);
// Check the matrix norm of the difference bw. the identity and C
norm = MatrixNorm(nrowsA, ncolsA, E);
//printf(“\n norm = %7.4lf\n”, norm);
// MEMORY DELETE
// Delete A
for(i=0;i<nrowsA;i++)
{
delete [] A[i];
}
delete [] A;
// Delete A_copy
for(i=0;i<nrowsA;i++)
{
delete [] A_copy[i];
}
delete [] A_copy;
// Delete B
for(i=0;i<nrowsB;i++)
{
delete [] B[i];
}
delete [] B;
// Delete C
for(i=0;i<nrowsA;i++)
{
delete [] C[i];
}
delete [] C;
// Delete E
for(i=0;i<nrowsB;i++)
{
delete [] E[i];
}
delete [] E;
return 0;
}
// by, W.H. Press, S.A. Teukolsky, W.T. Vetterling and B.P. Flannery.
// Only minor syntactic changes are made here to the original,
e.g. array indexing
// (0 to n-1 is used here, rather than the original 1 to n).
and
A.Y = I
=> Y = A∧-1
B:nxm = input matrix containing the m r.h.s. vectors (of length n).
This is overwritten with the corres. set of soln. vectors. That is,
x_1 = A∧-1.b_1,
x_2 = A∧-1.b_2,
x_3 = A∧-1.b_3.
*/
// Declaration
int i;
int icol; // col index
int irow; // row index
int j;
int k;
int u;
int v;
int *index_c = NULL; // col index used for pivoting
int *index_r = NULL; // row index used for pivoting
int *ipiv = NULL; // pivot index array
double big;
double dum;
double pivinv;
double eps = 1.0e-6; // ALTERATION: epsilon value to check for
a “zero” on the diagonal
// MEMORY NEW
// Integer arrays used to manage the pivoting
index_c = new int[n];
index_r = new int[n];
ipiv = new int[n];
++(ipiv[icol]);
if(irow != icol)
{
for(u=0; u<n; u++)
{
Swap(A[irow][u], A[icol][u]);
}
// Now the pivot row can be divided by the pivot element, located
at irow and icol.
pivinv = 1.0/A[icol][icol];
A[icol][icol] = 1.0;
// Now the rows are reduced, except for the pivot one.
for(v=0; v<n; v++)
{
if(v != icol)
{
dum = A[v][icol];
A[v][icol] = 0.0;
for(u=0; u<n; u++)
{
A[v][u] -= A[icol][u]*dum;
}
{
Swap(A[k][index_r[u] ], A[k][index_c[u] ]);
}
}
}
// MEMORY DELETE
delete [] index_c;
delete [] index_r;
delete [] ipiv;
}
E.4 SUMMARY
The Win32 Console Application, MatrixInversion, presents exploratory code to compute
the inverse of a matrix and the solution to a system of linear equations using the “Gauss–
Jordan elimination with full pivoting” method of Press et al. [1]. The key functions used are
GaussJordanEliminationFullPivoting(), MatrixMult(), MatrixNorm(),
PrepareMatrixInversion(), and Swap(), which compute a system of equations, perform
matrix multiplication, determine the matrix norm, prepare the system to be computed, and swap
values, respectively.
REFERENCE
1. Press, W. H., Teukolsky, S. A., Vetterling, W. T., and Flannery, B. P., Numerical Recipes in C: The Art of
Scientific Computing, 2nd edn., Cambridge University Press, Cambridge, U.K., 2002.
Appendix F: Using DiagramEng
F.1 INTRODUCTION
The DiagramEng software application provides users the means to efficiently represent mathematical
equations, perform interactive and intuitive model building, and conduct control engineering
experiments. The software incorporates block icons, representing model components and adjoining
connections, signifying a relationship between them.
DiagramEng has been built with Microsoft Visual C++ 6.0 [1] using the Microsoft Foundation
Classes (MFC) and the Standard Template Library (STL). The graphical user interface involves
menus, toolbars, a block library tree-like browser for block selection, and a palette upon which a
diagram, consisting of blocks and connections, can be drawn. The computation of a system model
is performed either directly or iteratively through the use of a Newton-method-based nonlinear
solver. A range of blocks, including the Derivative and Integrator blocks, used for numerical dif-
ferentiation and integration, respectively, allow the user to model time-based linear and nonlinear
differential equations.
The remainder of this Using DiagramEng document discusses functionality provided in the
menus and toolbars and provides examples of how to model typical real-world engineering prob-
lems and compute results that may be visualized or saved to an output data file.
F.2 MENUS
There are two frame-based sets of menus used in the DiagramEng application: (1) the Main
frame–based menus, which include the File, View, and Help menus (Figure F.1), and (2) the
Child frame–based menus (Figure F.2) which include the File, Edit, View, Model, Simulation,
Format, Tools, Window, and Help menus. A listing of menu-based functionality is presented in
Tables F.1 through F.13.
1133
1134 Appendix F: Using DiagramEng
FIGURE F.2 Child frame–based menus: File, Edit, View, Model, Simulation, Format, Tools, Window, and Help.
TABLE F.1
Main Frame–Based File Menu Entries and Function
File Menu Entry Function
New Creates a new empty child document
Open Opens an existing document previously saved to a model data file
Print Setup Sets up the printer properties
Recent Files Lists the four most recent files
Exit Exits the application, prompting the user to save unsaved files
TABLE F.2
Main Frame–Based View Menu Entries
and Function
View Menu Entry Function
Toolbar Shows or hides the Toolbar
Status Bar Shows or hides the Status Bar
TABLE F.3
Main Frame–Based Help Menu Entry and Function
Help Menu Entry Function
About DiagramEng Displays program, version number, and copyright information
Appendix F: Using DiagramEng 1135
TABLE F.4
Child Frame–Based File Menu Entries and Function
File Menu Entry Function
New Creates a new document in a new child document window
Open Opens existing document, prompting the user to save the current document if it
already exists in the child document window
Close Closes a document, prompting the user to save if the document content has not already
been saved
Save Saves the active document to a file
Save As Saves the active document to a new file
Print Prints the active document
Print Preview Previews the active document prior to printing
Print Setup Sets up the printer properties
Recent File Lists the four most recent files
Exit Exits the application, prompting the user to save unsaved files
TABLE F.5
Child Frame–Based Edit Menu Entries and Function
Edit Menu Entry Function
Undo Undoes the last system model–based editing action
Redo Redoes the last system model–based editing action
Cut Cuts the selection and places it on the Clipboard
Copy Copies the selection and places it on the Clipboard
Paste Inserts Clipboard contents onto the system model diagram
Delete Grouped Items Deletes items grouped by an enclosing rectangular region
Select All Selects all document content with an enclosing rectangular region
Add Multiple Blocks Presents a block library dialog window for multiple block selection
TABLE F.6
Child Frame–Based View Menu Entries and Function
View Menu Entry Function
Toolbar Shows or hides the toolbar
Status Bar Shows or hides the status bar
Common Ops. Toolbar Shows or hides the Common Operations toolbar
Common Blocks Toolbar Shows or hides the Common Blocks toolbar
Block Directory Shows or hides the block directory tree
Auto Fit Diagram Automatically fits diagram to view
Zoom In Zooms into detail, enlarging the size of the diagram
Zoom Out Zooms out of detail, reducing the size of the diagram
Reset Diagram Resets diagram to original size prior to zooming operations
1136 Appendix F: Using DiagramEng
TABLE F.7
Child Frame–Based Model Menu Entries and Function
Model Menu Entry Function
Build Model Builds the active model
Build Subsystem Builds the selected model subsystem (not functional)
TABLE F.8
Child Frame–Based Simulation Menu Entries and Function
Simulation Menu Entry Function
Start Starts the simulation, invoking Build Model if model not already built
Stop Stops the simulation
Numerical Solver Sets the numerical solver parameters (not all fields are functional)
TABLE F.9
Child Frame–Based Format Menu Entry and Function
Format Menu Entry Function
Show Annotations Shows or hides diagram annotations if present
TABLE F.10
Child Frame–Based Tools Menu Entry and Function
Tools Menu Entry Function
Diagnostic Info. Presents process and system memory utilization statistics
TABLE F.11
Child Frame–Based Window Menu Entries and Function
Window Menu Entry Function
New Window Opens another window for the active document
Cascade Arranges windows so they overlap
Tile Arranges windows as nonoverlapping tiles
Arrange Icons Arranges icons at the bottom of the window
Close All Documents Closes all documents and prompts the user to save if necessary
Name of child windows Shows names of windows and activates the selected window
Appendix F: Using DiagramEng 1137
TABLE F.12
Child Frame–Based Help Menu Entries and Function
Help Menu Entry Function
About DiagramEng Displays program, version number, and copyright information
Using DiagramEng Displays information about using the DiagramEng application
TABLE F.13
Context Menu Entries and Function
Context Menu Entry Function
Delete Item Deletes selected block, connection, or connection bend point
Delete Grouped Items Deletes items grouped by an enclosing rectangular region
Fine Move Item Moves an item using the arrows keys
Format Annotation Formats an existing annotation or inserts a new one
Insert Bend Point Inserts a bend point upon a connection object
Reverse Block Reverses the direction of a block
Set Output Signal Sets block output connection-based signal
Set Properties Sets block, port, and numerical solver properties
F.3 TOOLBARS
The three application toolbars are (1) the standard Main frame–based toolbar and the Child
frame–based toolbars, (2) Common Operations, and (3) Common Blocks, as may be seen in
Figure F.2. Tables F.14 through F.16 present information about the toolbar buttons and their asso-
ciated functionality.
TABLE F.14
Standard Main Frame–Based Toolbar
Toolbar Button Function
New Creates a new, empty child document
Open Opens an existing document previously saved to a model data file
Save Saves the active document to a file
Cut Cuts the selection and places it on the Clipboard
Copy Copies the selection and places it on the Clipboard
Paste Inserts Clipboard contents onto the system model diagram
Print Prints the active document
About Displays program, version number, and copyright information
The shaded entries are inactive since they relate to the Child frame–based document
(in the order displayed on the toolbar from left to right).
1138 Appendix F: Using DiagramEng
TABLE F.15
Common Operations Toolbar Buttons and Their Associated
Functionality
Toolbar Button Function
Select All Selects all document content with an enclosing rectangular region
Add Multiple Blocks Presents a block library dialog window for multiple block selection
Auto Fit Diagram Automatically fits diagram to view
Build Model Builds the active model
Start Simulation Starts the simulation, invoking Build Model if not already built
Stop Simulation Stops the simulation
Numerical Solver Sets the numerical solver parameters (not all fields are functional)
Show Annotations Shows or hides diagram annotations if present
Track Multiple Items Selects and moves multiple items
Edit Box Control Displays the current simulation time and final execution time
TABLE F.16
Common Blocks Toolbar Buttons and Their Associated
Functionality
Toolbar Button Function
Derivative Block Adds a Derivative block to the system model
Integrator Block Adds an Integrator block to the system model
Transfer Function Block Adds a Transfer Function block to the system model
Divide Block Adds a Divide block to the system model
Gain Block Adds a Gain block to the system model
Sum Block Adds a Sum block to the system model
Output Block Adds an Output block to the system model
Constant Block Adds a Constant block to the system model
Linear Function Block Adds a Linear Function block to the system model
Signal Generator Block Adds a Signal Generator block to the system model
Subsystem Block Adds a Subsystem block to the system model
Subsystem In Block Adds a Subsystem In block to the system model
Subsystem Out Block Adds a Subsystem Out block to the system model
FIGURE F.3 Main frame–based toolbar with the enabled buttons (no child document is present).
Appendix F: Using DiagramEng 1139
FIGURE F.4 Child frame–based toolbars: Common Operations toolbar (second from the top) and the
Common Blocks toolbar (third from the top), where the top Main frame toolbar is still shown.
FIGURE F.5 All blocks of the Common Blocks toolbar displayed on the palette.
F.4 EXAMPLES
Real-world engineering problems typically involve second-order linear differential equations that
need to be converted to first-order equations using order reduction, prior to their numerical integra-
tion, to obtain the trajectories of the dependent variables and their time derivatives. In addition, non-
linear dynamical systems often possess coupling and oscillatory dynamics that can be conveniently
modeled using feedback loops and computed with the Newton-method-based nonlinear solver and
the Integrator block. The following examples show the user how to model differential equations and
nonlinear dynamical systems.
1140 Appendix F: Using DiagramEng
where m, b, k, y(t), and u(t) are the mass, damping constant, spring constant, output mass displace-
ment from the equilibrium position, and external force input to the system, respectively. An order
reduction is used to reduce the second-order system to two first-order equations, where x1(t) = y(t)
and x2(t) = y∙(t) and results in the following system:
⎡ x1 (t ) ⎤ ⎡ 0 1 ⎤ ⎡ x1 (t ) ⎤ ⎡ 0 ⎤
⎢ x (t )⎥ = ⎢ − k m + u(t ) (F.2a)
⎣ 2 ⎦ ⎣ − b m ⎥⎦ ⎢⎣ x2 (t )⎥⎦ ⎢⎣ m −1 ⎥⎦
⎡ x1 (t ) ⎤
y(t ) = [1 0] ⎢ ⎥ (F.2b)
⎣ x2 (t ) ⎦
⎡ 0 1 ⎤
⎡ 0 ⎤
A= ⎢ k
⎢− b ⎥⎥ , B = ⎢ −1 ⎥ , C = [1 0 ] and D=0
− ⎣m ⎦
⎣ m m⎦
Students of control engineering will recall that the state and output equations in linear form are
where
x(t), u(t), and y(t) are the state, control, and output vectors, respectively
A(t), B(t), C(t), and D(t) are the state, control, output, and direct transmission matrices, respec-
tively [2]
One will notice on comparing Equations F.2 and F.3 that (F.2a) and (F.2b) are the state and output
equations, respectively. The corresponding block diagram representation of the state and output
equations (F.3) is shown in Figure F.6 [2].
.
The integration in the earlier diagram concerns that of x(t) to yield x(t), and since x(t) = [y(t), y∙(t)]T,
the initial condition for the Integrator block is x(0) = [y(0), y∙(0)]T (e.g., if the mass is initially at rest
with displacement 2.0 m, then x(0) = [2, 0]T).
The engineer can draw Figure F.6 using the DiagramEng application, as shown in Figure F.7,
and enter various selections of mechanical properties, m, b, and k and forcing functions u(t), to
generate different displacement outputs y(t) and to analyze the physical response behavior of the
mass–spring–damper system.
The general analytic solution yg(t) ≡ y(t) to (F.1) is the sum of the homogeneous solution yh(t) and
the particular solution yp(t), i.e.,
yg (t ) = yh (t ) + yp (t ) (F.4)
Appendix F: Using DiagramEng 1141
A(t)
2
+ y(t)
u(t) B(t) + ∫ . dt C(t) + Out
x(t) x(t) +
0 1 3 4 6 7 8
D(t)
FIGURE F.6 Block diagram representation of the state and output equations (F.3). (From Ogata, K., Modern
Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.)
FIGURE F.7 Block diagram model of the state and output equations (F.3) drawn with DiagramEng.
The homogeneous solution is obtained by setting the right-hand side of (F.1) to zero, as follows:
b k
⇒ y(t ) + y (t ) + y(t ) = 0 (F.5b)
m m
b k
r2 + r+ =0 (F.6a)
m m
with roots
r1,2 =
1
2m (
−b ± b2 − 4km ) (F.6b)
1142 Appendix F: Using DiagramEng
where
ω = β is the angular frequency
Ci, for i = 1, 2, are constants [3]
Values for the constants may be determined with knowledge of quantities b, k, m, u(t) and the initial
conditions x(0). The mathematician will notice here that since α < 0, yh(t) → 0 as t → ∞, with a
decaying oscillatory motion.
Both roots are real but negative, and hence, yh(t) → 0 as t → ∞, without oscillation.
The particular solution of (F.1) may be found by the Method of Undetermined Coefficients, and
given initial conditions, the coefficients may be determined and a general solution found.
Figure F.8a through c illustrates the three different damping conditions where the homogeneous
solutions (yh(t)) are those provided by (F.7 through F.9) and yp(t) = 1/k (given u(t) = 1) for the initial
conditions x(0) = [2, 0]T: (1) Figure F.8a shows underdamped oscillatory vibration, where yg(t) → 1.0
as t → ∞, for u(t) = 1, m = 1 = k, and b = 0.5; (2) Figure F.8b shows critically damped nonoscillatory
vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1, m = 1 = k, and b = 2; and (3) Figure F.8c shows
overdamped nonoscillatory vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1, m = 1 = k, and b = 3.
dx
= αx − βxy (F.10a)
dt
Appendix F: Using DiagramEng 1143
1.855350
1.710698
1.566047
1.421395
1.276744
f (t)
1.132092
0.987441
0.842790
0.698138
0.553487
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(a) t (s)
1.900001
1.800001
1.700001
1.600001
f (t)
1.500001
1.400000
1.300000
1.200000
1.100000
1.000000
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(b) t (s)
Plot of f (t) vs. t
2.000001
1.900057
1.800113
1.700169
1.600225
f (t)
1.500281
1.400337
1.300393
1.200449
1.100505
1.000561
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(c) t (s)
FIGURE F.8 (a) Underdamped (b2 − 4km < 0) oscillatory vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1,
m = 1 = k, b = 0.5, x(0) = [y(0), y∙(0)]T = [2, 0]T, and δt = 10 −3 s. (b) Critically damped (b2 − 4km = 0) nonoscil-
latory vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1, m = 1 = k, b = 2, x(0) = [y(0), y∙(0)]T = [2, 0]T, and δt =
10−3 s. (c) Overdamped (b2 − 4km > 0) nonoscillatory vibration, where yg(t) → 1.0 as t → ∞, for u(t) = 1, m = 1 = k,
b = 3, x(0) = [y(0), y∙(0)]T = [2, 0]T, and δt = 10 −3 s.
1144 Appendix F: Using DiagramEng
dy
= − γy + δxy (F.10b)
dt
where
t represents the independent time variable
α and γ are the rates of growth of the prey and predator, respectively
β and δ are the rates of competitive efficiency for the prey and predator species, respectively,
where α, β, γ, δ > 0
A block diagram representation of this system (F.10) and its DiagramEng implementation are shown
in Figures F.9 and F.10, respectively, where the input signals are the growth rates α and γ, which may
be initially chosen given a condition of no interaction (β, δ = 0) between the species (block numbers
appear beneath the blocks).
In the case where β, δ = 0, the equations of the populations are
dx
= αx ⇒ x(t ) = C1eαt (F.11a)
dt
dy
= − γy ⇒ y(t ) = C2e − γt (F.11b)
dt
where
C1 and C2 are constants
x(t) and y(t) are exponentially increasing and decreasing functions of time, for the prey and
predator populations, respectively
The population of the prey in the absence of the predator increases, and that of the predator decreases.
Mathematicians familiar with the study of nonlinear dynamical systems and chaos (see, e.g., the
texts [4] and [5]) will recognize that the fixed points occur when the populations are in equilibrium,
i.e., when x. (t) = 0 and y∙(t) = 0, resulting in two such points: (x0, y0) = (0, 0) and (x1, y1) = (γ/δ, α/β).
.
x(t) x(t)
α + ∫dt Out
–
0 2 4 6 10
9
+ .
y(t) y(t)
γ – ∫dt Out
1 3 5 7 11
FIGURE F.9 A block diagram representation of the Lotka–Volterra system of two coupled first-order non-
linear differential equations (F.10).
Appendix F: Using DiagramEng 1145
FIGURE F.10 The block diagram representation of the Lotka–Volterra system made using DiagramEng.
The stability of these points may be determined by observing the eigenvalues of the Jacobian matrix
of the system (F.10), i.e.,
⎡α − βy −βx ⎤
=⎢ (F.12b)
⎣ δy δx − γ ⎥⎦
For (x0, y0) = (0, 0), the eigenvalues of J(x, y) are λ1 = α and λ2 = −γ, with corresponding eigenvec-
tors v1 = [1, 0]T (the unstable manifold, x axis) and v2 = [0, 1]T (the stable manifold, y axis), respec-
tively, and hence, the critical point is a saddle point and the system is unstable, implying that the
extinction of both species is unlikely.
For (x1, y1) = (γ/δ, α/β), the eigenvalues of J(x, y) are λ1,2 = ±i αγ , i.e., they are purely imaginary
(Re(λ1,2) = 0), indicating the presence of a center (in the positive quadrant) rather than a spiral, and
Kibble states that as a result, there are cyclic variations in x(t) and y(t) which are not in phase [4] (the
population of the predators grows while that of the prey declines and vice versa).
A simulation of the Lotka–Volterra system (F.10) was made with the parameters α = 2, γ = 2,
β = 1 and δ = 0.5, where the initial conditions of integration were x(0) = 20 and y(0) = 10, the initial
output signals for the Divide blocks (which must be set since the Divide blocks are involved in two
feedback loops) were x4(t0) = −8 and x5(t0) = 8, and a time-step size of δt = 10 −4 s was chosen. The
cyclical variations in the populations of the prey and predators are shown in Figure F.11a and b,
respectively, where the population of the prey leads that of the predator, i.e., the variations are in
fact not in phase.
The phase portrait of the population of the predator vs. the prey, i.e., y(t) vs. x(t), may be gener-
ated by saving the output data through the Output blocks and plotting the two population values
1146 Appendix F: Using DiagramEng
28.535910
25.366511
22.197111
19.027711
f (t)
15.858311
12.688911
9.519511
6.350111
3.180712
0.011312
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(a) t (s)
14.281638
12.695432
11.109227
9.523021
f (t)
7.936816
6.350610
4.764405
3.178199
1.591994
0.005789
0.000000 2.000000 4.000000 6.000000 8.000000 10.000000 12.000000 14.000000 16.000000 18.000000 20.000000
(b) t (s)
against each other as shown in Figure F.12 (using a third-party graphical application). A saddle point
resides at the origin (x0, y0) = (0, 0), and a center is present at (x1, y1) = (γ/δ, α/β). As the prey declines
in number, the predator grows, and vice versa, and neither species becomes extinct. As t → ∞, it is
observed that the trajectories do in fact form a center.
F.5 OUTPUT
The output shown in Figures F.8 and F.11 is obtained by double-clicking the Output block and
selecting the Show Graph button on the OutputBlockDialog dialog window (Figure F.13). If the
underlying numerical data are desired, then these may be saved by selecting the Save Data button
and specifying the appropriate file name and location. Then, a third-party application may be used
to plot the data of different Output blocks against each other, as has been done to produce the phase
portrait of Figure F.12.
Appendix F: Using DiagramEng 1147
12 (x(0), y(0))
10
y(t)
0
0 5 10 15 20 25 30 35
x(t)
FIGURE F.12 Phase portrait of y(t) vs. x(t) showing the change in population of the predator vs. the prey,
where (x(0), y(0) = 20, 10): the saddle point is at (0, 0) and the center at (γ/δ, α/β).
FIGURE F.13 Output block dialog window allowing the user to view the graphical results (Show Graph) or
save the underlying data (Save Data).
where fs(t), for s ∈ {1,…, 4} (four signals are used here for simplicity), are the individual signals
being recorded for each time point ti ∈ [t0, tn], for initial and final simulation time points, t0 and tn,
respectively. The data are written to an output file, with default name “output_data.txt” where each
1148 Appendix F: Using DiagramEng
TABLE F.17
Incomplete Application Items
Item Status
Blocks Subsystem, Subsystem In, Subsystem Out, and Transfer Function blocks
currently do not perform data operations
Model Menu The Build Subsystem entry does not function as subsystem blocks are
not implemented
Numerical Solver The absolute and relative error tolerance parameters are not currently used,
the time-step type is “fixed-step”, and the Integration Method is “Euler (1st Order)”
row of data in the output file corresponds to all signal output for a particular time point ti and the
file is of the following form:
If there are no data in the output matrix, then the number of rows and number of columns are zero,
and only the time points corresponding to the system model simulation parameters will be written
to the output file.
F.7 SUMMARY
The topics covered in the “UsingDiagramEng.pdf” document include (1) the Main frame–based
menu, Child frame–based menu, and Context menu; (2) the Main frame–based toolbar and the
Child frame–based Common Operations and Common Blocks toolbars; (3) two examples con-
cerning ordinary differential equations and nonlinear dynamical systems; (4) forms of output data
including numerical simulation data (“output_data.txt”) and system model data (“model_data.txt”);
and (5) nonfunctional elements that will be completed in the next version of the software.
Appendix F: Using DiagramEng 1149
REFERENCES
1. Microsoft Visual C++ 6.0, Microsoft® Visual Studio™ 6.0 Development System, Professional Edition,
Microsoft Corporation, 1998.
2. Ogata, K., Modern Control Engineering, 4th edn., Prentice Hall, Upper Saddle River, NJ, 2002.
3. Salas, S. L. and Hille, E., Calculus: One and Several Variables (Complex Variables, Differential Equations
Supplement), 6th edn., John Wiley & Sons, New York, 1990.
4. Kibble, T. W. B. and Berkshire, F. H., Classical Mechanics, 5th edn., Imperial College Press, London,
U.K., 2004.
5. Strogatz, S. H., Nonlinear Dynamics and Chaos: With Applications to Physics, Biology, Chemistry, and
Engineering, Addison-Wesley, Reading MA, 1994.
Computer Science; Software Engineering
Software Application
Development
A Visual C++ , MFC,
®
Features
• Presents a comprehensive tutorial on the design and implementation of a block-
diagram-based software application that may be used for engineering modeling and
simulation
• Explains how to build a demonstration software application, and all of the chapters
in the book build upon each other to result in a large, real-world project
• Ties many facets of C, C++, and Visual C++ together to work as a whole with the
Microsoft Foundation Class Library and the Standard Template Library
• Provides the tools and knowledge needed for developers to implement and extend
the applications in the book for their own purposes
• Includes project source code
All computer code is included, allowing developers to extend and reuse the software
modules for their own project work. The book’s tutorial-like approach empowers students
and practitioners with the knowledge and skills required to perform disciplined, quality,
real-world software engineering.
K14813