@While computer hardware has made tremendous progress, software has a long way to go to catch up. With the widening gap between the advancement in hardware and software, is the result that many of the advances in computer hardware go untapped.
It is being predicted that Object-oriented Programming (OOP) will do for software what the microchip has done for hardware. Instead of the microchip, software resolution will be built around 'software' IC's or objects that will be simple, self-contained and reliable software components. OOP has revolutionized the programming world today
what structured programming did 20 years ago. It is already helping the computer industry churn out more reliable, understandable, manageable and reusable software.
WHAT IS OBJECT-ORIENTED PROGRAMMING
Though the term object-oriented programming (OOP) is widely used experts cannot seem to agree on an exact definition. However, the general consensus is that OOP involves defining Abstract Data Types (ADT) representing complex real-world or abstract objects and organizing programs around a collection of ADTs with an eye towards exploiting their common features. The term abstract data type refers to user-defined data types that together with a set of operations can be performed on that data. It is called abstract to distinguish it from the fundamental built-in data types, such as, int, char, float, which are provided by every programming language.
WHY DO WE NEED OBJECT-ORIENTED PROGRAMMING
Object-oriented programming was developed because several limitations were discovered in procedural programming approaches, which were followed by languages such as C, Pascal, Fortran etc. Some of the limitations found in procedural languages were as follows:
Procedural programming languages follow the approach of breaking a problem into smaller units called functions or procedures. As a problem becomes progressively complex, the number of procedures not only increases but they become more difficult to manage, understand and modify.
In conventional procedural programming several functions use common data. Any function or procedure in the program can easily access and modify that data. This can easily hamper the security and integrity of the data. This is a serious drawback with procedural programming languages.
Another problem with procedural languages is that since many functions access the same data, the way data is organized (structure) becomes critical. Reorganization of data requires rewriting of all the functions, which access to common data. This is an extra burden on a programmer.
GOALS OF OOP PARADIGM
New approach to programming: OOPs is an entirely new way of organizing a program. Using this approach, a program will not be organized in terms of the number of functions, procedures or global data (data available to all). Rather, it will combine data and functions into a single unit called an object. As a result, data will be secure from accidental or intentional corruption.
Re-using software components: Rather than manufacturing software components all the time the concept of reusability will result in lower development costs and
software maintenance, as well as save time.
Modeling the real world as close to the user's perspective as possible.
Besides the above mentioned advantages, object orientation also has a number of other features, which will be discussed in detail in the second session. These features are
Abstraction
Encapsulation
Data hiding
Polymorphism
Inheritance
C++ is a programming language which supports all the above mentioned features of OOP.
HISTORY OF C++
C++ programming language was designed and developed by Bjarne Stroustrup at the Computer Science Research Center, AT&T Bell Labs, New Jersey in the early Eighties. Dr. Stroustrup developed C++ to support the writing of complex simulation programs. Totally compatible with and based on the C language, C++ is a superset of it. C++ retains C's power and flexibility to deal with hardware-software interface and low-level system programming. The efficiency, compactness and power of expression of C are retained in C++. Like C, C++ is also case sensitive.
C++ is a hybrid language. The object-oriented paradigm can be fully exploited to produce
a purely object-oriented solution to a problem. C++ can be treated as a procedural language that contains some additional construction beyond C. C++ reflects both the procedural programming paradigm as well as a newer object-oriented paradigm.
C++ is designed to support software developed on a language scale. The increased type-checking present in it, compared to C, reduces errors in the C++ software system and provides for better multiprogramming efforts.
COMPILING AND LINKING
A program written in a high-level language is known as the source code or source file. The source file in C++ has the extension .cpp and like all source files, this file is not in the executable form. In order to convert a source file into an executable one, two processes have to be carried out, namely compiling and linking.
The process of compiling converts a source file into an object file, with the extension .obj. The object file contains machine language instructions that can be executed by the computer. However, the executable file is not obtained until the process of linking is complete.
An executable file always contains more than one object file, because programmers usually divide their programs into several source files. Each of these programs is then converted to an object file. The process of combining these object files is known as linking. Moreover, C++ has certain in-built functions, which are present in a library. These library routines also need to be combined with the user-written program.
C++ PROGRAMMING BASICS
There are certain fundamentals one needs to know in any language before writing even the most elementary programs such as basic program construction, variables and input/output. These are discussed in detail in this chapter. In order to learn about the basic construction of C++ programs, consider the following example.
#include <iostream.h>
void main()
cout<< "Well begun is half done";
FUNCTIONS
Functions are the building blocks of most programming languages and C++ is no exception. Notice that the above program has only one function called main(). Every C++ program must essentially have a main function. When a C++ program is run, the first statement that is executed is the beginning of the main function. The parentheses following the name of the function distinguishes it from other program elements and variables. The keyword void preceding the name of the function signifies that this function does not return a value and the empty parentheses denote that this function does not carry any arguments. Return values and function arguments will be discussed in detail in the following chapters.
Braces surround the body of the function. These braces are used to delimit a block of statements. In our example, the function body contains only one program statement with cout, but a function body can consist of many such statements.
PROGRAM STATEMENTS
Program statements are the fundamental building blocks of any programming language. They are instructions given to the computer. In our example, the following line is a program statement:
cout<< "Well begun is half done";
Every program statement in C++ ends with a semicolon. If the semicolon is not placed the compiler shows an error.
PREPROCESSOR DIRECTIVES
The first line of the program
#include <iostream.h>
is called a preprocessor directive. Program statements, as we know, are instructions given to the computer. A preprocessor directive is an instruction given to the compiler. The preprocessor directive #include tells the compiler to insert another file into the source file. In our example, the preprocessor directive #include tells the compiler to add the source file iostream.h to the program file before compilation. Why is this done? iostream.h is an example of a header file (also known as include file). This file contains declarations that are needed by the cout identifier and << operator. Without these declarations the compiler will not recognize these two keywords and gives an error during compilation.
There are a number of other header files, for example stdio.h, dos.h, string.h, conio.h etc.
VARIABLES AND CONSTANTS
Variables are the most fundamental part of any programming language. As the name suggests, variables can vary in value. They can have different values at different times (execution). The term variable is a symbolic name given to any value. It can also be defined as a symbol that represents the storage location in a computer's memory. The information that is stored in that location is called its value. Most programming languages have general variable types, such as integers, characters and floating points.
As opposed to variables, constants contain values, which remain the same throughout the existence of a program. The number 20 is an integer constant. The statement "Well begun is half done" in our program is a string constant. Similarly, 'a' is a character constant. A character constant is specified with single quotation marks around it, whereas a string uses double quotation marks.
VARIABLE NAMES
Since a variable is a symbolic name given to a value, there are some naming conventions that must be followed. Both uppercase and lowercase letters and digits from 1 to 9 can be used. The underscore '_' sign can also be used. The first character of the name must be a letter or underscore. The compiler distinguishes between uppercase and lowercase letters. Keywords cannot be used as variable names.
DATA TYPES
As mentioned earlier, every language has its own set of data types. We will now learn about the data types of C++ in detail.
Integer data types
Integer variables exist in several sizes, and the most commonly used integer data type is int. This data type requires two bytes of memory. This type of variable cannot store numbers with decimals. The following example illustrates the usage of integer variables.
#include<iostream.h>
void main()
int a;
int b;
a=20;
b=a+40;
cout<<a;
cout<<b;
The statement int a; declares a variable 'a' and specifies that its data type is integer. It is a good practice to define a variable in the first line of the program. However, it may also be defined anywhere in the program before being used.
Declaration and initialization
A declaration introduces a variable name and its data type in the program. However, it does not reserve memory for the variable. For instance
int a;
On the other hand, initialization of variables involves allocating a value to the variable alongwith defining it. For example
int a=10;
Character data type
Character variables occupy 1 byte of memory. The keyword used to represent character variables is char. This type stores integers in the range of -128 to 127.
#include<iostream.h>
void main()
char alpha='A';
cout <<alpha;
Float data type
Floating point variables are used to represent numbers with a decimal place like 3.1498 or -89.79 etc. These variables are used to represent real numbers. The following example calculates the area of a circle and displays it. The keyword float represents these variables
#include<iostream.h>
void main()
int rad=4;
float p=3.14
cout<<"The area of the circle is :";
cout<<p*rad;
Long data type
The long data type is similar to the int data type, except that it occupies 4 bytes of memory. That is why it can hold a larger range of values, from -2,147,483,648 to 2,147,483,647. The long type can also be defined as long int.
long int a;
long a;
Both the above declarations are valid.
There are three integer data types namely char, int and long and three floating point data types namely float, double and long double. The double occupies 8 bytes of memory and is capable of storing a floating point number with a much higher range and precision. The range and precision is mentioned in table 1.1 showing a summary of data types. The data type long double occupies 10 bytes of memory and its range of values and precision is slightly more than that of double.
Unsigned data types
The data types that have been discussed till now have all been signed. That is, they can store both negative and positive values. Sometimes, we need to store values, which are always positive such as the age of a person or the salary of an employee. In such cases, C++ allows us to define variables, which are only positive, and as a result can store a much bigger value in the same amount of memory. Only integer data types can be unsigned. All variables are by default signed. They are made so by adding the keyword unsigned before the variable.
unsigned char a;
unsigned int b;
unsigned float c;
Data Type
Bytes Occupied
Lowest Value
Highest Value
Precision Digits
-128
-32,768
32,767
-2,147,483,648
2,147,483,647
float
3.4x10-38
3.4x1038
double
1.7x10-308
1.7x10308
long double
3.4x10-4932
1.1x104932
unsigned char
unsigned int
65,535
unsigned long
4,294,967,295
INPUT AND OUTPUT
In the program statement cout<<"Well begun is half done"; the keyword cout (pronounced as "cout") is used to output data on the screen. This is done with the help of the insertion operator ('<<').
cout<<" Today is a fine day";
int a=10, int b=90,c;
cout<<a+b;
c=a+b;
cout<<"The value of c is :"<<c;
are some of the ways in which cout can be used to output data.
Inputting values into variables in C++ can be accomplished with the help of the cin keyword. For example
int a;
cout<<"Enter a value ";
cin>>a;
The '>>' operator is known as the insertion operator. The above statement causes the values entered by the user to be stored in the variable a.
MANIPULATORS
Manipulators are operators used with the insertion operator << to modify or manipulate the way data is displayed. Two of the most common manipulators are the setw and endl manipulators. In order to use these manipulators the header file iomanip.h should be included in the program file.
THE ENDL MANIPULATOR
This manipulator causes a line to be inserted. It has the same effect as sending the single '\n' character, which we will discuss later in escape sequences.
Consider the following program
#include<iostream.h>
void main()
long pop1=2343454, pop2=2334;
cout << "Location " << "Population"<<endl;
cout<< "New Delhi "<<pop1<<endl;
cout<< "Bombay " << pop2<<endl;
The output of the program is
Location Population
New Delhi 2343454
Bombay 2334
It can be visualized that each value displayed by cout occupies a field, an imaginary box with a certain width. The default field is just wide enough to hold the value. That is, the integer 567 will occupy a field three characters wide. Hence, the output of our program above.
THE SETW MANIPULATOR
With the output shown above it is difficult to compare integer values. If the same program is written with the help of the setw manipulator, the output would be something else.
The setw manipulator causes the number or string that follows to be printed within a field n characters wide, where n is the argument to setw(n). The value is right justified within the field.
The new output would be something like this
Location Population
New Delhi 2343454
Bombay 2334
ESCAPE SEQUENCES
Apart from using manipulators there is another way to obtain formatted output. This can be done with the help of escape sequences. The following example shows the usage of a very common escape sequence '\n'.
cout<< "What is your name?"<< "\n";
cout<< "My name is Vandana";
Escape sequences can be used both with character and string constants.
The escape sequence \n causes the string "My name is Vandana" to be printed in the next line.
The following table shows a list of some escape sequences.
Escape Sequence
Character
Bell (beep)
BackspaceB
New line
Return
Single quotation
Double quotation
Form feed
Backslash
Table 1.2: Some escape sequences
ARITHMETIC OPERATORS
C++ uses the four normal arithmetic operators +,-,* and / for addition, subtraction, multiplication and division. These operators work on all the data types, both integer and floating-pointing. Besides these, there are other operators which are used in C++.
THE REMAINDER OPERATOR
The remainder operator, represented by % sign, works only on integer data types. Also referred to as the modulus operator, it finds the remainder when one number divides another. For example (9%2) returns the value 1.
THE ASSIGNMENT OPERATOR
This operator, represented by '=' sign, is used to assign a value to a variable. For example, num=5 means the value 5 has been assigned to the variable num. This operator can be used in combination with other arithmetic operators to condense a statement. For example
sum=sum+num;
has the same effect as
sum+=num;
INCREMENT AND DECREMENT OPERATORS
Suppose the value of a variable has to be increased by 1. It could be achieved by simply writing
var=var+1;
A specialized operator called the increment operator (represented by '++') could do the same.
count=count+1;
count+=1;
count++;
All of them have the same effect. The opposite of the increment operator is the decrement operator '-' which decreases the value of a variable.
PREFIX AND POSTFIX
The increment and decrement operators can be used in two ways: prefix and postfix. Prefix means that the ++ operator precedes the variable and postfix means that the operator follows the variable. This will be clearer with the help of an example.
total=avg*++count;
The question to be asked here is this : Is the multiplication performed before or after the count is incremented? In this case, count is incremented first and multiplication performed later because a prefix notation is used. Had we done something like this: total=avg*count++, the opposite would have been achieved.
RELATIONAL OPERATORS
A relational operator is an operator which compares two values. The comparison involves such relationships as equal to, less than, greater than and so on. The result of the comparison may be true or false. This will be clearer with the help of an example.
#include<iostream.h>
void main()
int num;
cout<< "Enter a number :"<<endl;
cin>>num;
cout<<num<10<<endl;
cout<<num>10<<endl;
cout<<num= =10<<endl;
the output of the program will be something like this
Enter a number : 20
The value of a true expression is 1 and the value of a false expression is assumed to be 0. Therefore the output is like the one above.
There are six relational operators. Here is a list of them.
Operator
Meaning
Is greater than
Is less than
Is equal to
Not equal to
Greater than or equal to
Less than or equal to
LOOPS
When we want a part of a program to be repeatedly executed a certain number of times, loops are used. The repetition continues until a certain condition is true. When the condition becomes false, the control is transferred to the program statement following the loop.
THE FOR LOOP
The for loop is the most popular form of loops. Like all loops the for loop also executes a section of a code a prescribed number of times. It is usually used when the programmer knows beforehand how many times the section of the code has to be executed. The following example demonstrates the usage of the for loop:
#include<iostream.h>
void main()
int i;
for(i=0;i<10;i++)
cout<<I<<endl;
The output of the program is as follows
The for statement controls the loop. The keyword for is followed by parentheses, which contain the expressions separated by semicolons.
for (i=0;i<10;i++)
The three expressions are initialization, test condition and increment/decrement expressions.
The initialization expression is executed only once, when the loop is executed for the first time. It gives a loop variable an initial value. A loop variable is a variable, which is involved in the expressions of the loop. There may also be more than one loop variable.
The test condition expression usually involves a relational operator. It is evaluated each time through the loop. It determines whether the loop will be executed again or not. If the condition is true, the loop is executed again. If the condition is false the control comes out of the loop and the program statement following the loop is executed.
The increment expression changes the value of the loop variable, often by incrementing it. It is always executed at the end of the loop, after the loop body has been executed. In our example, the loop is executed 10 times. The first time the value of i is 0. This keeps incrementing till it attains the value of 9. Once the condition i<10 becomes false, the control comes out of the loop. The body of the loop is the code, which has to be executed repeatedly until the condition is true. In our example, the body of the loop contains only one program statement, but in other cases it may contain more than one. There is no semicolon after the for statement because the for statement and loop body together are considered to be one program statement.
Note : A for statement with single line body does not need braces.
The increment expression does not always need to increment. It can also perform other operations. In the next example, the loop variable is decremented.
#include<iostream.h>
void main()
unsigned int num;
unsigned long fact=1;
cout<< "Enter a number :";
cin>>num;
for (int i=num;i>0;i--)
fact*=i;
cout<< "The factorial is :"<<fact;
We can put more than one expression in the initialization, test condition and increment expressions parts of the for statement. Commas separate the different expressions.
For example:
for(k=0,j=1; k<=10,j>20;
THE WHILE LOOP
The for loop executes a code for a fixed number of times. What would happen if we do not specify how many times we want to execute the code. In such cases we make use of the while loop. In the following example we ask the user to enter a number. If the number is a 0, the loop terminates, otherwise the user is prompted to enter another number. There is no way the program would know how many times the user is prompted. This is totally controlled by the user.
#include<iostream.h>
void main()
int i=99;
while (i!=0)
cin>>i;
The syntax of the while loop is simpler than the for loop. It contains a test condition expression but no initialization and increment expressions. As long as the condition in the test condition expression is true the loop is executed. The absence of the increment expression may create some problems. For instance, the loop may work endlessly. In order to control this, a loop variable may be included which controls the number of times the block of code is executed. Like the for loop, this loop may also have multiple program statements.
THE DO WHILE LOOP
In a while loop, the test expression is evaluated at the beginning of the loop. If the test expression is false when the loop is entered, the block of code will not be executed. In some situations we may want the loop to be executed at least once regardless of the condition. In such cases the do while loop is used.
#include<iostream.h>
void main()
int n, f=1;
cout<< "Enter a positive integer :";
cin>>n;
f*=n;
while(n>1)
cout<< "The factorial is :"<<f;
DECISIONS
Decisions, like loops, are important building blocks of programs. At some point or the other, a program needs to make a decision whether a particular block of code has to be executed or not. Decision-making is supplemented by statements like if
.else and switch statements. As a result of decision-making, the control of the program jumps to a different part of the program.
THE IF STATEMENT
The if statement is the simplest form of a decision-making statement and is also the most useful. Here is an example
#include<iostream.h>
void main()
int x;
cout<< "Enter a number :";
cin>>x;
if(x>100)
cout<< "The number is greater than 100";
The output of the above program is something like this
Enter a number : 900
The number is greater than 100.
The keyword if is followed by a test expression in parentheses. The syntax of the if statement is very much like that of the while statement. But the difference is that the statements following if are executed only once and that of while are repeated till the test expression is true. There can be multiple statements delimited by braces in the if statement. The code inside the braces is executed if the condition is true, otherwise the compiler just ignores the program statements.
THE IF
..ELSE STATEMENT
As mentioned above the if statement lets you do something if the condition is true. If the condition is not true nothing happens. Suppose you want to execute a one set of statements when the condition is true and a different set when it is not. Then the if
else statement comes into the picture here. Let us write a program which asks the user to input a number. The program checks whether the number is odd or even and prints a message accordingly.
#include<iostream.h>
void main()
int x;
cout<< "Enter a number";
cin>>x;
if(num%2= =0)
cout<< "Number is even";
cout<< "Number is odd";
NESTED IF
.ELSE STATEMENTS
Some programs may not be as simple as our previous example. We may have multiple conditions and each condition should be tested if the previous condition does not hold true. When several conditions are woven one inside the other, the if statements are known as nested ifs.
include<iostream.h>
void main()
char dir 'a';
int x=20, y=10;
cout<< "Type enter to quit"<<endl;
while(dir!= '\r');
cout<< "Your location is"<<x<< ","<<y;
cout<< "Press direction key (n,s,e,w)";
dir=getchar();
if (dir=='n')
if(dir=='s')
if(dir=='e')
if(dir=='w')
This program keeps track of where you are and reports your position on the screen, which starts at coordinates 10,10.
Here is a sample interaction with the program.
Your location is 10, 10
Press direction key (n,s,e,w):n
Your location is 10, 9
Press direction key (n,s,e,w):e
Your location is 10, 9
Press direction key (n,s,e,w):e
You can press the Enter key to exit from the program.
This program uses an if statement nested inside an if.....else statement, which is nested inside another if.....else statement, which is nested inside another if......else statement. If the first test condition is false, the second one is examined, and so on until all four have been checked. If any one proves true, the program exits from all the nested decisions.
THE ELSE
IF STATEMENT
Sometimes nested if
.else statements can be very confusing and difficult to interpret. Especially when there are a number of ifs nested one inside the other. This difficulty can be overcome by writing the same statements using another approach. Such statements are called else
if statements.
#include<iostream.h>
void main()
char dir= 'a';
int x=10, y=10;
cout<<"Type enter to quit ";
while(dir !='\r');
cout<<"your location is "<<x<<","<<yy;
cout<<"Press direction key (n,s,e,w):";
dir=getche();
if(dir== 'n')
else if(dir == 's')
else if(dir == 'e')
else if(dir == 'w')
THE SWITCH STATEMENT
When there are a number of conditions and all decisions depend on the value of the same variable, the else
if statements may also appear very clumsy and difficult to use. C++ offers another statement which could be used as a substitute for the else..if statement under such conditions.
#include<iostream.h>
void main()
int score;
cout<<"Enter test score :";
cin>>score;
switch(score)
case 10 :
cout<< "A"<<endl;
break;
case 9 :
cout<<"B"<<endl;
break;
case 8 :
cout<<"C"<<endl;
break;
default :
cout<<"Error : score is out of range";
This program prompts the user to enter a score. After evaluating the score it prints either 'A' or 'B' or 'C' etc. The switch keyword is followed by a switch variable in parentheses. Braces then delimit a number of case statements. Each case keyword is then followed by a constant and then a colon. The data type of the case constants should match that of the switch variable. Also, before entering the switch, the program should assign a value to the switch variable. The switch statement evaluates the variable (inside the parentheses following the switch statement) and then looks for its value among the case constants listed. The statements in that statement list are then executed.
THE BREAK STATEMENT
In the previous program, the break keyword causes the entire switch statement to exit. The control is transferred to the first statement following the end of switch construction. Do not forget to give the break statement, without which the control passes down to the next case statement, which is not desirable.
THE DEFAULT KEYWORD
We also noticed the default keyword in the previous program. This keyword gives the switch construction which is a way to take an action if the value of the loop variable does not match any of the case constants. No break is necessary after the default keyword, since we are at the end of the switch.
THE CONTINUE STATEMENT
The break statement takes you out of the bottom of a loop. Suppose the user wants to go to the beginning of the loop from the middle of the code and thus skip the remaining code. In such a situation using the continue statement gives the desired result.
#include<iostream.h>
void main()
long dividend, divisor;
char ch;
cout<< "Enter dividend :";cin>>dividend;
cout<< "Enter divisor :"cin>>divisor;
if(divisor==0)
cout<<"Illegal divisor \n";
continue;
cout<< "Quotient is"<<dividend/divisor;
cout<< ",remainder is "<<dividend%divisor;
cout<< "\n Do another? (y/n):";
cin>>ch;
while (ch!= 'n');
If the user inputs 0 for the divisor, the program prints an error message and, using continue, returns to the top of the loop to issue the prompt again.
Here is a sample output :
Enter dividend :10
Enter divisor :0
Illegal divisor
Enter dividend
A break statement in this situation would cause an exit from the do loop and the program.
@The basic idea behind object-oriented language is to combine both data and functions that operate on that data into a single unit. Such a unit is called an object. The design of an object is called a class. Thus objects are user-defined data types containing both data and functions that manipulate that data. The data in the classes are called data members and the functions are referred to as member functions or methods. Hence, in object-oriented methodology, an object is an abstract entity that tries to model the real world.
CHARACTERISTICS OF AN OBJECT-ORIENTED LANGUAGE
OBJECTS
When you approach a programming problem in an object-oriented language, you no longer ask how the problem will be divided into functions, but how it will be divided into objects. Thinking in terms of objects rather than of functions has a surprisingly helpful effect on how easily programs can be designed. This results from the close relation between objects in programming and those in the real world.
The range and extent of object-oriented programs is limited only by your imagination. Objects could be physical objects like automobiles in a traffic-flow simulation, countries in an economics model or aircrafts in an air-traffic-control system.
They could be elements of the computerized environment such as windows, menus, the mouse, keyboard, etc. Objects can also be collections of data such as an inventory, a personnel file or a dictionary.
CLASSES
In OOP we say that objects are members of classes. What does it mean? Almost all computer languages have built-in data types. For instance, a data type int, meaning integer, is predefined in C++. You can declare as many variables of type int as you need in your program:
int day;
int count;
Similarly, you can define many objects of the same class. A class is thus a collection of similar objects.
DATA ENCAPSULATION
Data encapsulation is the ability of an object to hide its data from other parts of the program. Only the member functions, which belong to the same class, can access all data members.
INHERITANCE
The process of creating new classes from existing classes is known as inheritance. The new class inherits all the properties and capabilities of the existing class and adds embellishments and refinements of its own. In our daily lives we use the concept of classes being divided into subclasses. We know that the class of animals is divided into mammals, amphibians, insects, and so on. The class of vehicles is divided into cars, trucks, buses and motorcycles. The principle in this sort of division is that each subclass shares common characteristics with the class from which it is derived. Cars, trucks, buses and motorcycles all have wheels and a motor
these are the defining characteristics of vehicles. In addition to the characteristics shared with other members of the class, each subclass also has its own unique characteristics. For example, buses have seats for many people, while trucks have space for hauling heavy loads.
In a similar way, an OOP class can be divided into subclasses. In C++ ,the original class is called the base class. Other classes can be defined that share its characteristics and possess their own as well. These are called derived classes. This concept will be explored in detail in later sessions.
REUSABILITY
Once a class has been written, created and debugged, it can be distributed to other programmers for use in their own programs. This is called reusability. It is similar to the way a library function in a procedural language can be incorporated into different programs.
In OOP, the concept of inheritance provides an important extension to the idea of reusability. A programmer can take an existing class, and without modifying it, add additional features and capabilities to it. Deriving a new class from an existing class, which we have already discussed, does this.
POLYMORPHISM
Using operators or functions in different ways, depending on what they are operating on, is called polymorphism (the ability of an object to exist in more than one form). Polymorphism provides a common interface but different functionality for several different classes so that the objects of those classes can be manipulated identically by the program. When an existing operator such as + or =, is given the capability to operate on a new data type, it is said to be overloaded. Overloading is a kind of polymorphism and a very important feature of OOP.
CLASS SPECIFICATION
Now that we have learnt about classes and objects conceptually, lets us take an example that demonstrates the syntax and general features of classes in C++.
#include<iostream.h>
class smallobj
private:
int somedata;
public:
void setdata(int d)
somedata=d;
void showdata()
cout<< "\n Data is" <<somedata;
void main()
smallobj s1,s2;
s1.setdata(1066);
s2.setdata(1766);
s1.showdata();
s2.showdata();
The class smallobj specified in this program contains one data item and two member functions. These functions provide the only access to the data item from outside the class. The first member function sets the data item to a value, and the second displays the value. As mentioned earlier, combining data and functions together into a single unit is the whole idea of object-oriented programming.
An object is said to be an instance of a class. In the class smallobj we define two objects s1 and s2. Both are given values, which are displayed.
Let us examine the class specifier in more detail. The specifier starts with the keyword class, followed by the class name smallobj. The body of the class is delimited in braces and terminated by a semicolon.
PRIVATE AND PUBLIC ACCESS SPECIFIERS
The body of the class contains two more keywords: private and public. As already discussed, one of the important features of OOPs is data hiding. The primary mechanism to hide data is to put it in a class and make it private. Private data can be accessed only by member functions of the class in which it is declared. Class members are private by default. Public data and functions on the other hand are accessible from outside the class.
Usually the data within a class is private and the functions are public.
The smallobj class contains one data item, somedata, which is of type int and is private.
Member functions are functions that are included within a class. These functions are also referred to as methods. There are two member functions in smallobj: setdata() and showdata().
INLINE FUNCTIONS
The member functions in smallobj are definitions. The actual code for the functions is contained within the class specification. Such functions are called inline functions. It is also possible to declare a function inside a class body and define it outside the class body.
The invocation of a function normally causes program control to move to the function, and suspends the execution of the currently active function. After the function has been completed the suspended function resumes execution at the point immediately following the calls. Function invocation is administered on the program's runtime stack.
C++ provides the inline function mechanism in which the explicit function calls can be avoided to reduce runtime overheads. An inline function is a function whose code gets expanded at the point of its call during compilation. Such functions can be a member of a class or a global function.
To make a function inline
Precede the return type of the function with the keyword inline
Define the function before any calls to that function
A function defined within the declaration of the class is inline by default. The inline specification is only a request to the computer. The compiler may or may not honor that request. It depends on the type of the code the function contains. The inline functions are best reserved for small, frequently used functions. Excessive use of inline functions may lead to large executables.
Note: Prototype declaration is compulsory in C++, when the function is defined outside the class body.
DEFINING OBJECTS
The first statement in main(),
smallobj s1, s2;
defines two objects s1 and s2. But this definition does not create any objects. No memory space is set aside for the objects yet.
CALLING MEMBER FUNCTIONS
The next two statements in main() call the member function setdata()
s1.setdata(1066);
s2.setdata(1766);
A member function is always called to act on a specific object, not on the class in general. Only an object of that class can access member functions of a class. To use a member function, the dot operator (.) connects the object name and member function. The first call to setdata().
s1.setdata(1066);
executes member function of the s1 object. This function sets the variable somedata in object s1 to the value 1066. Similarly, the rest of the calls set values and display them.
OBJECTS AS DATA TYPES
Objects can be used as variables of a user-defined data type. Here is an example.
class Distance
private:
int feet;
int inches;
public:
void setdist(int ft, float in)
feet=ft;
inches=in;
void getdist()
cout<< "\n Enter feet:";
cin>>feet;
cout<< "Enter inches:";
cin>>inches;
void showdist()
cout<<feet<< "\" <<inches<< "\";
void main()
Distance dist1, dist2;
dist1.setdist(11, 6.25);
dist2.getdist();
cout<< "\n dist1 =";
dist1.showdist();
dist2.showdist();
MEMBER FUNCTIONS DEFINED OUTSIDE THE CLASS
So far we have seen member functions defined inside the class specifier. This need not always be the case. When a function is defined outside the class, the syntax changes. The function name is preceded by the class name, Distance, and a new symbol the double colon (::) This symbol, called the scope resolution operator, specifies what class it is associated with.
CONSTRUCTORS
Whenever an object is created, memory is allocated to it. But allocation of memory does not ensure initialization of data inside the object. The data within a class cannot be initialized when it is declared. This happens because the declaration of a class serves only as a template. For instance, the following declaration is invalid.
class point
int x_cord=0;
int y_cord=0;
Writing an initialization function that will assign initial values to each data member can solve this problem. This function can be called upon as soon as the object is created. The only drawback of this approach is that if the programmer forgets to invoke the initialization function, the program will operate on an object that has attributes, which have meaningless values.
There is a solution to this problem. While defining a class, a member function should be included in the class, which gets executed whenever an object of that class is created. Such a member function is called a constructor. The constructor has the same name as that of the class. For example,
class point
private:
int x_cord;
int y_cord;
public:
point()
x_cord=0;
y_cord=0;
In class student the constructor student() initializes the attributes x_cord and y_cord to 0. Whenever an object of the class student is created the constructor is called automatically and the attributes x_cord and y_cord are initialized to 0. It may be noted that the constructor does not return any value. Moreover, since it needs it needs to be invoked automatically from outside the class definition, it belongs to the public definition of the class. Normally, all C++ functions need to specify a return type and explicitly return a value of that data type. But this is not so in case of constructors. Since constructors are automatically invoked when objects are created, there is no program or function to which they will return a value.
OVERLOADING CONSTRUCTORS
A constructor can take parameters. A constructor that does not take parameters (as in the previous example) is called the default constructor of a class. The default constructor is invoked automatically when the object is defined as follows
point p1;
Apart from the default constructor that does not take any parameter, the programmers often include other constructors that take different types of parameters.
Suppose a person wants to create a point object and initialize its attributes to any specified values. In such a case, the default constructor would not be of any use. The class definition will have to include another constructor that takes the coordinates as parameters from the user. The class definition can now be modified as follows
class point
private:
int x_cord;
int y_cord;
public:
point()
x_cord=0;
y_cord=0;
point(int ro, int col)
x_cord=ro;
y_cord=col;
Which constructor is invoked when a point object is created depends upon how the object is created. If the definition is as follows,
point p1;
then the default constructor is called. If the definition includes a list of parameters then the other constructor is called. For instance,
point p2(12,35); // x_cord is initialized to 12 and y_cord is initialized to 35
Now in the class point we have two member functions with the same name. Since both are constructors, they have to share the same name. Such constructors are known as overloaded constructors.
If an object is created without any arguments, and no default constructor has been defined in the class, then the compiler is forced to create a default constructor of its own before creating the object. The default constructor in this case, initializes the data to some predefined value.
Sometimes class constructors are not invoked to initialize a newly defined class object. This happens when one object is used to initialize another object.
point p1(15,10); //Constructor invoked for p1
point p2=p1; //Data of p1 is copied to p1
// No constructor is invoked for p2
DESTRUCTORS
Constructors serve to automatically initialize an object at the moment of creation. Destructors are functions that are complementary to constructors. They serve to de-initialize objects when they are destroyed.
A destructor is invoked either when an object of the class goes out of scope, or when the memory occupied by it is deallocated using the delete operator. A destructor like a constructor has the same name as that of a class. In order to distinguish it from the constructor it is prefixed with '~' (tilde) sign.
A destructor deinitializes the storage space of an object prior to normal deallocation that is done when an object goes out of scope. It is not possible to overload a destructor. A single class can have only one destructor. A destructor cannot take arguments, specify a return value or explicitly return a value.
POINTER TO AN OBJECT
An object, like the variables of any other type, can be referred to with the use of pointers. The pointer that is used to point to the object must be of the same type. A pointer to an object can be used to access the members of that object, using the '->' operator.
int main ()
point p1, *p_ptr1; //An object p1 and a pointer p_ptr1
p_ptr1=&p1; //Assign the address of p1 to p_ptr1
p_ptr1->set_point(10,12) //where set_point is a function of class point
OBJECTS AS FUNCTION ARGUMENTS
Like all data types objects can also be passed as arguments. The following example is an illustration of this.
By now we are aware that object-orientation is based on the concept of reflecting the real world. Inheritance exists in the real world in various forms. For example, a whale inherits the properties of a mammal. A two-in-one music system inherits the properties of a radio and a tape-recorder. Many more such examples of inheritance can be found. As inheritance is a common happening in the real world, so also is it an important feature of object-oriented programming.
Inheritance refers to the properties of a superclass being available to a subclass. Inheritance, in an object-oriented language like C++, makes the data and the methods of a superclass physically available to its subclass.
Inheritance has many advantages, the most important being the reusability of the code. Once a class has been defined and debugged, it can be used to create new subclasses. Reusing existing classes saves on time and effort. The class (superclass) from which the subclass is derived is referred to as the base class. The subclass that inherits the properties of the base class is referred to as the derived class. Each object of the derived class includes all the members of the base class. The derived class inherits all the properties of the base class. Owing to this the derived class has more properties than its base class. However, a derived class may override some of the properties of the base class.
Any class can be base class.
More than one class can be derived from a single base class.
One derived class can be the base class to another.
SPECIFYING THE DERIVED CLASS
The syntax for deriving a class from a base class is
class Derived:public Base
The single colon (':') in the derived declaration is used to specify the base class. The colon is followed by the keyword public. Public has certain significance, which will be discussed later.
DERIVING A NEW CLASS
Let us consider an example. We create a class counter. This class has a data member count, whose value we want to increment with the help of a function called increment().
Suppose we also wanted a function which could decrement this count. One way to do this would be to insert a decrement routine directly into the source code of the class counter. However, for several reasons we may not want to do this. Firstly, because the class counter has worked well and has undergone testing and debugging. Secondly, we might not want to access its source code, especially if it had been distributed as part of a class library.
To avoid these problems we can use inheritance to create a new class based on counter, without modifying its source code. Consider the code below:
class counter
protected:
unsigned int cnt;
public:
counter() {cnt=0;}
counter ( int c) {cnt=c;}
int get_count() {return cnt;}
counter increment()
cnt++;
return counter (cnt);
class countdn:public counter
public:
counter decrement()
cnt --;
return counter (cnt);
void main()
countdn c1;
cout<< " \n c1=" <<c1.get_count();
c1.increment();
c1.increment();
c1.increment();
cout<< "\n c1="<<c1.get_count();
c1.decrement();
c1.decrement();
cout<< "\n c1="<<c1.get_count();
The new class countdn inherits all the features of the counter class. It does not require a constructor or the get_count() functions.
ACCESS SPECIFIERS
Using keywords like public and private can control the access to class members.
The keyword public specifies that objects of the derived class are able to access public member functions of the base class.
The alternative to is the keyword private. When this keyword is used, objects of the derived class cannot access public member functions of the base class.
A protected member on the other hand can be accessed by member functions in its own class or in any subclass derived from its own class.
Since objects can never access private or protected members of a class, the result is that no member of the base class is accessible to objects of the derived class.
Access specifier
Accessible from own class
Accessible from derived class
Accessible from objects outside class
Public
Protected
Private
Various types of accessibility of access specifiers
ACCESS COMBINATIONS
There are a number of possibilities for access and they will become clear in the following example:
#include<iostream.h>
class A // base class
private:
int privdataA;
protected:
int prodataA;
public:
int pubdataA;
class B:public A // publicly derived class
public:
void funct()
int a;
a=privdataA; // error:not accessible
a=prodataA; // ok
a=pubdataA; // ok
class C:private A // privately derived class
int a;
a=privdataA; // error:not accessible
a=prodataA; // ok
a=pubdataA; // ok
void main()
int a;
b objB;
a=objB.privdataA; // error:not accessible
a=objB.protdataA; // error:not accessible
a=objB.pubdataA; // ok
c objC;
a=objC.privdataA; // error:not accessible
a=objC.protdataA; // error:not accessible
a=objC.pubdataA; // error:not accessible
The program specifies a base class, A, with private, protected and public data items. Two classes, B and C, are derived from A. B is publicly derived and C is privately derived.
Functions in the derived class can access protected and public data in the base class. Also objects of the derived classes cannot access private or protected members of the base class.
Objects of publicly derived classes can access public members of the base class A, while objects of the privately derived class C cannot.
LEVELS OF INHERITANCE
Classes can be derived from classes that are themselves derived. Here is an example of this. Consider a base class called employee. Three classes called manager, scientist and laborer are derived from this base class. Further, a special class called foreman is derived from the class laborer as shown in the following figure.
class A
class B: public A
class C: public B
Here B is derived from A, and C is derived from B. The process can be extended to an arbitrary number of levels
D could be derived from C, and so on.
The following example illustrates how the above diagram can be actually implemented in code:
#include<iostream.h>
class employee
private:
char name[80];
unsigned long number;
public:
void getdata()
cout<< "\n Enter last name:";
cin>>name;
cout<< "Enter number:";
cin>>number;
void putdata()
cout<< "\n Name:"<<name;
cout<< "\n Number:"<<number;
class manager:public employee
private:
char title[80];
double dues;
public:
void getdata()
employee::getdata();
cout<< "Enter title:";
cin>>title;
cout<< "Enter golf club dues:";
cin>>dues;
void putdata()
employee::putdata();
cout<< "\n Title:"<<title;
cout<< "\n Golf club dues:"<<dues;
class scientist:public employee
private:
int pubs;
public:
void getdata()
employee::getdata();
cout<< "Enter number of pubs:";
cin>>pubs;
void putdata()
employee::pubdata();
cout<< "\n number of publications:";
pubs;
class laborer:public employee
class foreman:public laborer
private:
float quotas;
public:
void getdata()
laborer::getdata();
cout<< "Enter quotas:";
cin>>quota;
void putdata()
laborer::putdata();
cout<< "\n Quotas:"<<quotas;
void main()
laborer l1;
foreman f1;
cout<<endl;
cout<< "Enter data for laborer";
l1.getdata();
cout<< "Enter data for foreman";
f1.getdata();
cout<<endl;
cout<< "Data on laborer ";
l1.putdata();
cout<< "Data on foreman";
f1.putdata();
MULTIPLE INHERITANCE
A class can be derived from more than one base class. This is called multiple inheritance.
Figure 3.4: The multiple level inheritance
The syntax for multiple inheritance is similar to that for single inheritance.
class A
class B
class c:public A,public B
The base classes from which C is derived are listed following the colon in C's specification; they are separated by commas.
The relationships between various classes can be depicted as follows:
class student
class employee
class manager:private employee, private student
class scientist:private employee, private student
class laborer:public employee
//Sample code to illustrate the usage of multiple level inheritance//
#include <iostream.h>
class student
private:
char school[80];
char degree[80];
public:
void getedu()
cout<< "Enter name of school or university:";
cin>>school;
cout<< "Enter highest degree earned \n:";
cin>>degree;
void putedu()
cout<< "\n School or University:"<<school;
cout<< "\n Highest Degree Earned"<<degree;
class employee
private:
char name[80];
unsigned long number;
public:
void getdata()
cout<< "\n Enter Last name:";
cin>>name;
cout<< "\Enter number:";
cin>>number;
void putdata()
cout<< "\n Name:"<<name;
cout<< "\n Number:"<<number;
class manager: private employee, private student
private:
char title[80];
double dues;
public:
void getdata()
employee:: getdata():
cout<< "Enter title:";
cin>>title;
cout<<"Enter golf club dues:"; cin>>dues;
student::getedu();
void putdata()
employee::putdata();
cout<< "\n Title"<<title;
cout<< "\n Golf club dues:"<<dues;
student::putedu();
class scientist:private employee, private student
private:
int pubs;
public:
void getdata()
employee::getdata();
cout<< "Enter number of pubs:";cin>>pubs;
student::getedu();
void putdata()
employee::putdata();
cout<< "\n Number of publications:"<<pubs;
student:: putedu();
class laborer:public employee
void main()
manager m1;
scientist s1,s2;
laborer l1;
cout<<endl;
cout<< "Enter data for Manager1";
m1.getdata();
cout<< "Enter data for Scientist1 ";
s1.getdata();
cout<< "Enter data for Scientist2";
s2.getdata();
cout<< "Enter data for Laborer1";
l1.getdata();
cout<< "\n Data on Manager1";
m1.putdata();
cout<< "\n Data on Scientist1";
s1.putdata();
cout<< "\n Data on Scientist2";
s2.putdata();
cout<< "\n Data on Laborer1";
l1.putdata();
POLYMORPHISM
Using operators or functions in different ways is called polymorphism. When an existing operator like = or + is given the ability to operate on a new data type, it is said to be overloaded. Overloading is also a kind of polymorphism.
OPERATOR OVERLOADING
Operator overloading refers to giving the normal C++ operators, such as +,*,<= additional meanings when they are applied to user-defined data types. Operator overloading is one of the most exciting features of object-oriented programming. It can transform complex, obscure, program listings into obvious ones. For example, statements like
d3.addobjects(d1,d2);
can be changed to the much more readable form
d3=d1+d2;
Normally
a = b + c;
works only with basic types like int and float, and attempting to apply it when a, b and c are objects of user-defined class will show errors in the compiler, However, by using overloading, you can make this statement legal even when a, b and c are user-defined types.
OVERLOADING UNARY OPERATORS
Let us start by overloading a unary operator. Unary operators are those which act on only one operand. An operand is a variable acted on by an operator. The increment (++) and decrement (--) operators are examples of unary operators.
In the following example we create a class called counter to keep track of a count.
class counter
private:
unsigned int count;
public:
counter() {count=0;}
int get_count() {return count;}
void operator ++() {count ++;}
void main()
counter c1, c2;
cout<< "\n c1="<<c1.get_count();
cout<< "\n c2="<<c2.get_count();
c1++;
c2++;
++c2;
cout<< "\n c1="<<c1.get_count();
cout<< "\n c2="<<c2.get_count();
In this program we create two objects of class counter:c1 and c2. The counts in the objects are displayed and are initially 0. Then using the overloaded ++ operator, we increment c1 once and c2 twice, and display the resulting values,
THE OPERATOR KEYWORD
How does the operator know when to act on a user-defined operand? The keyword operator is used to overload the ++ operator in this declaration:
void operator ++();
The return type (void in this case) comes first, followed by the keyword operator, followed by the operator which has to be overloaded and finally the arguments within parentheses.
The declarator syntax tells the compiler to call this member function whenever the ++ operator is encountered, provided the operand is of type counter.
Operator arguments
In main() the ++ operator is applied to a specific object, as in the expression c1++. Yet the operator takes no arguments. What does this operator increment? It increments the count data in the object of which it is a member function. Since member functions can access the particular object for which they have been called at any time, this operator requires no arguments.
Operator return values
The operator ++() function in the previous program does not return a value. So if we use a statement like this in main()
c1=c2++;
the compiler will show an error.
To make our operator ++ more flexible, we must provide a way for it to return a value.
class counter
private:
unsigned int count;
public:
counter() {count=0;}
int get_count() {return count;}
counter operator ++()
count++;
counter temp;
temp.count =count;
return temp;
void main()
Counter c1, c2;
cout<< "\n c1="<<c1.get_count();
cout<< "\n c2="<<c2.getcount();
c1++;
c2=c1++;
cout<< "\n c1="<<c1.get_count();
cout<<"\n c2="<<c2.getcount();
In this program, the operator ++() function creates a new object of type counter, called temp, to use as a return value. The operator ++() increments the count data in its own object as before and creates the new temp object. It then assigns count in the new object the same value as in its own object. Finally, it returns the temp object.
OVERLOADING BINARY OPERATORS
Binary operators can be overloaded just as easily as unary operators. We will look at examples that overload arithmetic operators, comparison operators and arithmetic assignment operators.
We have already studied examples of Distance objects. Distance objects can be added using a member function add_dist()
dist3.add_dist(dist1, dist2);
But by overloading the + operator we can reduce this complex expression to
dist3=dist1+dist2;
class Distance
private:
int feet;
int inches;
public:
Distance()
{feet=0;inches=0.0;}
Distance(int ft, float in)
{feet=ft;inches=in;}
void getdist()
cout<< "\n Enter feet:";
cin>>feet;
cout<<"Enter inches:";
cin>>inches;
void showdist()
cout<<feet<< "\" <<inches<< "\";
Distance operator +(Distance);
Distance Distance::operator +(Distance d2)
int f=feet+d2.feet;
float i=inches+d2.inches;
if (i>=12.0)
i-=12.0;
return distance (f,i);
void main()
Distance dist1, dist3, dist4;
dist1.getdist();
Distance dist2(11, 6.25);
dist3=dist1+dist2;
dist4=dist1+dist2+dist3;
cout<< "\n dist1=";dist1.showdis();
cout<< "\n dist2=";dist2.showdis();
cout<< "\n dist3=";dist3.showdis();
cout<< "\n dist4=";dist4.showdis();
In an expression like
dist3=dist1+dist2;
it is important to understand how the return value and arguments of the operator relate to the objects. What does this function use as its argument
dist1 or dist2. The argument on the left side of the operator (dist1:
) is the object of which the operator member is a function. The object on the right side of the operator (dist2) must be furnished as an argument to the operator. The operator returns a value, which is stored in dist3.
CONCATENATING STRINGS
In C++, the + operator is not normally used to concatenate strings as it can be in certain languages. That is, something like
str3=str1+str2;
is invalid.
Let us overload the + operator to do such an operation.
class String
private:
char str[80];
public:
String ()
strcpy( str, ""); }
String( char s[])
strcpy(str,s);
void display()
{cout<<str;}
String operator +(String ss)
if (strlen(str)+strlen(ss.str)<80)
string temp;
strcpy(temp.str, str);
strcat(temp.str, ss.str);
return temp;
cout<< "\n string overflow";
void main()
string s1= "\n Merry Christmas!";
string s2= "Happy New Year";
string s3;
s1.display();
s2.display();
s3.display();
s3=s1+s2;
s3.display();
VIRTUAL FUNCTIONS
Virtual means existing in effect but not in reality. A virtual function then, is one that does not really exist but nevertheless appears real to some parts of the program.
Why do we need virtual functions? Suppose you have a number of objects of different classes and you want to put them all on a list and perform operations on them using the same function call. Take for example, a graphics program that includes several different shapes: a triangle, a ball, a square and so on. Each of these classes has a member function draw() that causes the object to be drawn on the screen.
Now suppose you plan to make a picture by grouping a number of these elements together, and you want to draw the picture in a convenient way. One approach is to create an array that holds pointers to all the different objects in the picture. The array might be defined like this:
shape * ptrarr[100];
If you insert pointers to all the shapes in this array, you can then draw an entire picture using a simple loop:
for(int j=0;j<n;j++)
ptrarr[j]->draw();
This is an amazing capability
completely different functions are executed by the same function call. If the pointer in ptrarr points to a ball, the function that draws a ball is called; if it points to a triangle, the triangle-drawing function is called. This is an important example of polymorphism.
NORMAL FUNCTIONS ACCESSED WITH POINTERS
class Base
public:
void show()
cout<< "\n Base";
class Derv1:public Base
public:
void show()
cout<< "\n Derv1";
class Derv2:public Base
public:
void show()
cout<< "\n Derv2";
void main()
derv1 dv1;
derv2 dv2;
Base *ptr;
ptr=&dv1;
ptr->show();
ptr=&dv2;
ptr->show();
The Derv1 and Derv2 classes are derived from class Base. Each of these three classes has a member function show(). In main() we create objects of class Derv1 and Derv2, and a pointer to class Base. Then we put the address of a derived class object in the base class pointer by saying
ptr=&dv1;
We are assigning the address of an object of one class (Derv1), to the pointer of another class (Base). This is acceptable to the compiler. The rule is that pointers to objects of a derived class are type-compatible with pointers to objects of the base class.
ptr->show();
is executed which function is called? Is it Base::show() or Derv1::show()? The output of the program is
That means in both the occasions, the function in the base class is executed. Sometimes, though this is what we may want, it does not solve our problem
accessing objects of different classes using the same name.
VIRTUAL MEMBER FUNCTIONS ACCESSED WITH POINTERS
We can make a slight change to our program by placing the keyword virtual in front of the declarator of the show() function in the base class.
class Base
public:
virtual void show()
cout<< "\n Base";
class Derv1:public Base
public:
void show()
cout<< "\n Derv1";
class Derv2:public Base
public:
void show()
cout<< "\n Derv2";
void main()
derv1 dv1;
derv2 dv2;
Base *ptr;
ptr=&dv1;
ptr->show();
ptr=&dv2;
ptr->show();
Now the output of the program is
Derv1
Derv2
because the functions of the derived classes, and not the base class, are executed.
LATE BINDING
The question we must ask here is how does the compiler know which function to execute and when? In case of normal functions the compiler always executes the function of the base class. But in virtual functions the compiler does not know what class the contents of the ptr may contain. So it keeps the decision pending until the program is running. During the run time, when it is known what class is pointed to by ptr, the appropriate function is called. This is called late binding or dynamic binding. Choosing functions the normal way during compilation time is known as early binding or static binding.
PURE VIRTUAL FUNCTIONS
A pure virtual function is a virtual function with no body. You may have noticed in the example using a virtual function, that Base::show() is never executed. This is a common occurence. There is no need for the base-class version of the particular function as we only use the versions of the function in the derived classes. When this is true, the body of the virtual function in the base class can be removed, and the notation=0 added to the function declaration, like this
#include<iostream.h>
class Base
public:
virtual void show()=0;
class derv1:public Base
public:
void show()
cout<< "\n Derv1";
class derv2:public Base
public:
void show()
cout<< "\n Derv2";
void main()
Base *List[2];
Derv1 dv1;
Derv2 dv2;
List[0]=&dv1;
List[1]=&dv2;
List[0]->show();
List[1]->show();
The equal sign here has nothing to do with assignment; the value 0 is not assigned to anything. The =0 syntax is simply how we tell the compiler that a function will be pure, that is, have no body.
If we can remove the body of the virtual show() function in the base class, is it possible to remove the function altogether. That would be even better, but it does not work. Without a function a show() in the base class, statements like
list[0]-> show();
would not be valid, because the pointers in the list[] array must point to members of class Base.
As you can see, we have made another change, which of course is unrelated. The address of the member functions are stored in an array of pointers, and accessed using array elements. This works in the same way as using a single pointer.
FRIEND FUNCTIONS
The concepts of encapsulation and data hiding prevent functions which do not belong to the class from accessing an object's private data. However, these situations may seem very rigid at times.
Suppose you want a function to operate on objects of two different classes. This function uses the objects of these two classes as arguments, and operates on their private data. This may seem normal if the classes were inherited from the same class. If they are not then in such a situation friend functions come to our rescue. Here is an example.
class Beta;
class Alpha
private:
int data;
public:
alpha(){data=3;}
friend int frifunc(alpha, beta);
class Beta
private:
int data;
public:
beta() {data=7;}
friend int frifunc(alpha,beta);
int frifunc(alpha a, beta b)
return(a.data + b.data);
void main()
alpha aa;
beta bb;
cout<<frifunc(aa, bb);
In this program, the two classes are Alpha and Beta. The constructors of these two classes initialize their values.
We want the function frifunc() to have access to both these private data members, so we make it a friend function by using the friend keyword in both classes.
friend int frifunc(alpha, beta);
This declaration can be placed anywhere in the class; it does not matter if it goes with either the public or private section.
An object of each class is passed as an argument to the function frifunc() and it can access the private data member of both classes through these arguments.
Remember that a class can not be referred to until it has been declared. Class Beta is referred to in the declaration of the function frifunc() in class Alpha, so Beta must be declared before Alpha. Hence the declaration
class Beta;
at the beginning of the program.
THE FRIEND CLASS
The member functions of a class can be made friends at the same time when you make the entire class a friend. The following program shows how this happens:
class Alpha;
private:
int data1;
public:
alpha() {data1=99;}
friend class beta;
class Beta
public:
void func1(alpha a){cout<< "\n data1="<<a.data1;}
void func2(alpha a){cout<< "\n data1="<<a.data1;}
void func2(alpha a){cout<< "\n data1="<<a.data1;}
void main()
Alpha a;
Beta b;
b.func1(a);
b.func2(A);
b.func3(a);
In class Alpha the entire class Beta has been made a friend class. Now all the member functions of Beta can access the private data of Alpha.
@THE WINDOWS ENVIRONMENT
Windows is known as a Graphical User Interface (GUI) since it uses graphics to organize the user's workspace. Users can choose items and execute programs by pointing and clicking with a mouse. Also the programs which run from within Windows also have a GUI, for example, MS-Access. These programs can run only from within Windows only. This is because Windows provide these programs with a number of in-built functions and data, which are not available in other environments. The functions provided by Windows include those with the functionality to draw text in different sizes and styles, using font data, draw lines, geometric shapes and changing color. Windows programs make use of this in-built function and do not have to be coded to perform these tasks. The functions an application can call are provided by an interface known as Application Program Interface (API) i.e. an interface between an application and Windows environment.
Every Windows environment has its unique API. For example, the API that Windows 95 supports (also called the Win32 interface) is a 32-bit API. The Windows 95 API is virtually identical to that of Windows NT except for the difference that Windows 95 API lacks some of the security features and multiple processors that WinNT has. In contrast to WinNT and Win95, Win3.x is a 16-bit API, with functions that work only with information that is of 16-bits in size.
Hence in Windows programs the application program does not interact with the hardware directly but through the Windows environment and so API is the interface between the application program and Windows environment. This slightly decreases the execution speed of the application program.
PROGRAMMING IN DOS VS. PROGRAMMING IN WINDOWS
Programs in DOS works on Character User Interface and in Windows they use Graphical User Interface.
DOS programs are program-driven whereas programming in Windows is event-driven. This means that in Windows for example if you click on Access only then are events generated.
Many DOS programs write directly on the video memory and the printer port. The disadvantage of this technique is that there is always a need to supply driver software for every video board and every printer model. Windows introduced a layer of abstraction called the Graphics Device Interface (GDI). Windows provides the video and printer drivers and your program need not know the type of video board and printer attached to the system. Instead of addressing the hardware, programs call GDI functions that reference a data structure called the device context. The device context is an internal structure that Windows uses to maintain information about an output device. Windows maps the device context structure to a physical device and issues the instructions. GDI is almost as fast as direct video access and allows different applications to share the display.
Windows applications interact with the Windows environment and not directly with the hardware. Windows environment takes care of the hardware for the program as the environment has in-built support of the basic hardware, such as the video-
display, memory, mouse, keyboard and printer.
Code in Memory
Windows does not notify the computer what sequence of steps is to be followed. Computers are structured to wait until they receive messages from Windows before they continue with processing but MS-DOS programs call the operating system to get user input. Windows programs process user input via messages from the operating system.
Windows causes programs and data to move around in the memory to make room for other program segments and data. This movement allows many programs to co-exist in a fairly limited amount of memory.
In DOS programming, the operating system gives the whole control of the CPU to the application program when it is loaded and takes the control back only when the application complet
es its execution. So it follows that the CPU is dedicated to only one program. In Windows programming, after loading the application file for execution, the control of the processor remains in the hand of Windows. So when an event occurs the message is given to Windows application which is focussed at that time and the processor is given the control of the application only to process that message. So Windows is capable of handling more than one application at a time.
WINDOWS PROGRAMS
The programs you write in Windows make use of a large number of functions that are a part of the environment and all Windows programs share these working libraries. The collection of working functions is maintained in files stored in the SYSTEM32 directory on the hard disk. The SYSTEM32 directory is created when Windows is installed. The 3 primary files of SYSTEM32 are:
GDI32.DLL: is a Windows NT 32-bit Graphics Device Interface API library and is a core Windows NT component
USER32.DLL: is a Windows NT User Library to provide support for user interface routines.
KERNEL 32.DLL: provides WINNT 32-bit API support and is a core Windows component.
Each of these programs, in turn, calls driver files for specific functions. Windows only loads the modules it needs into the memory and takes them out of the memory when they are no longer needed. Windows also reduces memory consumption. All applications running at one time share the same basic library support for the hardware.
MULTITASKING FEATURES OF WINDOWS
Under Windows, every program becomes a RAM-resident pop-up. This means that several programs can be run and displayed at the same time. The user can move around the screen, change the size of the windows, switch between different programs and transfer data from one program to another. The ability to switch from one program application to another is called Multitasking.
Multitasking is of two types:
Non-Preemptive/Cooperative Multitasking: Non-Preemptive Multitasking relies upon each part of the program voluntarily giving up the CPU to another task. Here the advantage is that the application program remains in control but an unsociable task can take away the control of the CPU if it does not let the operating system take control. For example, Windows 3.x uses this form of multitasking (when the mouse button is pressed all other activities stop).
Cooperative Multitasking means that an application can assume control until it yields to other applications. For most applications, this type of multitasking is most commonly acceptable. But if you want to do several things simultaneously then preemptive multitasking offers a smoother operation as seen in the case of WINNT, OS2 and Win 95.
Preemptive Multitasking: In a preemptive multitasking system, the operating system can forcibly remove control from a task and then resume it later without the task even noticing that it was paused for a while. In simple terms, it means that one Windows application can get the control of the CPU without the knowledge of another Windows application.
MEMORY MANAGEMENT IN MFC (Microsoft Foundation Classes)
The most important feature of memory management is the extensive usage of Dynamic Linked Libraries (DLL). This means that specially constructed libraries can be linked and loaded at run time. Dynamic linking increases program modularity because you can combine and test DLLs separately. You can then, either statically or dynamically link the application framework classes into your applications.
Win32 API PROGRAMMING ENVIRONMENT
Microsoft Visual C++ is available in Standard, Professional or Enterprise Versions. The VC++ Standard edition incorporates all the features necessary to develop C or C++ applications for Windows. In fact, it uses the same integrated development tools like MFC classes, run-time libraries and Windows libraries as the VC++ Professional edition. It also provides the necessary API tools and reference documentation for the Window API development kit in online help files. Alongwith the basic features like text editor, project manager, build utility, browser or debugger and resource editor which are present in the Standard edition, the Professional version also consists of:
Microsoft C/C++ optimizing compiler
Microsoft profiler
The Enterprise edition consists of
ActiveX
Data Access
Enterprise Tools
Graphics
Tools
By using a totally integrated environment you should always approach programming an application the way another user would do so while using your program
the visual interface element approach. VC++ program consists of two steps:
Visual Design -designs user-interface objects such as dialog boxes, icons, toolbars and menus.
Coding -here one should concentrate on writing the functional code to handle messages.
EVENT-DRIVEN PROGRAMMING
A program in the Windows environment remains inactive/passive until Windows sends it a message. Windows uses event-driven architecture to incorporate event-driven programming.
EVENT HANDLING IN A TRADITIONAL PROGRAM
In traditional programming the application waits for the user to respond and only then does it take a particular action. It prompts the user again and waits for the user's input. As it waits for the user, the program is busy internally in a wait loop, querying the keyboard to see if the user has typed anything, using input/output functions. Even while waiting for input, the program executes CPU instructions. Usually the program uses most of the CPU time and memory and other resources. In such traditional systems the role of the operating system is simply to locate and load the correct application file and transfer control to it. The application can call functions in the operating system to access files and perform other tasks, but the control remains with the application. When an I/O event occurs, the information is directly transferred to the application. So the traditional MS-DOS program model is adequate for a single-tasking system. But in Windows, there can be many different programs in memory-sharing CPU time, each represented by a window on the screen.
EVENT HANDLING IN WINDOWS
Windows is responsible for handling the mouse clicks and moves, all keystrokes, all disks and printer access and indeed, all I/O related events. No single program can take direct control of an I/O device. So when the user clicks on the mouse button, Windows figures out where the mouse cursor is and transmits the mouse click information to the application that has a window directly below the cursor. Other programs, which may also have windows on the screen, are unaffected. When the user presses a key on the keyboard, Window figures out which Window has the keyboard focus and sends the keystroke information only to that window.
CPU TIME
A single-tasking operating system gives control to an application for as long as the application runs. Windows however gives control to an application only when an event that affects the application occurs. If the user presses a key when an application's window has the focus, Windows transmit this information to the application. The application, in turn, takes the appropriate action, such as displaying the character. When the application has processed the keystroke, it returns control to Windows, which can check for the next keystroke or mouse-click, which may or may not be intended for the same application.
STRUCTURE OF WINDOWS PROGRAM
A Windows program can perform two basic tasks:
Creation of the program's own Window and performing startup activities such as setting aside memory space when the program is first loaded.
Processing messages from Windows.
The client area of the window is the central portion in which the program can draw graphics and write text. If a program tries to write outside the client area, nothing shows up on the screen. This is how Windows prevents programs from interfering with each other on the screen. When a program's Window is visible, it will wait until Windows sends it a message. The waiting is accomplished by means of a program loop called the Message Loop.
The general outline of a Windows program is as follows:
Create the program's window
Do any initialization if any
Fetch message sent from Windows
Is message QUIT?
If the message == QUIT
Terminate the program;
Return control to Windows;
If the message is something else
Do appropriate action based on the message;
Return control to Windows;
ENDLOOP
INSTANCES, HANDLES, HUNGARIAN NOTATIONS AND DATA TYPES
In event-driven programming, messages are passed to programs in response to the input received from the user, which can be in the from of mouse clicks or keystrokes. In the programs all these messages are received and handled by what is known as a message loop.
Given below is an example of a program to create a Window
#include <windows.h>
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int CmdShow)
Every running copy of a program is called an instance. To refer to any part of an instance a handle is required. A handle is an unsigned integer that Windows uses internally to keep track of objects in the memory. Handles are used also to keep track of running applications, allocated memory blocks and a host of other objects. A handle is assigned to a particular entity and it serves to identify that entity whether it is an instance, a window or a block of memory. There are a number of related data types for identifying different kinds of handles. All these data types begin with the letter H. Thus HWND is the data type for a window handle, and HGLOBAL is the global memory handle. In the above program, hInstance is the handle that identifies the running applications data and hPrevInstance is the handle that contains the value for the last copy started.
Internally, a portion of Windows called Kernel maintains tables that allow Windows to convert handles to physical memory addresses. A handle is a pointer to a pointer to a memory location. The reason for this complexity is that windows moves object in memory to make room for other objects. If a window moves an object in memory the handle table is updated. The handle used by the program does not need to change since it is always translated to the right physical memory address by the window kernel no matter where the object is moved in the memory.
MEANING OF VARIABLES USED IN THE PROGRAM
hInstance This is the instance handle for this running application. Windows
creates this unique ID number when the application starts. If you start more than one instance of a program each instance will have a unique instance handle and hInstance is the number that identifies the running application's data.
hPrev More than one copy of the same Windows application can run at
the same time. If another copy is started, hPrev will contain an hInstance of the last copy started. If there is only one instance of the application running, hPrev will be NULL. (NULL is defined as zero in windows.h).
lpszCmdLine A pointer to a character string containing the command line
arguments passed to the program.
NCmdShow An integer value that is passed to tell the program whether to appear minimized, as an icon, normal, or maximized when first displayed.
hWnd Handle to the window.
The data types of the variables used in a Windows program follow a naming convention called as Hungarian Notation, in honor of Charles Simonvi. The idea is to precede the variable name with key letters that describe the type of data it represents. A list of some Hungarian Notations used are as follows:
Variable name prefix code Data type
b BOOL (Boolean); variable can take either True or False values
by byte (unsigned character)
c char (character)
dw DWORD (double word)
fn Function
h Handle
i int (integer)
l Long
n Short (int) or near pointer
p Pointer
s Character string
sz Character string terminated by zero
w Word (two bytes)
windows.h HEADER FILE
The windows.h header file is one of the key header files for a Windows program. It contains a multitude of definitions needed by all Windows programs, including prototypes for the hundreds of window functions, definitions of structures, constants and derived data types. This file must be included in every Windows program.
WinMain Function
WinMain is Windows equivalent of the normal C language function main and so every Windows function must have a WinMain function. When a Windows application starts it is the WinMain function that begins execution. When a Windows application ends it is the WinMain that returns the exit code to Windows. The exit code is an integer value-the reason why WinMain is declared an int PASCAL function. PASCAL is a compiler keyword as it notifies the compiler to use the function calling conversions of the PASCAL language rather than the C language. The reason for this is that it produces less computer code when compiled into machine instructions. This is because the PASCAL calling convention puts parameters passed by the function on to the stack in reverse order as listed in the function's declaration. The compiler always knows the first argument is at the top of the stack in case of the PASCAL convention whereas in case of the C convention the compiler needs to determine the location of the first argument by working down the stack. When converted into CPU instructions the C calling convention results in some extra instructions each time the function is called. PASCAL, on the other hand, does not allow the function to have a variable number of arguments. Windows uses the PASCAL convention whenever possible to make the programs smaller and faster.
CreateWindow Function
The CreateWindow function creates new windows and child window controls. The function is used both in WinMain function to create the application's main window and also within the program to create child windows and child windows controls, such as button sand scroll bars. CreateWindow builds a window based on a predefined control class. The location, size and style of the window are passed to CreateWindow as parameters.
Syntax:
HWND CreateWindow (LPSTR lpClassName, LPSTR lpWindowNmae, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HANDLE hInstance, LPSTR lpParam);
** The example creates an overlapped window which has an overlapped style. The CW_USEDEFAULT identifier indicates that we want default dimensions for our window.
DISPLAYING A WINDOW
After the CreateWindow call returns, the window has been created internally in Windows. However, the window does not yet appear on the screen so your application must display the window by using the ShowWindow function. This function tells Windows to display the new window. The first parameter to this function is the handle to the window just created by the function CreateWindow. The second parameter is the nCmdShow, which tells the application whether to display the window as an open window or as an icon.
Syntax:
BOOL ShowWindow(HWND hWnd, int nCmdShow);
nCmdShow can take any of the following values:
SW_HIDE Hides the window
SW_MINIMIZE Minimizes the window
SW_RESTORE Activates and displays the window
SW_SHOW Activates and displays the window in current size and position
SW_SHOWMAXIMIZED Activates and maximizes the window
SW_SHOWMINIMIZED Activates and minimizes the window
The following function displays a Window:
ShowWindow(hWnd,nCmdShow);
WNDCLASS STRUCTURE
All the objects of Windows like buttons and scroll bars are classes of Windows. The Window environment supports the creation of new windows from a class of windows called as WNDCLASS. The members of WNDCLASS include the style of the window, the window icon, window cursor shape, menu name, etc. This structure is defined in WINDOWS.H
The WNDCLASS structure contains the following members:
Typedef struct tagWNDCLASS{
Word STYLE;/*BINARY FIL SPECIFIES THE WINDOW STYLE*/
LONG (FAR PASCAL *lpfn WndProc)();/*poiter to message function*/
int cbClsExtra; /*extra bytes with class (c
an be 0)*/
int cbWndExtra; /*extra bytes with each window(can be 0)*/
HBRUSH hbrBackground /*the brush to use in painting background*/
LPSTR lpszMenuName; /*the class menu name*/
LPSTR lpszClassName; /*the name of the class once registered*/
} WNDCLASS;
RegisterClass Function
New window classes need to be registered. This is done using the RegisterClass function. The RegisterClass takes only one parameter- a pointer to the WNDCLASS structure. Before using the RegisterClass function, it is necessary to populate members of the WINDCLASS structure with suitable values.
Here is an example to register a class in the WinMain function.
WNDCLASS wnd;
wnd.style = CS_HREDRAW | CS_VREDRAW; /* CS_HREDRAW and CS_VREDRAW are binary flags that specify the drawing vertical or horizontal. */
wnd.lpfnWndProc = (WNDPROC)WndProc; /* The Windows environment sends message data from Windows class to the WindProcfunction to WndProc address is being assigned here. */
wnd.hbrBackground = GetStockObject(WHITE_BRUSH); /* white background */
wnd.lpszMenuName = NULL; /* no menu created */
wnd.lpszClassName = "My Class"; /* name of the window class */
/* register the class */
if (!RegisterClass(&wnd)) /* quit if class can not be registered */
return (0);
The function LoadIcon loads the icon IDI_APPLICATION and returns the handle to the icon in memory. LoadCursor loads the standard arrow cursor shape and returns a handle to the cursor in memory.
Once the window class is registered, it is created using the CreateWindow function and is displayed using the ShowWindow function.
MESSAGE LOOPS - THE MSG STRUCTURE
Windows applications cooperate with Windows environment by continuously checking whether there are any incoming messages. Writing a small program loop called the message loop does this. This determines the message and the procedure to display. When the Window procedure processes a message, it returns 0 from the window procedure. All messages that a window procedure chooses not to process must be passed to a window function named DefWindowProc. The value returned from DefWindowProc must be returned from the window procedure.
Messages are small packets of data that Windows sends to a running program. Windows generates messages for every hardware event. It passes these messages to the appropriated thread message queue. At start-up, a process is granted a single thread. A process can add threads to the queue by calling Win32 functions. You can visualize a message as being a block of memory divided into the six different types of data defined in the MSG data structure.
The following is an example of the MSG structure.
typedef struct tagMSG
HWND h Wnd; /* window's handle */
WORD message; /* message-id value */
WORD Wparam; /* one WORD of data passed with the message */
LONG 1Param; /* LONG data passed with the message */
DWORD time; /* time the message was sent */
POINT pt; /* mouse cursor x, y location when sent */
}MSG;
Consider the following code segment:
#include<windows.h>
long FAR PASCAL WndProc(HWND,WORD,WORD,LONG);
int PASCAL WinMain(HANDLE hInstance,HANDLE hPrevInstance,LPSTR lpszCmdLine,int nCmdShow);
The program has two main functions: WinMain and WndProc. WinMain initializes the application and then performs the task of message retrieval and dispatch using a looping construct. The loop terminates when a WM_QUIT message is received. On receipt of this message WinMain exits the application returning the value passed in the WM_QUIT message's wParam parameter. If the message WM_QUIT is received as a result of the program calling the function PostQuitMessage, the value of wParam is the value of the PostQuitMessage function's ExitCode parameter.
The WndProc that is associated with a window by the process of registering the window class handles the processing of messages. Registration depends on the structure wndclass that contains a pointer lpfnWndProc, which points to the WndProc function. The wndclass structure also contains specifications for the cursor, background brush, etc. The structure is passed as a parameter alongwith the name of the class to the function RegisterClass. In the above function only one message has been mapped - the WM_DESTROY message. All other messages that the program may receive are handled by the default handler function DefWindowProc.
COMMON API FUNCTIONS
The commonly used API functions are:
GetMessage FUNCTION
The GetMessage retrieves the messages from the calling thread's message queue, which are directed to a program window. GetMessage pulls the next waiting message from Windows into the MSG structure. If there are no messages waiting control is given to the window which has waiting messages. The function returns True only if there are any other messages otherwise it returns False. GetMessage does not retrieve messages for windows that belong to other threads or applications.
Syntax:
BOOL GetMessage(LPMSG lpMsg, HWND hWnd, WORD w1, WORD w2);
lpMsg pointer to a MSG message structure.
hWnd handle to the window receiving messages
w1 lowest value message to receive, usually set to be 0
w2 highest value to receive , usually set to 0;
DispatchMessage FUNCTION
This function sends Windows messages to the Window's WndProc function. This function is invoked from within the WinMain function's message loop.
Syntax:
LONG DispatchMessage(LPMSG lpMsg)
lpMsg pointer to the MSG STRUCTURE
TranslateMessage FUNCTION
This function translates keyboard messages into character messages. It generates a WM_CHAR message when a virtual key code is received. It is normally a part of the program's message loop. The function returns a True value if the message is translated, else it returns False.
Syntax:
BOOL TranslateMessage(LPMSG lpMsg);
lpMsg pointer to the structure MSG.
PostQuitMessage FUNCTION
The function terminates an application by making a thread. It posts a WM_QUIT message to the application. This is typically used in response to a WM_DESTROY message.
Syntax:
void PostQuitMessage(int exitcode);
DestroyWindow FUNCTION
This function deletes the window identified by the handle of the window.
DefWindowProc FUNCTION
This function provides a default processing for Windows messages i.e. it provides a default action to be taken when a message that has not been coded is received by your program.
Syntax:
LONG DefWindowProc(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam);
hWnd handle to the window receiving messages
wMsg this is the message;
wParam data passed with the message
lparam data passed with the message
MessageBeep FUNCTION
This function is used to alert the user. It has a sound type parameter that is usually set to zero. Each sound type is identified by an entry in the sounds section of the registry, which is a central database used by Windows to store application settings and system settings.
Syntax:
BOOL MessageBeep(UINT uType);
uType sound type as identified by an entry in the sounds section of the register.
MessageBox FUNCTION
This function creates a small message window containing a message.
Syntax:
int MessageBox(HWND hWnd, LPSTR lpText, LPSTR lpCaption, Word wType);
lpText message in the window
lpCaption message in the caption area
wType refers to the type of message box required.
SetWindowText FUNCTION
This function sets the text in the caption area of the window to specific value.
Syntax:
SetWindowText(HWND hWnd, LPSTR wCaption)
COMMONLY USED MESSAGES
WM_COMMAND MESSAGE
This message notifies the program that the user has selected a menu item and that the child window controller has an accelerator key (shortcut key).
WM_DESTROY MESSAGE
This message notifies the program that a window is destroyed after it is removed from the screen. This message is sent after the window image is removed from the screen.
WM_QUIT MESSAGE
This message indicates a request to terminate an application and is generated when the application calls the PostQuitMessage. It causes the GetMessage function to return 0.
WM_CHAR MESSAGE
This message is used to retrieve ASCII keyboard inputs such as letters, numbers and printable symbols.
In the previous program we had declared wndclass.lpszMenuName =NULL. When we want to declare a menu we can say:
wndclass.lpszMenuname MAKEINTRESOURCE(IDR_MENU1);
/*The menu declares a default identifier IDR_MENU1 attached to it*/
So in the case of switch statements WM_COMMAND Message can be used because this message is generated when a menu item is selected.
PROJECT AND SOURCE FILES
A program is built from source files and libraries. Source files are compiled into object files and then linked with the libraries to create a program. Before you create a program you need to create a project for it. A project can consist of only one or many source files, resources and library files.
Projects keep track of various file and libraries that are needed to build programs or a library. They also contain information about compilers and linker options. By creating a project and selecting a project type you can specify the kind of target file you want to generate when you build the project. A project also contains information saved in workspace such as window sizes and positions.
A project is stored on the disk as two files:
A makefile (.MAK extension)
A status file (.VCW extension)
The makefile specifies the rule for the compiler and linker to build the target file. When you open the project file the status file is automatically loaded. The makefile shows how to compile your entire application by listing the dependencies among source object, executable and data files for your new application.
A project also speeds up development time by recompiling only files that have changed since the program's last compilations.
STEPS TO CREATE A NEW PROJECT
The first step is to select the option Win32 and type in the project name choosing the project Workspace
After selecting OK another screen appears which asks you about the application you want to work on. Select a simple Win32 application from there and press Finish
After pressing Finish a simple Win32 application is created. If you want to enter the source code of the program select the option New from the menu File and select the tab Files. From the screen choose the option Text File and give it any name and then press OK.
Select text file to enter source code.
Save the text file from the Save As option of the File menu. From here you can also change the name of the file if you want to.
For a simple Windows application with the minimum number of files, a project will contain at least the following files:
MAK file
MDP file
NCB file
C file
The first three files are created automatically when a new project workspace is created. You can also create the .C file using the Text into File option from the Insert menu.
COMPILING AND LINKING PROGRAMS
To compile your program you select the option Compile from the Build menu. The program is linked using the option Build and is executed using the Run option.
@THE WINDOWS ENVIRONMENT
REVISITING OBJECT ORIENTATION
Some of the important concepts of OOP are:
Encapsulation: this is the hiding of the non-essential details of an object. This is a means of implementing abstraction and is used interchangeably with the term information hiding.
Abstraction: this is the process of concentrating on the essential details of an object, and hiding the non-essential ones.
Polymorphism: is the phenomenon where different objects respond differently i.e. they respond by using different methods to the same message from another object.
Inheritance: is a method by which one class shares the structure and behavior of another class.
PROGRAM STRUCTURE
Microsoft Foundation Classes (MFC) has divided the Windows program into 4 different parts:
CFrameWnd: the main window that includes toolbar, borderline, window title, window menu and status bar.
CDocument
CView
CWinApp
So a simple application program consists of CDocument, CView, CFrameWnd and CWinApp classes objects.
Note in any Windows program there is only one CWinApp class object but there may be many CView, CDocument and CFrameWnd class objects.
CWinApp CLASS
The CWinApp class is the base class from which you derive a Windows application object. An application object provides member functions for initializing each application and for running the application. The CWinApp class object links the 3 objects of CDocument, CView, and CFrameWnd. There is only one object of CWinApp in the program.
InitInstance FUNCTION
Windows allows several applications to run at the same time. Application initialization is divided into two parts: a one-time application initialization that is done the first time the program runs and instance initialization that runs each time a copy of the program runs. Every time a new instance is initialized, the framework's implementation of WinMain calls this function. After an instance has been initialized, a message loop is set up for the instance. When an application class is derived from CWinApp, the InitInstance function is overridden.
Run FUNCTION
This function provides a default message loop. Run acquires and dispatches Windows messages until the application encounters the WM_QUIT message. It is rarely overridden.
Exitinstance FUNCTION
This function is called by the framework from within the Run member function to exit from the current instance of the application. The fu
nction should not be called from anywhere but within the Run member function. The default implementation of this function writes framework options to the applications .INI file. It can be overridden to perform a clean up when an application terminates.
CFrameWnd CLASS
This class provides the functionality of a Windows Single Document Interface (SDI) overlapped or pop-up frame window, alongwith the members for managing the window. To create a useful frame for your window it is necessary to derive a class from CFrameWnd Class.
The CFrameWnd object is created in two steps. The first step is to invoke the constructor, which constructs the CFrameWnd object and then call the Create function, which creates the frame window and attaches it to the CFrameWnd object.
Consider the following program segment:
#include<afxwin.h> /*this header file contains the definition for the CWinApp & CWnd Class*/
class Mywindow:public CFrameWnd
public:
Mywindow()//constructor definition//
Create(NULL, "Message Window");
MessageBox(""SAMPLE WINDOW", "THIS IS THE CONSTRUCTOR");
~Mywindow()//destructor definition//
MessageBox("SAMPLE WINDOW", "THIS IS THE DESTRUCTOR");
//Application Class
class Myapp: public CWinApp //user defined application
public:
BOOL InitInstance();
BOOL ExitInstance();
}; // end of class declaration
BOOL Myapp::InitInstance()
::MessageBox(0, "SAMPLE WINDOW", "INTIINSTANCE",
MB_OK|MB_ICONASTERISK);//pointer to the user defined window//
Mywindow *MyWindowObject; //creation of a new user defined window//
Mywindowobject = new Mywindow;//variable containing a path to window//
m_pMinWnd=Mywindowobject;//sets the visibility status of the window to maximum//
// double colon used as the MessageBox function of SDK is invoked//
::Messagebox(0, "SAMPLE WINDOW", "EXIT INSTANCE",
MB_OK|MB_ICONHAND);
return TRUE;
// creation of an application object
Myapp Application Object;
The function Create is a member of the CFrameWnd class. It creates a new window frame and attaches it to the CFrameWnd object. Create initializes the window's class name and window name and registers default values for its style, parent and associated menu. In this the first parameter points to a null terminated string that names the window class. If the first parameter is NULL then the predefined default CFrameWnd attributes are made use of during the creation process and the second parameter points to a null-terminated character string that represents the window name. This is used as the text for the title bar.
After deriving the CFrameWnd class the next step is to derive the CWinApp class where it is important to override the InitInstance Function of the CWinApp class. The InitInstance function should create an instance of the CFrameWnd derived class
Mywindow.
The style MB_OK used in the MessageBox displays an OK button on the message box. This ensures that the message box is displayed until the user clicks on the OK button. The rest of the styles and their icons are as follows:
STYLES ICON DISPLAYED
MB_ICONHAND & MB_ICONSTOP
MB_ICONQUESTION
MB_ICONEXCLAMATION
MB_ICONASTERISK & MB_ICONIFORMATION
The final task is to create an instance of the CWinApp derived class Myapp. The following statement does this
Myapp Applicationobject;
So when the application is executed an object of CWinApp class is created. This in turn creates an object of the CFrameWnd Class and the window is displayed.
MESSAGE MAPS
Message maps are mechanisms which help trap the window messages and commands to Windows, documents, views and other objects in an MFC application. MFC provides a collection of macros that specify which function will handle various messages for the class.
Most messages result from user interaction with the program. Commands are generated by clicking on menu items or toolbar buttons or by accelerator keystrokes. Window messages are also generated when a user moves or resizes a window. Control notification messages are generated by mouse clicks or other user interactions with control such as a button or list box control in a dialog box. The Run member function of the class CWinApp retrieves messages and dispatches them to the appropriate window. The target window object usually handles window messages. Each object is capable of receiving messages or commands and has its own message maps. These maps pair a message or command with the name of its handler
the function that performs a specific task on receipt of messages. When a window or any other command target object receives a message or command it searches its message map for a match. If it finds the handler, it calls it up. There are 3 main categories of messages:
Windows messages: These primarily include messages beginning with the WM_ prefix, except for WM_COMMAND. Windows and views handle windows messages and have parameters that are used in determining how to handle the messages.
Control notifications: These include WM_COMMAND notification messages from controls and other child windows to their parent windows. For example an edit control sends its parent a WM_COMMAND message containing the EN_CHANGE control notification code when the user has taken an action that may have altered text in the edit control. The window handler for the message responds to the notification message in the appropriate manner such as retrieving the text in the control. The framework routes control notification messages in the same way as other WM_ messages. The exception, however, is the BN_CLICKED control-notification message sent by the buttons when the user clicks on them. This message is treated specially as a command message and is routed like other command messages.
Command messages: These include WM_COMMAND notification messages from user-interface objects - menus, toolbars, buttons and accelerator keys.
The messages are passed from the window to the application program, which is active at that item. The CView first receives the message in the application program. It is then passed to the CDocument. If it is not processed by these two classes then the CFrameWnd and CWinApp receive it and process it.
MESSAGE MAP MACROS
The MFC library offers a programming model for message-based programming. In this model, message maps are used to designate the functions that will handle various messages for a particular class.
The DECLARE_MESSAGE_MAP macro is used at the end of a class. It indicates that there are message map entries after the class for which handlers are written as member functions of the class.
The BEGIN_MESSAGE_MAP macro is used to begin the definition of message maps. The macro takes two parameters
the class name of the derived class and the class name of the parent class.
The END_MESSAGE_MAP macro marks the end of the definition of a message map indicating that there are no more message map entries for a particular class.
The ON_COMMAND macro is usually inserted in a message map by the ClassWizard or can be manually indicated as to which function will handle a command message from a user-interface object. When a command-target receives a window with the specified ID, ON_COMMAND calls the member function defined in the respective class to handle the message.
**A colon does not terminate message map entries.
Let us now add message map entries to an earlier sample program.
#include<afxwin.h>
#include "resource.h"
class Mywindow:public CFrameWnd
//the code is same as that of the previous one//
void GenHandle();
Mywindow();
DECLARE_MESSAGE_MAP()
// MESSAGE MAP ENTRIES//
BEGIN_MESSAGE_MAP (Mywindow, CFramewnd)
ON_COMMAND (ID_MY_FILE,GenHandle)
END_MESSAGE_MAP()
void Mywindow:: GenHandle() //handler for option file
Message box ("this program uses message maps", "message Maps");
Mouse has become an indispensable device as far as Windows is concerned, although a programmer can make programs without using a mouse. Despite this, it is essential to support mouse operations in case of Windows programming. So all Windows-based applications must support mouse handling since most events, which occur in Windows, are mouse dependent. On receiving messages from the mouse an appropriate action is taken by the application in question.
The various mouse messages are:
WM_MOUSEMOVE MESSAGES
CLIENT AREA MOUSE BUTTON MESSAGES (Client area is the actual portion of the windows excluding the borders, menu bar and caption bar.)
NON-CLIENT AREA MOUSE BUTTON MESSAGES (Non-client area is the portion of the windows which includes the caption bar, menu bars and borders.)
Windows sends messages to either the client area or the non-client area of a window.
Client area mouse Button messages are messages sent to the client area and non-client area mouse Button messages are messages sent to the non-client area of the window.
WM_MOUSEMOVE MESSAGES
When the mouse is moved the window automatically moves the mouse cursor on the screen and accordingly the cursor's position is updated.
To keep programs informed about the location of the mouse cursor, Windows sends WM_MOUSEMOVE messages. These messages are sent only to the application in which the mouse cursor is positioned and not in any other application. The lParam data sent alongwith the WM_MOUSEMOVE message contain the x and y coordinates as regards the mouse position. The LOWORD and HIWORD macros defined in the windows.h file extracts these coordinates. The wParam data passed alongwith WM_MOUSEMOVE messages contains a value which indicates whether one or several keys were pressed when these messages were being communicated to the window i.e. whether the Shift key or the Control were pressed or not. The combination of keys and buttons pressed can be determined by comparing the wParam value sent to mouse notification codes in windows.h header files.
MOUSE ACTIVITY BUTTON PRESSED BUTTON RELEASED
LEFT MOUSE BUTTON WM_LBUTTONDOWN WM_LBUTTONUP
CENTRE MOUSE BUTTON WM_MBUTTONDOWN WM_MBUTTONUP
RIGHT MOUSE BUTTON WM_RBUTTONDOWN WM_RBUTTONUP
MOUSE ACTIVITY BUTTON DOUBLE CLICKED
LEFT MOUSE BUTTON WM_LBUTTONDBLCLK
CENTER MOUSE BUTTON WM_MBUTTONDBLCLK
RIGHT MOUSE BUTTON WM_RBUTTONDBLCLK
MOUSE ACTIVITY BUTTON PRESSED BUTTON RELEASED
LEFT BUTTON WM_NCLBUTTONDOWN WM_NCLBUTTONUP
CENTER BUTTON WM_NCMBUTTONDOWN WM_NCMBUTTONUP
RIGHT BUTTON WM_NCRBUTTONDOWN WM_NCRBUTTONUP
Consider the following code which displays information regarding processing of wParam values sent along with WM_MOUSEMOVE messages:
long FAR PASCAL WndProc(HWND hWnd, WORD wMessage, WORD wParam, WORD lParam)
int x,y;
switch(wMessage)
case WM_MOUSEMOVE:
x=LOWORD(lParam);
y=HIWORD(lParam)';
if(wparam & mk_shift)
MessageBox(hWnd, "SHIFT", "shift key", 0);
if(wParam & MK_CONTROL)
MessageBox(hWnd, "CONTROL", "CONTROL KEY", 0);
if(MK_SHIFT & MK_CONTROL)
MessageBox(hWnd, "SHIFT AND CONTROL", "shift and control keys", 0);
In the above code, x and y positions are stored in the x and y integer variables respectively. These values are received by the LOWORD and HIWORD macros. When the mouse messages are sent the wParam value is combined with the value of the key pressed accordingly. MessageBox function displays a message on the screen.
There are 3 types of pointers:
NEAR 2 bytes in size and so used to point in one memory segment.
FAR 4 bytes in size so they are used to point in different segments.
TRACKING A MOUSE
Tracking allows a user to move the mouse cursor in a window.
The following program makes use of WM_MOUSEMOVE and mouse button messages to move the mouse cursor over the client area of a window. The program consists of three menu options: mouse tracker, clear screen and quit. If the mouse tracker option is chosen the mouse cursor leaves behind a small line on window's client area every time a WM_MOUSEMOVE message is received. On clicking the left mouse button the letter L is written. If the mouse cursor is moving very rapidly then WM_MOUSEMOVE message is sent every 10 to 20 pixels. The clear screen option clears the screen and the quit option exits from this program.
//sample program is as follows//
#include<windows.h>
#include"resource.h>
long FAR PASCAL WndProc(HWND, WORD, WORD, LONG);
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,LPSTR lpszCmdLine, int nCmdShow)
Create a new text or source file (.c) and include it in the project. Files with the extension .mak, .ncb and .ndp will be created.
Create a menu resource with the options mouse tracker, clear screen and quit, and save the program with the extension .rc.
LineTo FUNCTION
This function draws a line from the current position to a new point. It is used with the MoveToEx function to draw lines. LineTo function draws a line to coordinates x and y using the currently selected pen.
Syntax:
BOOL LineTo(HDC hDC, int x, int y)
where hDC is the device context handle.
MoveToEx FUNCTION
This function moves the current position of the cursor to the new position.
Syntax:
MoveToEx(HDC hDC, int x, int y, LPPOINT lpPoint)
where lpPoint is the pointer to a point structure. This parameter may be NULL.
Windows does not provide a single function to draw isolated lines (i.e. both the functions have to be used together).
TRAPPING MOUSE CURSOR'S POSITION:
An application that takes complete control of the mouse is said to have captured the mouse. An application can trap a mouse with respect to the screen and client coordinates. Screen coordinates are pixels measured from the upper left corner of the screen and client coordinates are pixels measured from the upper left corner of the window's client area. The lParam contains the cursor's position on the screen.
ClientToScreen FUNCTION
This function converts a point from client to screen coordinates. This function is used in programs that use a mouse to capture images off the screen.
Syntax:
void ClientToScreen?(HWND hWnd, LPPOINT lpPoint)
lpPoint is a long pointer to the point structure. Initially the point contains the client coordinates and this function does not return any value.
The ScreenToClient function also works in the same way as the ClientToScreen function except for the fact that it converts the client coordinates to the screen coordinates.
SetCapture FUNCTION
This function captures the mouse so that only prog
rams with the mouse captured receive mouse messages. This means that, mouse messages are sent only to the program which calls the SetCapture function and not to any of the other running programs.
Syntax:
HWND SetCapture(HWND hWnd);
hWnd handle to the window that captures the mouse.
The function returns a handle to the window that previously had the mouse captured. It returns a NULL if no window has captured the mouse.
ReleaseMouse FUNCTION
This function releases the captured mouse. It returns the mouse to the system, so that all windows can receive the message. This function is mainly used with the programs that outline or copy areas of the screen.
Syntax:
void releaseCapture(void)
The function does not return any value.
GetMessagePos FUNCTION
This function retrieves the x and y positions in screen coordinates of the cursor when a
message is sent.
Syntax:
DWORD GetMessagePos(void);
The function returns the x and y coordinates of the screen.
STOCK OBJECTS
The basic tools that Windows uses to provide device-independence for an application is called a device context or DC. The DC is an internal structure that Windows use to maintain information about an output device. Instead of sending output directly to the hardware an application sends it to DC and then windows sends it to the DC. Windows then sends it to the hardware which might either be a screen or a printer.
The device context also supplies a paint box containing items such as fonts, a drawing pen, a brush, palettes and regions. These objects are referred to as GDI objects. In Windows you can use a different pen or a different brush. The new object is first obtained and put in the paintbox where it replaces the old object. API functions responsible for these activities are:
GetStockObject function which obtains a GDI object
SelectObject function which puts the object into the paintbox and replaces the previously existing object.
So to use an object with a device context, you first create the object and select it. A GDI object needs to be deleted after use to avoid filling up the system memory. GDI objects continue to occupy the memory even after the application program terminates. Hence you delete these objects after use. Objects cannot be deleted if they are attached to the device context. You either delete them after releasing the device or select a stock object into the device context to displace the object you wish to delete.
The first handle to a device context obtained is usually a handle to predefined objects. These predefined objects are always available and are referred to as Stock objects. The default pen is a solid black line 1 pixel wide and the default brush is a solid white brush. A handle to the stock object can be obtained through the GetStockObject function. Once an object is selected it is used by every painting function that uses the device context. A stock object can be displaced from the device context by selecting another object.
TRAPPING THE MOUSE CURSOR'S POSITION
// SAMPLE PROGRAM TO CHANGE MOUSE CURSOR'S POSITION//
#include<window.h>
#include "resource.h"
// code is same as the previous program//
long FAR PASCAL WndProc(HWND hWnd,WORD wMessage,WORD wParam,LONG lParam)
WinMain function creates a window with a caption Mouse Cursor.
The WM_CREATE message notifies that the window is being created. The program data initialization and startup routines begin here. The WM_CREATE message is processed before CreateWindow is processed and before the window is made visible. The LoadCursor function loads a new cursor set. Each and every cursor set is associated with a particular identifier. The SetCursor function changes the cursor shape while processing the WM_MOUSEMOVE message. This function is used in windows that do not have a cursor loaded as a part of windows class definitions.
LoadCursor FUNCTION
This loads the cursor shape, which is predefined and custom-designed. The cursor must be loaded prior to the set cursor function. If you wish to have different cursor shapes then the cursor loaded as a part of Windows. The class definition should be set to NULL.
where, lpCursorName is a pointer to a string containing the cursor name.
SetCursor FUNCTION
This function sets the cursor to a shape that is desired by the user.
Syntax:
HCURSOR SetCursor(HCURSOR hCursor)
MOUSE RELATED MESSAGE MAP ENTRIES
ON_WM_LBUTTONDOWN() This message map entry can be used to trap the
depression of the left mouse button. It has an overridden function called OnLButtonDown which acts as the default handler for this message map entry .
ON_WM_LBUTTONUP() This message map entry can be used to trap the release of the left mouse button. The overridden function is OnLButtonUp which is the default handler for this message map entry.
ON_WM_RBUTTONDOWN() This message map entry can be used to trap the depression of the right mouse button. The overridden function is OnRButtondown which is the default handler for this message map entry.
These macros can be used to create shortcut menus.
CMenu CLASS
This class is useful for creating menus and manipulating them. The CMenu Object is used to represent the Windows menu. The menu resource is implicitly attached to the window when Create command is used to create a window.
However, one can also construct a CMenu Object separately. The sequence of steps to be followed is:
Create a pointer of the CMenu class in your CFrameWnd derived class.
Instantiate the CMenu object in the constructor of your CFrameWnd derived class.
Create the frame window but do not load a default menu.
Load the menu resource created through the graphic editor into memory using the LoadMenu function. Since the resource ID of the menu is an integer the MAKEINTRESOURCE macro has to be used to convert the integer value to a resource type compatible with the Win32 resource management function LoadMenu. This macro is used in place of a string containing the name of the resource. The menu resource is loaded from the applications executable file and is attached to the CMenu object.
The SetMenu function is then called to set the current menu to the specified menu and to redraw the window to reflect the menu change. SetMenu does not destroy a previous menu.
Before exiting, an application must free system resource associated with a menu if the menu is not assigned to a window. Calling the DestroyMenu member function can do this.
The following example illustrates the use of CMenu class and its member functions:
class Mywindow : public CFrameWnd
protected : CMenu *m_MyMenu; //pointer to CMenu class
public : Mywindow(); //pointer to CMenu class
//rest of code same as previous one//
Mywindow :: Mywindow()
m_Mymenu=new CMenu; //instantiate the menu
//create the window but do not load the default menu
Create(0, "sample window");
//load the menu//
m_Mymenu(LoadMenu(MAKEINTRESOURCE (IDR_MENU1);
//set the menu//
SetMenu(m_Mymenu);
SHORTCUT MENUS
Shortcut menus are also known as floating pop-up menus. Shortcut menus are a feature available in most packages that work on Microsoft Windows (these menus get displayed when the right mouse button is clicked and a menu gets displayed on the right hand side of the current mouse position). Care has to be taken that too many options are not included and that only relevant options are displayed.
To create a shortcut menu whose options are same as the sub-menu of the main menu of the application do the following:
Declare a pointer to the CMenu class.
Use the GetSubMenu member function of the CMenu class to get the address of the sub-menu to be popped up and assign it to the pointer.
Use the TrackPopMenu function to display the shortcut menu.
// The following program snippet shows the usage of the shortcut menus and assumes that the menu has already been created in the CFrameWnd class//
TPM_RBUTTONDOWN This is the first parameter which causes the pop-up menu to
track the right mouse button.
p1.x and p1.y p1 is of type CPoint - the CPoint class has two attributes x and y, which hold the x and y coordinates value of the point at which the right mouse button was clicked. These values are passed to the TrackPopupMenu to indicate the position where the popup menu was displayed.
this This pointer is accessible only within the member functions of a class or struct type. It normally points to the object for which a member function is called. Static member function do not have this pointer.
NULL This ensures that the pop-up menu is dismissed if the user clicks outside the pop-up.
CHANGING THE MOUSE CURSOR
The mouse cursor can be changed to indicate a specific task such as specify which of the following operations are in progress e.g. saving files, dragging and dropping objects from one part of the screen to another, marking texts, drawing objects, etc. The common steps required to change the mouse cursor are:
The first step is to create a cursor using the graphic editor.
Next load the cursor using the CWinApp member function LoadCursor. This function loads the cursor into memory for later use and returns a handle to the cursor.
Then, the mouse has to be captured using the CWnd member function SetCapture.
After the mouse has been captured, the SDK SetCursor function has to be invoked to change the current cursor type. When doing so, the handle received from the LoadCursor function has to be passed as a parameter to identify the particular cursor.
Finally, the CWnd member function ShowCursor is invoked to display the cursor on the screen.
After the processing is over the CWnd member function ReleaseCapture has to be invoked to free the current cursor and restore the default cursor.
//Students are advised to write the code for the above steps on their own as a practise exercise.//
CREATING RESOURCES
Resources include menus, accelerators, dialog boxes, icons, cursors, bitmaps, fonts and strings tables. Accelerator defines keyboard shortcuts for executing menu items, or other commands using the keyboard instead of the mouse. The string table is a table of character string that is used for messages and other static character data used within the program.
STEPS TO CREATE A RESOURCE
Let us assume that the project workspace with the name proj2 has been created and that it contains a text file with the name text.c. In order to create a menu bar resource with the options Display and Quit (the option display has a sub-option message), the following steps are to be carried out:
Select the option Insert from the menu bar. From the menu that is displayed, choose the option Resource. The Insert Resource dialog box is displayed thereafter.
On selecting New from the above dialog box the applicant is taken back to the workspace where the options for making a menu are displayed as shown in the following screen:
In the above screen, type Display as the first option and along its side type Quit as the second option. Press Enter after Display and a pop-up menu is displayed. Type Message in it and click on the right mouse button. Another menu gets displayed Select properties from there and a Menu Item Properties dialog box is displayed. Type in the required ID along with the caption (if you want to change the caption).
Save the resource script file. To do so, select the option File from the menu bar and choose the sub-option Save. The Save As dialog box is displayed. By default the file has the filename Script.rc, which can be changed.
The project workspace will now contain the .C file, the .RC file and the resource file if all the files have been included into the workspace using the Insert - Files Into Project option.
Consider the following sample program code for creating a window with a menu:
#include <windows.h>
#include "resource.h"
long Far PASCAL WndProc(HWND, WORD, WORD, LONG);
int PASCAL WinMain( HANDLE hinsatnce, HANDLE hprevinstance, LPSTR lpsz, int ncmd)
In the above program the WM_COMMAND message is generated when you select a menu item.
@USES OF COMPUTER GRAPHICS
Computer graphics are used in many areas and its list of applications is keeping pace with the increasing demand for computers with graphic capabilities.
Interactive computer graphics has the added advantage of making pictures of real, concrete objects as well as those of abstract synthetic objects. You are no longer confined to static images such as photographs. In fact, you now interact with real animated pictures.
With motion dynamics, objects can be moved and tumbled with respect to a stationery observer. Or, the objects can remain stationery and the user can move around them, zooming onto a particular thing as per his convenience. Video arcades offer computer-based games such as car races, driving simulators and video games with interactive motion dynamics.
You can also update the shape, color or other properties of objects being viewed or displayed. For example, a system can display the deformations of an air plane structure in flight in response to the operator's manipulation of the graphical representation of control dynamic mechanisms.
Interactive computer graphics permit extensive user-computer interaction. Such interaction enhances our ability to understand data, to visualize real or imaginary objects and to perceive the latest trends. All graphic packages, however complicated, are based on the simple application of paintbrush.
CHARACTER MODE vs. GRAPHICS MODE
In the character mode the dot pattern for each character is shown on the screen stored in the video boards memory chips. The usual display configuration allows 80 characters across each row and 25 line of characters. Each character occupies a fixed position on the screen. The character always has the same font although the color and background of each letter can be changed. The character mode, however, puts a limit on what you can show on the screen. This display limits the way of showing different character fonts such as italics, bold and finely formed pictures. In the graphics mode, everything is drawn one dot at a time. A typical VGA screen will show 640 pixels horizontally and 420 pixels vertically. Each pixel can take any of the 16 colors. More expensive displays such as super VGA show 1028/760 pixels with 256 simultaneous colors. Windows always runs in the graphics mode. This is lower than using a character display mode but much more flexible since everything is drawn one pixel at a time. Windows has no trouble displaying characters in different sizes, with italics and bold face letters and attractive window messages with buttons and scroll bars.
DEVICE INDEPENDENCE
Device independence is the most important feature of Visual C++ which means that the program works using different screens, keyboards and printers without any modification to the program. Simply put, Visual C++ is hardware independent. Due to this feature Visual C++ is not used as a system language. In Visual C++ the interaction of the application program has been abstracted from hardware.
GRAPHICS DEVICE INTERFACE
The graphics device interface (GDI) provides Windows applications with the device independent interface to the screen and the printer. The GDI is a layer between the application and the different types of hardware. This architecture frees the programmer from having to deal with the problem of modifying his program according to the needs of the hardware and instead lets this layer tackle this problem. This GDI is a part of Windows that converts the Windows graphics to actual commands sent to the hardware. The GDI is a program file called GDI32.EXE i.e. which is stored in the SYSTEM32 directory (in case of Win NT and SYSTEM in case of Win 95) and the Windows environment loads this file into the memory when it is required for graphical output. Windows also loads a device driver (this is a program that assists GDI in converting Windows graphics commands to hardware commands) program if the hardware conversions are not part of the GDI32.EXE file. In other words, GDI is an interface between the application program and the hardware. It is used to display everything on the screen. The class that supports the GDI is the CDC.
CONCEPTUAL FRAMEWORK FOR INTERACTIVE GRAPHICS
APPLICATION PROGRAM
The application program creates, stores into and retrieves information from the second component-the application model.
APPLICATION MODEL
The application model represents the data or the objects to be displayed on the screen. It also handles user input. This program produces views or visual display information of the input received from the application program by sending it to the third component-the graphics system.
GRAPHICS SYSTEM
A graphics system is a series of graphics output commands that contain a detailed geometric description of what is to be viewed and the attributes describing how it must appear. The graphics system is responsible for actually producing the picture from detailed descriptions and for passing the user's input to the application program for processing. The graphics system is thus an intermediary between the application program and the display hardware that affects an output transformation from objects in the application model to a view of the model.
The fundamental task of the designer of an interactive graphics applications program is to specify what classes of data items or objects are to be generated and represented pictorially. The designer should also specify how the user and the application program are to interact in order to create and modify the model and its visual representation. Most of the programmer's task involves creating and editing the model and handling user interaction. The actual creation of views is handled by the graphics system.
In short, graphics systems are commands that describe what is to be viewed and with what
attributes.
INTERACTION HANDLING
The application program which you create usually responds to user interactions in two modes:
Screen update mode: the user may require only the screen to be updated. For example, by highlighting a selected object or by making available a new menu of choices. In this the image of only the selected object is changed. The model of the images is not changed as the application does not change the model. Only the graphics package is called to redraw either the whole image or only a selected portion.
Model change mode: the user action calls for a change in the model. For example, by adding or deleting a component. The application must update the model and then call the graphics package to update the screen from the model. Either the entire model is retraversed to regenerate the image from scratch or with more sophisticated incremental update algorithms the screen is updated selectively. In this case the user manipulates not an image but a model of that image. Therefore it is the application's job to interpret user input. The graphics system has no responsibility for building or modifying the model, either initially or in response to user interaction. Its only job is to create images from geometrical descriptions and to pass along the user input data to the application. In other words the application interprets the user input and then the model is rebuilt or modified by the application. The graphics package only redraws the image of the model with changed geometrical description.
While interactions are handled by the event-driven loop the basic emphasis from the designer's perspective is on developing device-independent graphics packages. Manipulating device contexts develops these.
A device context is a window structure that contains information about the drawing attributes of a device. It can also be defined as a link between Windows-based applications, a device driver and an output device. All drawing calls are made through the device context object, which encapsulates the Windows API for drawing lines, shapes and text. As a link it provides a connection to an output device as well as permits drawing on that device. Device contexts are used to draw on the screen, the printer or on a metafile. A device context constrains the drawing area an application can use. For example, it limits the painting to the region of the client window and ensures that an application cannot accidentally draw outside its frame window. Device context acts as a link between the device and the application program.
A device context also functions as a drawing toolbox. It contains:
Drawing attributes, which contains the background and foreground colors, text alignment, justification, assorted origins and drawing modes.
Drawing tools (GDI objects) includes pens, brushes, fonts and palettes. At one time only one pen, one brush and one font can be selected and used.
Current drawing positions certain drawing functions rely on the concept of a current drawing position. The device context maintains a single current position point.
Whenever you want to draw onto a device context you need to access the MFC CDC class or its derived class-CClientDC. While the former is automatically created and manipulated by MFC for accessing all devices, the latter is used for accessing the client area of the window. Given below is a list of CDC derived classes and their respective drawing areas:
CDC DERIVED CLASS DRAWING AREA
CClientDC Client area of the window
CPaintDC Invalid region of the client area
CWindowDC Anywhere on the application window
CMetafileDC A file pseudo device
PRINTING TEXT ON THE SCREEN
Consider the following piece of code:
hDC=GetDC(hWnd);
Textout(hDC, 10, 10, "good morning", 20);
ReleaseDC (hWnd, hDC);
hDC is the handle to the device context.
hWnd is the handle to the window.
GetDC retrieves a device context handle (HDC) for the client area of the application's window.
TextOut writes the text to this device context, starting at a point 10 pixels down and 10 pixels to the right to the upper left corner of the client area.
ReleaseDC is used to free the memory associated with the device or window.
When the CDC class is used do not forget to destroy it once it is constructed.
Consider the code segment:
GetDC(hwnd); // constructs CDC//
TextOut(" Good Morning");// to display on the screen//
If you do not destroy it now using the ReleaseDC function then your program might crash later.
In case of OnDraw() CDC is destructed automatically.
However, in case of CClientDCc you do not have to destroy the CDC. Consider the code segment given below:
sprintf(temp, "x= %d, y=%d", pt.x, pt.y); // sets the screen device for the client area//
CClientDC dc(this); // display the text on the screen//
dc.TextOut(0, 0, temp, screen(temp);
The above program defines the device as CClientDC(this), but does not destroy it later.
So it is better to use CClientDC in case you forgot to destroy the object of the class created using the CDC.
GetDC FUNCTION
This function retrieves the handle of a display device context for the client area of the window. Depending on the class style of the window the GetDC function retrieves a common class or a private device context. Default attributes are assigned to the common device contexts each time they are retrieved. Attributes of class and private device contexts are left unchanged. When the application finishes using DC, ReleaseDC is used to free the device context. If successful the GetDC function returns the DC for the window's client area. Otherwise the return value is NULL.
Syntax:
HDC GetDC(HWND hWnd)
ReleaseDC FUNCTION
This function releases a device context making it available for use by other applications. ReleaseDC frees only common and Windows-based device contexts. It has no effect on the class or private device contexts. It returns TRUE if the DC is released otherwise the value returned is FALSE.
Syntax:
int ReleaseDC(HWND hWnd, HDC hDC);
TextOut FUNCTION
This function writes a character string at the specified location, using the selected font. An application retrieves this mode by calling the GetTextAlign function and modifies it by calling the SetTextAlign function. It returns TRUE if TextOut function is successful, otherwise it returns FALSE.
Syntax:
BOOL Textout(HDC hDC , int X, int Y, LPCTSTR lpszString, int cbstring);
hDC refers to the device context.
X refers to the logical x-coordinate of the reference point used to align the string.
Y refers to the logical y-coordinate of the reference point used to align the string.
lpszString, refers to the pointed string to be drawn.
cbstring refers to the number of characters in the string.
/*Sample Program to print text on the screen */
#include <windows.h>
#include "resource.h"
long FAR PASCAL WndProc (HWND hWnd, WORD, WORD, LONG);
In the above program if the user has pressed Show Text option then the ID_MESS identifier causes the text to be displayed on the screen. This is done using the TextOut function. If the user has selected the Quit option, then the ID_QUIT identifier causes the window to be deleted. This is done using the DestroyWindow function. The WM_DESTROY message causes the application to be stopped. The PostQuitMessage function generates the WM_QUIT message. The default option handles the default messages received by the program from the Windows environment.
Steps to execute the program
Create a new project workspace.
Create a .rc file that contains the above program listing.
Create a new menu resource file that has the menu bar with the options Show text and Exit.
Include the .c file, the .rc file and the resource.h file in the project workspace.
Compile, build and execute the program.
The output of the above program is as follows
WM_PAINT MESSAGE
In the sample program above once the text is displayed on the screen it remains there. If the window is minimized or maximized then the text disappears. The reason for this is that Windows repaints the area every time it is uncovered or changed in size. This automatic repainting logic is the reason why your program vanishes from the screen when it is covered by another application. When the window is about to repaint the application programs window it sends the application WM_PAINT Message. The function of this message is to redraw the screen or the client area. This message gets executed whenever minimization or maximization of Windows is done.
Consider the following code segment:
case WM_PAINT:
hDC=BeginPaint(hWnd,&ps);
TextOut(hDC,10,40,"Painting The Screen",19);
EndPaint(hWnd,&ps);
break;
where ps is a variable of type PAINTSTRUCT structure.
The above code repaints the screen instead of doing the default action. All programs using WM_PAINT use BeginPaint and EndPaint functions in sequence. The BeginPaint function fills values in a PAINTSTRUCT data structure, which is defined in the windows.h header file as follows,
typedef struct tag PAINTSTRUCT
HDC hdc;/*device context handle */
BOOL fErase;/*background redrawn?TRUE / FALSE */
RECT rcPaint;/*RECT of client area update rect.*/
BOOL fRestore; /*reserved*/
BOOL fIncUpdate;/*reserved*/
BYTE rgbReserved[16];/*reserved*/
} PAINTSTRUCT
Only the first three elements in the PAINTSTRUCT data structure are used by their application program. Windows reserves the others. The fErase element is a flag, which is TRUE if the background of the window has been redrawn, and FALSE if it has not been. The rcPaint element is a pointer to a rectangle that contains the part of the client area that will be repainted. This is called the invalid rectangle area of the window. The rcPaint data is of type RECT structure. The RECT structure defines a rectangle by its upper left and lower right corners, and is given in the windows.h header file as under:
typedef stru
ct tag RECT
int left;
int top;
int right;
int bottom;
} RECT;
BeginPaint FUNCTION
The BeginPaint function prepares the window's client area for painting. It is used to retrieve the device context handle for the window's client area when processing a WM_PAINT message. BeginPaint files in the data in the PAINTSTRUCT data structure.
Syntax:
hDC BeginPaint(HWND hWnd,LPAINTSTRUCT lpPaint);
hWnd refers to the handle of the window.
lpPaint refers to the pointer to a PAINTSTRUCT data structure that BeginPaint function will fill.
EndPaint FUNCTION
The EndPaint function ends the painting cycle started by the BeginPaint. This function is used at the end of the painting cycle to release the device context. The ReleaseDC is used to free a device context created by the GetDC function. EndPaint can be used to restore a caret which may have been hidden by a call to the BeginPaint function.
Syntax:
void EndPaint(HWND hwnd, LPPAINTSTRUCT lppaint);
hwnd refers to the handle to the window to be repainted.
lppaint is the pointer to the PAINTSTRUCT data structure that was used by the BeginPaint function.
The EndPaint function can be used as follows:
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
TextOut (hdc, 10, 20, "sample data", 15);
EndPaint(hwnd, &ps);
break;
ps is the variable of type PAINTSTRUCT.
hdc is the device context handle.
Hwnd is the window's handle.
Consider the following program that demonstrates the usage of the WM_PAINT message:
#include<windows.h>
#include "resource.h"
long FAR PASCAL WndProc(HWND, WORD, WORD, LONG)
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hprevinstance, LPSTR lpsz, int ncmdshow)
// code is same as the previous codes//
}// end of WinMain//
long FAR PASCAL WndProc(HWND hwnd, WORD wmessage, WORD wparam, LONG lparam)
The default settings for a device context show the characters in black and white, so one can change the color and character font by introducing new values into the device context before the characters are displayed.
The following table gives detail of all the possible device context settings:
Settings Required
Function To Be Used
Meaning
Character font
HGDIOBJ SelectObject(HDC hdc, HGDIOBJ hgd);
Sets the font used by TextOut and related text output functions
Character color
COLORREF SetTextColor(HDC hdc, COLORREF ncolor);
Sets the color of the text
Character background
COLORREF SetBkColor(HDC hdc, COLORREF color0;
Sets the background color that surrounds each character.
Background mode
Int SetBkMode(HDC hdc, int mode);
Checks if the painting of the text and graphics covers everything underneath (OPAQUE) or if the white areas are transparent.
Text alignment
UINT SetTextAlign(HDC hdc, UINT umode);
Checks how output characters are aligned.
Text spacing
Int SetTextCharacterExtra(HDC hdc,, intnchar);
Sets the amount of extra space between each character
Text justification
BOOL SetTextJustification(HDC hdc, int nbreak, int nbreak);
Justifies a string prior to output by TextOut function; used with GetTextExtent to determine the amount of space needed to make a character string exactly fill a space.
HGDIOBJ SelectObject(HDC hdc, HGDIOBJ hgd);
Sets the pen to draw lines and outlines of objects.
Sets the color palette to use if the output device supports more than the default 20 colors
CHARACTER SET INTERFACE
Character sets are translation tables that give meaning to a string of bytes in character strings. The interpretation of character data depends not only on its contents but on the selected character set as well. The various functions available in MFC are:
CharLower FUNCTION
This function performs an in-place conversion of the string or returns the conversion of a single character to lower case, based on the current locale. It returns a pointer to the converted data, if a string is passed or a single character converted.
Syntax:
LPTSTR CharLower(LPTSTR lpstring);
lpstring refers to a pointer to a zero terminated string to be converted to lower case or a single character stored in the LOWORD to be converted.
CharLowerBuff FUNCTION
This function converts a buffer of characters in place to lower case. It returns the number of bytes in the converted portion of the string.
Syntax:
DWORD CharLowerBuff(LPTSTR lpsz, DWORD dwbytes);
where dwbytes refers to the number of bytes to be inspected and converted.
CharUpper FUNCTION
This function converts a string or returns the conversion of a single character to uppercase based on the current locale. It returns a pointer to the converted data, if a string is passed or a single character is converted to uppercase.
Syntax:
LPTSTR CharUpper( LPTSTR lpstring);
CharUpperBuff FUNCTION
This function converts a buffer of characters in place to uppercase and returns the number of bytes in the converted portion of the string.
Syntax:
DWORD CharUpperbuff(LPTSTR lpstring, DWORD dw);
KEYBOARD MESSAGE PROCESSING
As already mentioned events in Windows occur when a particular option is clicked on. Alongwith this, Windows-based applications are designed to take care of keyboard messages also. So Windows has been designed to support various keyboards keeping in mind the feature of device-independence. When a key is pressed, Windows sends a WM_KEYDOWN message and WM_KEYUP message when a depressed key is released.
The program can determine which key was pressed by examining the wparam parameter that is sent with the keyboard messages. wparam encodes the virtual key code for the key pressed and released.
Virtual key codes is one of the ways Windows makes sure that a program written for one type of computer keyboard will function properly on another type. These key codes are defined in the windows.h file. No matter what type of hardware is used virtual key code remains the same.
Given below is a list of virtual codes of some of the keys:
KEYS VIRTUAL CODE
F1 VK_F1
CTRL VK_CTRL
0 VK_NUMPAD0
F3 VK_F3
TAB VK_TAB
SHIFT VK_SHIFT
F4 VK_F4
F2 VK_F2
The only key, which does not have a virtual key code, is Alt. The status of Alt key is passed with the lparam value in the WM_KEYDOWN or WM_KEYUP message.
Example:
long FAR PASCAL WndProc(HWND hwnd, WORD wmessage, WORD wparam, LONG lparam
static BOOL bshift= FALSE;
switch(wmessage)
case WM_KEYDOWN:
switch(wparam)
case 'a':
break;
case VK_TAB:
break;
case VK_SHIFT:
bshift=TRUE;
break;
case WM_KEYUP:
switch(wparam)
case VK_SHIFT:
bshift=False;
break;
break;
WM_CHAR MESSAGE
The WM_KEYUP and WM_KEYDOWN have certain limitations. For example, the letter 'A' has the same virtual key code whether it is in uppercase or lower case. Windows provides an easy solution to this problem. The TranslateMessage function is put into the program's message loop in the WinMain function, GetMessage is used to fetch the message data from Windows, and DispatchMessage is used to send the message to the WndProc function. When Translate Message detects a keyboard action that translates to an ANSI character it generates a WM_CHAR message. The wparam sent with the message is the ANSI character code. Function keys and other keys, which do not have an ANSI character equivalent do not generate the WM_CHAR message. You pick the keys pressed with WM_KEYDOWN or WM_KEYUP.
The code for TranslateMessage can be as follows (put in the program's message loop in the WinMain()):
while (GetMessage(&msg, NULL, 0, 0)
TranslateMessage(&msg);
DispatchMessage(&msg);
SOME MESSAGES GENERATED WHEN A KEY IS PRESSED
Key Pressed Message Generated
Any key WM_KEYDOWN
The ASCII code of letter key WM_CHAR
Any key is released WM_KEYUP
Any key is depressed while the Alt key is pressed WM_SYSKEYDOWN
While the Alt key is pressed , the ASCII code for the letter key is also pressed WM_SYSCHAR
Any key is released while the Alt key is released WM_SYSKEYUP
WM_KEYDOWN is processed for the function keys, the Ctrl key and other virtual keys that do not have ANSI equivalents. WM_CHAR is used for ANSI characters.
PROCESSING KEYSTROKE MESSAGES
To process keystroke messages add WM_KEYUP and WM_KEYDOWN cases to the Windows procedure. If the Home key or End key is pressed and if any other key is released, the message displayed in the following code appears.
case WM_KEYDOWN:
switch(wparam)
case VK_HOME:
MessageBox(hwnd, " home key pressed", "key down", MB_OK);
break;
case VK_END:
MessageBox(hwnd, " end key pressed", "key down", MB_OK);
There are two kinds of fonts that can be used in developing a window's program: stock fonts and logical fonts.
STOCK FONTS
Stock fonts are accessed with the GetStockObject function, which returns a handle (HFONT), to the stock object's data. This is the handle needed by SelectObject functions to select the object into a device context. The available stock fonts are:
ANSI_FIXED_FONT
ANSI_VAR_FONT
DEVICE_DEFAULT_FONT
OEM_FIXED_FONT
SYSTEM_FONT
SYSTEM_FIXED_FONT
Example:
HDC hdc;
HFONT hfont;
hdc=GetDC(hwnd);
hfont=GetStockObject(ANSI_FIXED_FONT);
SelectObject(hdc, hfont);
Using the GetStockObject function and SelectObject function in the same line can save a little space. Consider the following statement:
The default font for a video device context is the SYSTEM_FONT. This is a variable-pitch font, which means that the characters are not all of the same width. Fixed-pitch fonts use the same spacing between characters regardless of the output and variable fonts are easier to read and take up less space.
LOGICAL FONTS
Windows also comes with font files that define the characteristics of characters written in a number of different styles and sizes. To use font data in a program, obtain a handle to the font data and select it onto the device context. This is same as using a stock font except that one has to load data from a separate file and not from Windows using the GetStockObject function.
The CreateFont function is used in place of GetStockObject function to load the font and return a handle to the font data. CreateFont function does more than just load the font data into the memory. It can create new fonts by interpolating from the data in a font file. It can create new font sizes, create bold and underlined styles, and lets you rotate and distort the font's characters. These interpolated fonts are called logical fonts because they come from Windows font logic and not just from a font data file.
//The characters are measured in both height and width in logical units, with the default units of a device context, logical units are equal in pixels.//
CreateFont FUNCTION
This function creates a logical font that has specific characters.
Syntax:
HFONT CreateFont(
int nHeight /*logicak height of the font*/
int nWidth /*logical average character width*/
int nEscapement /*angle of escapement*/
int nOrientation /*base-line prientation angle*/
int fnWeight /*font weight*/
DWORD fdwItalic /*font weight*/
DWORD fdwItalic /*italic attribute flag*/
DWORD fdwUnderline /*uderline attribute flag*/
DWORD fdwStrikeOut /*strikeout attribute flag*/
DWORD fdwCharSet /*character set identifier*/
DWORD fdwOutputPrecision`/*output precision*/
DWORD fdwClipPrecision /*clipping precision*/
DWORD fdwQuality /*output quality*/
DWORD fdwPitchFamily /*pitch and family*/
LPCTSTR lpszFace /*pointer to typeface name string*/
Sample demonstration on usage of fonts
This program creates a logical font when processing the WM_CREATE message as the program starts running. The font is given a logical size of 36 and is built from the FF_ROMAN family of fonts. The handle to the font data is stored in the static variable hFont. When the user selects a font type using the program menu, the selected font is stored in the variable nFontChoice. Selecting a new font also results in calling InvalidateRect function, which causes the client area to be repainted and WM_PAINT message to be sent to the WndProc function.
The actual processing of the output is done through the WM-PAINT message. The currently selected font is selected into the device context before calling TextOut function to output the string. In case of stock fonts, the font data is accessed using the GetStockObject function. If the Roman font is selected the hfont handle is used to allow SelectObject function to select the logical font into the device.
# include <window.h>
# include "resource.h"
long FAR PASCAL WndProc(HWND, WORD, WORD, LONG)
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hprevinstance, LPSTR lpsz, int ncmdshow)
// code is same as the previous codes//
}// end of WinMain//
long FAR PASCAL WndProc(HWND hwnd, WORD wmessage, WORD wparam, LONG lparam)
Before elaborating on bitmaps it is necessary to define resources. Resources are data included in an application's .EXE file or in a library's .DLL file. When Windows loads a program into memory for execution, it usually leaves the resources on disk. Only when Windows needs a particular resource does it load the resource into memory. Most resources are read-only and marked as discardable. Windows uses several different types of resources:
Icons
Cursors
Bitmaps
Character strings
Menus
Keyboard accelerators
Dialog boxes
Fonts
User-defined resources
For each of these types resources are created using the appropriate tool such as the image editor or text editor.
WHAT IS A BITMAP
The term bitmap refers to an image formed by a pattern of bits. In Windows there are two kinds of bitmaps:
Device-dependent bitmap: The way bitmaps are arranged in the memory of this type depends on the intended output device. Because there is a close correlation between the bits in memory and the pixels on the screen, a memory bitmap is said to be device- independent.
Device-independent bitmap: This describes the appearance of an image rather than the way the image is represented internally by a particular display device. Because this external definition can be applied to any display device it is
referred to as device- independent.
CREATING AND LOADING A BITMAP FILE
Consider writing a program to display a bitmap. First, the bitmap should be created and an identifier associated with it. The bitmap file is then stored in the .rc file. If the reader is creating a menu such as Display and Quit then the resource will also contain a menu resource. Follow these steps to create a bitmap using a resource editor:
Create a sample project workspace with the name proj2.
Use the Insert option of the menu bar and select the Resource sub-option. From the screen Insert Resource that appears select the option Bitmap as shown in figure 3.4.
Press New option and another screen appears where tools are available for you to create a bitmap as follows:
After creation the bitmap should be saved. At this point the resource file contains the bitmap.
In the application's resource(.rc) file add a bitmap statement that defines the bitmap as an application resource:
mybitmap BITMAP FILE.BMP
Here mybitmap is the name of the bitmap; the file name FILE.BMP specifies the file that contains the bitmap.
In your application's resource file, load the bitmap by using the LoadBitmap function. This function takes the bitmaps resource name, loads the bitmap into the memory and returns handle of the bitmap.
HBITMAP hbit;
hbit= LoadBitmap(hinstance, "mybitmap");
CREATING AND FILLING A BLANK BITMAP
You can also create a bitmap (blank) by using the CreateBitmap function.
Select the bitmap into a memory device by using the SelectObject function.
Draw the bitmap image by using GDI output functions.
The following example creates a star-shaped bitmap by first making a bitmap that is compatible with the display and then filling the compatible bitmap by using the polygon function.
In the above function the GetDC retrieves a handle to the device context. Only then will the bitmap will be compatible with the screen. If you want the bitmap to be compatible with some other device then you have to use the CreateDC function to retrieve the handle of that device. The Create CCompatibleDC function creates the memory device context in which the image of the bitmap is drawn. Following this, the Create Compatiblebitmap function creates a blank bitmap, setting the size of the bitmap to 64 by 64 pixels.
After the bitmap has been created, the SelectObject function selects the bitmap into the memory device context and prepares it for drawing. The handle of the previously selected bitmap is saved in the variable hmbit. The patBlt function then clears the bitmap and sets all the pixels to white. The polygon function draws the star by using the endpoints specified in the array of point structure.
The BitBlt function then copies the bitmap from the memory device context on the screen. The SelectObject and DeleteDC functions restore the previous bitmap and delete the memory device context. Finally, the ReleaseDC function releases the device context.
DISPLAYING A BITMAP
Windows provides several ways to display a bitmap:
By using the BitBlt function to copy the bitmap from the memory device context to a screen.
Use the StretchBlt function to copy a stretched or compressed bitmap from a memory device context to a screen.
Use the CreatepatternBrush function to create a brush that incorporates the bitmap.
Display the bitmap as the menu item in a menu by replacing the original menu item text as defined in the .rc file with the bitmap.
One way to display the coordinates of the bitmap is by specifying the coordinates as 64, 64 etc. Another way to specify the width and height of the bitmap to be displayed is to retrieve the coordinates from the bitmap itself. You can do this by using the GetObject function which fills a specified structure with the dimensions of the given object.
The example given below shows how to retrieve the dimensions of a bitmap:
The next example copies the width and height of the bitmap to the bmwidth and bmheight members of the structure of the bitmap structure. You can use these values in BitBlt as follows:
hdcdest is the device context receiving the bitmap.
nx and ny are the logical x and y coordinates of the upper left corner of the destination rectangle.
nwidth and nheight are the width and height of the destination in logical units.
hdcarc is the device context of the source.
nxarc and nyarc are the logical x and y coordinates of the upper left corner of the source rectangle.
nwidtharc and nheightarc are the height and width of the source rectangle in the logical units.
VALUES MEANING
BLACKNESS All output is black.
DSTINVERT Destination bitmap is inverted.
MERGECOPY Uses the Boolean AND to combine the pattern and source bitmap.
MERGEPAINT Uses Boolean OR to combine the inverted source bitmap with the destination bitmap.
NOTSRCCOPY Copies the inverted source bitmap to the destination.
NOTSRCERASE Uses the Boolean OR to combine the destination and source, then inverts the combination.
PATCOPY Copies the pattern to the destination bitmap.
PATINVERT Uses Boolean XOR to combine the destination bitmap with the pattern.
PATPAINT Uses Boolean OR to combine the inverted source with the pattern and then uses the Boolean OR to combine the result of the previous operation with the destination.
SRCAND Uses Boolean AND to combine the source and destination bitmaps.
SRCCOPY Copies the source bitmaps to the destination.
SRTCERASE Uses Boolean AND to combine the inverted destination bitmap with the source bitmap.
SRCINVERT Uses Boolean XOR to combine the source and destination bitmaps.
SRCPAINT Uses Boolean OR to combine the source and destination bitmaps.
WHITENESS All output is white.
USING A BITMAP IN A PATTERN BRUSH
One can use a bitmap in a brush by creating a pattern brush. After creating the pattern brush, you can select the brush into a device context and use the PatBlt function to copy it on the screen or the rectangle, Ellipse. This way other drawing functions can use the brush to fill the interiors. The following example loads the bitmap and uses it to create a pattern brush.
hbitmap=LoadBitmap(hinstance, "checks");
hbrush=CreatePatternBrush(hbitmap);
You can also select the brush into the device context by using the SelectObject function:
holdbrush = SelectObject(hdc, hbrush);
After selecting the brush you can use PatBlt function to fill a specified area within the bitmap. For example, the following statement fills the upper-left corner of a window with the bitmap:
PatBlt (hdc, 0,0,100,100,PATCOPY);
You can also use a pattern brush as a Windows background brush. To do this simply assign the brush handle to the hbrBackground member of the window class structure as in the example given here:
ws.hbrBackground=CreatePatternBrush(hbitmap);
Once you have assigned the brush handle to hbrBackground, Windows uses the pattern brush whenever it erases the window's background. By using the function SetClassWord, the current background brush can also be changed.
SetClassWord(hwnd, GCW_HBRBACKGROUND, hbrush);
The above statement changes the background brush for all the Windows of this class. If you want to change only the background for one window then you must explicitly process the WM_ERASEBKGND messages that Windows receives. The following example demonstrates this:
The WM_ERASEBKGND message passes a handle of a device context in the wparam parameter. The SelectObject function selects the desired background brush into the device context. The GetClientRect function retrieves the area that needs to be created. The PatBlt function copies the pattern overwriting anything already in the update rectangle. The SelectObject function then restores the previous brush to the device context.
Whenever the application or the user moves the window in which a pattern brush has been or will be used, the application must align the pattern brushes to the new position by using the UnrealizeObject function. This function resets the origin of the brush or a logical palette. The HGDIOBG parameter identifies the handle of a brush or palette.
DELETING A BITMAP
A bitmap after being created and viewed needs to be deleted, since like any other resource it occupies the memory. So by deleting the bitmap we are actually releasing the memory. To delete a bitmap first remove it from any device context in which it is currently selected and then delete it by using the DeleteObject function.
Sample statements:
SelectObject(hmdc, holdbitmap);
DeleteObject(hbitmap);
The above example deletes the bitmap identified by the hbitmap variable after removing it as the currently selected bitmap in the memory device context identified by the hmdc variable.
Sample program to create a star shaped bitmap
BITMAP.C
#include<windows.h>
#include "resource.h"
# include <window.h>
# include "resource.h"
long FAR PASCAL WndProc(HWND, WORD, WORD, LONG)
int PASCAL WinMain(HINSTANCE hInsatnec, HINSTANCE hprevinstance, LPSTR lpsz, int ncmdshow)
// code is same as the previous codes//
}// end of WinMain//
long FAR PASCAL WndProc(HWND hwnd, WORD wmessage, WORD wparam, LONG lparam)
Windows provide a variety of drawing tools to use with the device contexts. It provides pens to draw lines, brushes to fill interiors and font to draw text. MFC provides graphic- oriented classes equivalent to drawing tools available in Windows.
The following list illustrates the available classes and their equivalent available Windows GDI handle:
Class Handle Purpose Default Value
CPen HPEN Used to draw the border of the region Solid black pen, 1 pixel wide
CBrush HBRUSH Used to fill a region Solid white brush
CFont HFONT Used to manipulate font characteristics System font
CBitmap HBITMAP Used to create and install bitmaps None
CPalette HPALETTE Used to manipulate palettes None
The following steps are typically followed when you need to make use of a graphic object for a drawing operation:
Define the graphic object. Initialize the object with the type-specific Create function such as CreatePen member function of the CPen class. Alternatively, initialize the object in the constructor.
Select the object into the current device context, saving the old graphic object that was used before.
When you have finished using the current graphic object select the old graphic object back into the device context to restore its state.
Ensure that the graphic object you created is deleted when you have finished using it.
There are two ways of creating a graphic object:
One-stage construction: construct and initialize the object in one-stage, within the constructor.
Two-stage construction: construct and initialize the object in two separate stages. The constructor creates the object and a separate initialization function initializes it.
DRAWING IN THE CLIENT AREA OF THE WINDOW
The CDC class is the base class from which other classes pertaining to the device contexts are derived. All drawing operations are performed in the client area of the application window. The class, which MFC provides to perform the drawing operations in the client area, is the CClientDc class. The CClientDC class is derived from the CDC class.
CClientDC CLASS
This class is used to manipulate the client area of the window associated with a device context. When an object of CWinApp class is created and instantiation of CFrameWnd class is done then a window is created. To draw the client area one needs to access the device context of the particular window that you wish to create. In the CFrameWnd class include the following statement:
CClientDc mydc(this);
The above statement creates the CClientDC object and initializes it at the time of construction with the this pointer. The statement lets you access the existing window's client area. The this pointer ensures that the object receives a handle of the currently active window's client area and no new client area is created.
The this pointer is a pointer implemented by the C++ programming language to keep track of the instance of the class currently in use. Evaluating the expression this pointer accesses member data. The this pointer is not modifiable hence assignments to this are not allowed.
DRAWING A RECTANGLE AND A LINE
In Windows drawing a figure is always done onto a device context. This is because Windows supports device-independent graphics. So when you draw a rectangle you first draw it onto a device context. It is then displayed on the actual device. First obtaining the current device context and then drawing onto it can access a device. To draw a rectangle you make a call to the CDC member function Rectangle. This function draws a rectangle using the GDI objects that are currently available in the device context.
Consider the following code snippet:
// function to draw a rectangle//
void DrawRect()
CClientDC mydc(this); //access the current device context//
Mydc.Rectangle(100, 100, 200, 200); // draw a rectangle (first parameter is the
x-coordinate of the upper-left corner of the rectangle,
the second one is the y-coordinate of the upper-left
corner of the rectangle, the third is the x-coordinate of
the lower-right corner of the rectangle, and the last one
gives the lower-right corner of the rectangle).
return;
The function draws the rectangle using the currently chosen pen in the device context.
Drawing a line is quite similar to drawing a rectangle. The only difference is the call to the MoveTo and LineTo member functions of the CDC class. Consider the following code snippet:
// function to draw a line//
void Drawline()
CClientDC mydc(this);
mydc.MoveToLine(100,100); //moves to the start position of drawing the line
mydc.LineTo(200,200); // draws a line from the current position to the start position
The function DrawLine draws a line from the point (100, 100) to the point (200, 200) on the screen. The MoveTo member function moves the cursor to the point (100, 100) from where it draws a line to the point (200, 200) using the LineTo function. It may be necessary to change the outline color of the drawing object. This can be done using the CPen class member function.
CPen CLASS MANIPULATION
This class encapsulates the Pen drawing tool. Construction of the CPen class can be done in two ways:
One-stage construction: the object is constructed by overloading the constructor. In other words the object attributes are set at the time of creation itself.
Two-stage construction: this has typically two steps:
A pointer is assigned to the CPen class and with the C++ pointer the CPen object is instantiated.
The CreatePen member function of the CPen class is invoked which then creates the pen of the required style.
To use the
pen to draw onto the device context, the pen that was created using any of the two methods is selected onto the device context with the CDC member functions SelectObject.
CDC MEMBER FUNCTION
SelectObject
This function selects an object into the device context. The CDC class provides five versions specialized for particular kinds of GDI objects -pens, brushes, fonts, bitmaps and regions.
The newly selected object replaces the previously selected object of the same type. This is because the five types of GDI objects are chosen into the device context at every point of time. When you select a new GDI object the old GDI object has to be stored safely. Once you have finished using the new GDI object the old GDI object is restored back to the device context as it was the default GDI object created by Windows. If it is not restored, it could result in the dreaded GENERAL PROTECTION FAULT ERROR.
The algorithm for drawing a rectangle with a user-defined pen is
Start the function
Access the current device context using the CClientDC function
Create a user-defined pen
Select the new pen into the device context and store the old pen
Draw the rectangle with the new pen
Restore the old pen to the device context
End the function
The following code illustrates the above algorithm:
// function to draw a rectangle with a red outline//
void drectangle()
CClientDC mydc(this);
CPen *oldpen , *npen;
npen = newCPen;
npen(CreatePen(PS_SOLID, 1, RGB(255, 0 ,0);
oldpen=mydc.SelectObject(oldpen);
mydc.Rectangle(100,100,200,200);
oldpen=mydc.SelectObject(oldpen);
The algorithm for drawing a rectangle with a user-defined brush is
Start the function
Access the current device context using the CClientDC class with the this pointer
Create a user-defined brush
Select the new brush into the device context and store the old brush
Draw the rectangle with the new brush
Restore the old brush to the device context
End the function
The following code snippet illustrates the usage of a user-defined brush:
void drect()
CClientDC mydc(this);
CBrush *oldbrush , *nbrush;
nbrush = new CBrush(RGB(255, 0 ,0); //creating a new red colored brush (red
color is 255)//
oldbrush=mydc.SelectObject(oldbrush);
mydc.Rectangle(100,100,200,200);
oldbrush=mydc.SelectObject(oldbrush);
@USER INTERFACE OBJECTS
User interface is an important aspect of any application framework. It is desirable to make the interface as user-friendly as possible. This is the reason why graphics interface has replaced textual interface. The aim of user interface design is the speed of use and reduction of errors. It also relies on the point and click method to select menu items, icons and objects on the screen. Typing is necessary only to input text on the screen. Menus, toolbars, status bars and dialog boxes are some user interface objects. A menu provides a selection from a list of items, a toolbar provides for selection by clicking on an icon rather than going through the menu system. The status bar is a mean of providing visual feedback to the user and dialog boxes handle user input. User interface objects are derived from classes of the MFC library.
Windows uses in-built logic to create buttons, scrollbars and listboxes, edit controls and screen backgrounds. All these objects have the following common purposes:
The object is rectangular
The area occupied by the object must be repainted when covered and uncovered by another object to maintain the illusion of permanence.
The object is an independent entity which can send and receive messages.
Generalization of characteristics into a common group of objects is called a window. Windows environment manages these Windows for a program. On creation of a window the program communicates with the individual elements by sending and receiving messages. Windows uses the term control to describe the predefined classes of Windows for common objects such as buttons and list boxes.
Windows provides every application with Windows controls. Since they are predefined Windows classes, they have to be created before invoking the RegisterClass function. Examples are scroll bars, edit controls, combo boxes, push buttons, dialog boxes, radio buttons and static controls.
TYPES OF CONTROL DESCRIPTION/FUNCTION
Push buttons Push buttons are a round cornered rectangles that comprises the text. The text is centered in the control. The control sends a message to the parent whenever the user chooses the control.
Radio buttons A radio button is a circular button that includes identifying text to the left or right. The circle is filled with a solid dot when it has been selected. It is the applications' responsibility to fill or clear the dot when the user turns the button on or off. Radio buttons of the same class should appear in the same group.
Static control Created from the Static Windows class when calling Create Window () these controls are usually used for titles and other text that the user does not directly manipulate. The usage of static control avoids the need for processing WM_PAINT messages, which is required for keeping the window.
Group box This is a rectangular box used to group buttons together. A caption can also be included in the upper left corner of the group box.
Edit text control This control lets the user enter the text from the keyboard.
Scroll bar Created from the SCROLLBAR Windows class when calling the CreateWindow(), this can be either in the form of separate controls or can be attached to the sides of the Windows.
ComboBox A combobox is a combination of two different types of controls. It combines a list box and a static control or an edit control. In addition to the edit control, the user is also provided with a list box containing the most probable choices.
List box This is rectangle containing a list of text strings. A user can browse through the list and then select one or more items. The list box sends a message to the parent Windows about the selected item(s).
Check box This is a rectangular button that can include text to the left or right of the button. The box is marked with a x when it is selected. It is the applications' responsibility to check and uncheck the box when the user turns the box on or off. Autocheck boxes also exist in which the act of checking or unchecking is carried out by Windows itself.
Dialog box A dialog box is a temporary Windows created by an application to retrieve user input. An application typically uses dialog boxes to prompt the user for additional information for commands. A dialog box contains one or more Windows child controls with which the user can enter text, choose options, or direct the action of the command. A dialog box itself does nothing. It merely exists as a frame to hold a group of related controls.
A dialog box comprises various child windows as illustrated below:
TYPES OF DIALOG BOXES
Modal Dialog Box: This is the most commonly used dialog box. When this type of dialog box is on the screen the user cannot switch to any other area of the same program. By default it limits the access to the other visible windows of the program that called the dialog box. However, the user can switch to other applications while the modal dialog box is being displayed. An example being the open window.
The following are the two types of modal dialog boxes:
System modal dialog box: Only the WS_SYSMODAL style in the dialog box template can create this dialog box. The dialog box can also be created using the SetSysModalWindow function. It takes over the entire Windows environment and does not allow any other application to receive messages or gain input until this dialog box disappears.
Application modal dialog box: In this type the user can still interact with the user interface objects of the other applications.
Modeless Dialog Box: This is rarely used. It is basically a pop-up window. It stays on the screen and is available for use at any time. This dialog box allows the user to switch between applications and to other Windows in the application that created the dialog box. It is created using the CreateDialog function. An example of this kind of dialog box is progress bars.
Both the modal and system modal dialog boxes can be created with the DialogBox function. As a result the entire window's messages are sent to the dialog box function while the dialog box is on the screen. The EndDialogBox function closes the dialog box. While creating a dialog box it is important for the programmer to decide upon which dialog box to make. The modality of the dialog box decides which actions in the application can be carried out when the dialog box is displayed.
A modal dialog box runs in the exclusive mode whereas a modeless dialog box runs in the non-exclusive mode. Despite the flexibility offered by modeless dialog boxes modal dialog boxes are used more often. This is because dialog boxes are mainly used as an extension of a menu command. Hence a dialog box typically provides data that an application needs to complete the command. With a modal dialog box the rest of the application's interface comes to a standstill till the user decides whether to proceed with the operation or terminate it.
Even though they are not extensively used, modeless dialog boxes have their own uses. For example, a modeless dialog box can go in search of a modal dialog box.
WORKING OF A DIALOG BOX
Design a dialog box using the dialog box editor. All the program's resources are saved in a single .rc file.
Each child Windows control within the dialog box is given an ID number, which is assigned a name in the header file.
Each dialog box requires a message processing function to be added to the program.
The dialog box message processing function is listed in the EXPORTS section of the program's module definition file (.def file).
The program activates the dialog box by calling the DialogBox function passing the dialog box name and the dialog box function address as parameters.
The dialog box stays on the screen until the EndDialog function is called from within the dialog box function. This closes the dialog box function.
Dialog boxes take control from the parent Windows when the DialogBox function is invoked. When a dialog box is visible one can switch to another program but not to the parent Windows. Clicking on a button in the dialog box destroys a dialog box. Internally, the dialog box executes the EndDialog function to destroy a dialog box Windows and returns control to the parent Windows.
CDialog CLASS
This is the base class from which dialog boxes are derived. For creating custom dialog boxes new classes are derived from the class. The header file afxdlgs.h has to be included to create a dialog box.
STATIC TEXT AND EDIT CONTROLS
Static Text Controls are primarily used for displaying the text. They can also be used to display icon images and rectangles. Edit Controls can range from a small rectangle for entering a single word or number to a Windows that occupies the parent window's client area. The key features of these controls are:
When text is typed the edit control automatically recognizes the backspace key, the delete key and the arrows keys for movement inside the edit control. A vertical line called the caret marks the current location in the edit control.
Blocks of text can be marked for selection with the help of the mouse.
The scroll bar types (WS_HSCROLL AND WS_VSCROLL) need to be added during the Create Window function call so as to allow the user to enter text within the edit controls.
Edit controls do not have the ability to format text with different fonts and character styles.
The program given below demonstrates the usage of static controls and edit controls when designing a Students Registration Form. A window with a menu option Student and Quit is created so that when the option Student is selected a dialog box comes on the screen. This prompts the user to enter the name, age, address and latest qualifications.
On entering the details the user has to press the OK push button to confirm the entries or has to cancel them. The user clicks on OK and a message box with the caption student details is displayed.
/*sample program to demonstrate the use of static text and edit controls by filling up a student registration form*/
#include<windows.h>
#include "resource.h"
#include<stdio.h>
#include<string.h>
long FAR PASCAL WndProc(HWND hwnd, WORD wmessage, WORD wparam, LONG lparam);
long FAR PASCAL stud(HWND hdlg, WORD wmessage, WORD wparam, LONG lpar
int age;
char str1[30], str[100], str3[40], str4[40];
HANDLE hinstance;
int PASCAL WinMain(HANDLE hinst, HANDLE hprevinstance,LPSTR lpsz, int ncmd)
long FAR PASCAL stud(HWND hdlg, WORD wmessage, WORD wparam, LONG lparam);
BOOL err;
switch(wmessage)
caseWM_COMMAND:
switch (wparam)
case ID_NAME2:
GetDlgitemText(hdlg, ID_NAME2, str1, 20);
strcpy(str,str1);
break;
case ID_AGE2:
age1= GetDlgItemInt(hdlg, ID_AGE2, &err, TRUE);
break;
case ID_ADDR2:
GetDlgItemInt(hdlg, ID_ADDR2, str3, 20);
break;
case ID_DET2:
GetDlgItemText(hdlg, ID_DET2, str4, 20);
break;
case IDOK:
sprintf(str, " %s, aged %d living at %s has the following acdemic details %s,"str, age1, str3, str4);
MessageBox(hdlg, str, "Student Details", 0);
EndDialog(hdlg, TRUE);
return 1;
case IDCANCEL:
EndDialog(hdlg, FALSE);
return 1;
}/*end of switch*/
break;
}/*end of switch*/
return 0;
}/*end of stud*/
The WinMain function creates a Window with a caption Student Registration Form. The DialogBox function creates a modal Dialog box template resource. The DialogBox function does not return control until the specified callback function terminate the modal dialog box by calling the EndDialog function. The MAKEINTRESOURCE macro returns an integer value, which is the resource identifier for the dialog box template. The default resource identifier is IDD_DIALOG1. ID_STUD1 is the identifier associated with the Enter Details menu item.
Stud() a user-defined procedure, which is invoked at this point creates a dialog box with all the necessary text and edit controls. The EndDialog function destroys a modal dialog box causing the system to end all the processing for the dialog box. ID_QUIT is the identifier associated with the Quit menu item. On choosing Quit the user can exit from the registration form.
The WM_COMMAND message is sent when the user selects a command item or when a control sends a notification message to its parent window. The various edit controls processed by WM_COMMAND are ID_NAME2, ID_AGE2, ID_ADDR2 & ID_DET2, which allow the entry of the students name, age, address and qualification respectively. The GetDlgItemText and GetDlgItemInt functions retrieve text and numeric contents associated with control in the dialog box.
DialogBox FUNCTION
This function creates a modal dialog box from a dialog box resource template. This function does not return control until the specified callback function terminates the modal dialog box by calling the EndDialogBox function. If the function succeeds, the return value is the result parameter that processes messages for the dialog box.
Syntax:
int DialogBox( HANDLE hinstance, LPCTSTR lptemp, HWND hwndparent, DLGPROC dlg);
hinstance identifies an instance of the module whose executable file contains the dialog box template.
lptemp identifies the dialog box template. The parameter is either a pointer to a null terminated string that specifies the name of the dialog box tempalte or a n integer value that specifies the resource identifier of the dialog box template.
hwndparent identifies the window that owns the dialog box.
dlg is a pointer to the dialog box procedure which processes the message for the dialog box.
EndDialog FUNCTION
This function destroys a modal dialog box. If the function succeeds, the return value is TRUE else the value is FALSE.
Syntax:
BOOL EndDialogBox(HWND hdlg, int nresult)
hdlg identifies the dialog box to be destroyed.
nresult specifies the value to be returned to the application from the function that created the dialog box.
GetDlgItemText FUNCTION
This function retrieves the title or text associated with a control in the dialog box. If the function succeeds the return value specifies the number of characters copied to the buffer not including the terminating null character else the return value is zero.
Syntax:
UINT GetDlgItemText( HWND hdlg, int hdlgitem, LPTSTR lpstring, int nmaxcount);
hdlg identifies the dialog box that contains the control.
hdlgitem specifies the identifier of the control with the text.
lpstring points to the buffer to receive the title or the text.
nmaxcount specifies the maximum length in characters of the string to be copied to the buffer pointing to the lpstring. If the string exceeds the limit the string is truncated.
GetDlgItemInt FUNCTION
This function translates the text of a specified control in a dialog box into an integer value. If the function succeeds the return value pointed to by lptranslated is set to TRUE and the return value is the translated value of the control text. Otherwise the value pointed to is FALSE.
Syntax:
GetDlgItemInt (HWND hdlg, int hdlgitem, BOOL *lptranslated, BOOL bsign);
hdlg identifies the dialog box that contains the control.
hdlgitem specifies the identifier of the control with the text to be translated.
*lptranslated points to a Boolean variable that retrieves a function success or failure value.
bsign signifies whether the function should examine the text for a minus sign at the beginning and returns a signed integer value if finds one. TRUE specifies that this should be done and FALSE specifies it should not be done.
CREATING BUTTON CONTROLS
Consider the following program illustrating the usage of various controls like radio buttons, check boxes etc.
In this program when the user clicks on the student menu item it activates a dialog box which typically consists of various control buttons. The edit control buttons waits for the user to enter the data. The group box course is made of two radio buttons and the group box facilities availed is made of two check boxes, library and installments. These buttons prompt the user to make a selection. The two push buttons OK and Cancel confirms the entries and cancels them respectively.
At this point if the user selects OK a message box with a caption Student Details is displayed
//The code for the above output is as follows//
#include<windows.h>
#include "resource.h"
#include<stdio.h>
#include<string.h>
long FAR PASCAL WndProc(HWND hwnd, WORD wmessage, WORD wparam, LONG lparam);
long FAR PASCAL stud(HWND hdlg, WORD wmessage, WORD wparam, LONG lparam);
long FAR PASCAL stud(HWND hdlg, WORD wmessage, WORD wparam, LONG lparam);
switch(wmessage)
case WM_COMMAND:
switch (wparam)
case ID_NAME2:
GetDlgitemText(hdlg, ID_NAME2, str1, 20);
strcpy(str,str1);
break;
case ID_CP:
flag= 'C';
strcpy(str2, " Career");
break;
case ID_NCP:
flag= 'N';
strcpy(str2, "Non Career");
break;
case ID_INS:
check1= 'y';
if (check1 == 'y')
strcpy(str3, "\n Installments");
check= 'n';
break;
case ID_LIB:
check2= 'y';
if (check2 == 'y')
strcat(str3, "\n DromeMembership");
check= 'n';
break;
case IDOK:
sprintf(str, " %s, is enrolled for a % and has the following facilities:"str1, str2);
sprintf(str, "%s %s ", str, str3);
MessageBox(hdlg, str, "Student Details", 0);
EndDialog(hdlg, TRUE);
return 1;
case IDCANCEL:
EndDialog(hdlg, FALSE);
return 1;
}/*end of switch*/
break;
}/*end of switch*/
return 0;
}/*end of stud*/
ID_STUD1 is the identifier associated with the Enter Details menu item.
ID_QUIT is the identifier associated with the Quit menu item.
ID_NAME2 is the identifier associated with the edit control that prompts the user to enter the students' name.
ID_CP is the identifier associated with the radio button Career.
ID_NCP is the identifier associated with the radio button Non-Career
ID_INS is the identifier associated with the check box Installment.
ID_LIB is the identifier associated with the check box Library.
IDOK & IDCANCEL is the identifier associated with the OK and Cancel push buttons.
CREATING A LIST BOX
A list box is a control Window that contains a list of items from which the user can choose.
Consider the above list box. If it is to be created in a Window the code for the creation of Window with a list box remains the same except for some of the commands.
The following code illustrates the creation of the above Window:
//* sample program to demonstrate the use of list boxes*//
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include "resource.h"
#define MAXTRAV 10
#define MAXLEN 80
#define MAXNUM 10
long FAR PASCAL WndProc(HWND hwnd, WORD wmessage, WORD wparam, LONG lparam);
long FAR PASCAL stud(HWND hdlg, WORD wmessage, WORD wparam, LONG lparam);
sprintf(str, " %s, is travelling to %s and has the following payment odes:"str1, str2);
sprintf(str, "%s %s", str, str3);
MessageBox(hdlg, str, "travel Details", 0);
EndDialog(hdlg, TRUE);
return 1;
case IDCANCEL:
EndDialog(hdlg, FALSE);
return 1;
}/*end of switch*/
break;
}/*end of switch*/
return 0;
}/*end of stud*/
trav is an array that stores the name of places that are later transferred to a list box. The WinMain function creates a Window with a caption Travel Details. ID_TRAV# is the identifier associated with the Travel menu item. On selecting this a dialog box is displayed which has the identifier ID_TARV2. ID_QUIT is the identifier associated with the Travel menu item, which allows you to exit from the application. The WM_INITDIALOG message is sent to the dialog box which is displayed. Dialog box procedures typically use this message to initialize controls and carry out other initialization tasks that affect the appearance of the dialog box. The SendDlgItemMessage function sends a message to the specified control in the dialog box.
An application sends a LB_STRING message to add a string to a list box. If the list does not have a LBS_SORT style then the string is added to the end of the list. Otherwise it is inserted in the list which is then sorted. An application sends a LB_SETCURSEL message to select a string and scroll it into view, if necessary. When the new string is a selected the list box removes the highlight from the previously selected string. ID_NAME2 is the identifier associated with edit control.
ID_LIST1 is the identifier associated with the list box, ID_CASH is the identifier associated with the radio button Cash and ID_CHQ is associated with the radio button Cheque. IDOK and IDCANCEL are associated with the OK and Cancel push buttons. travel1() is user-defined and describes the various controls in the dialog box.
SendDlgItemMessage FUNCTION
This function sends a message to the specified control in the dialog box.
Syntax:
LONG SendDlgItemMessage( HWND hdlg, int ndlgitem, UINT msg, WPARAM wparam, LPARAM lparam)
hdlg identifies the dialog box that contains the control.
ndlgitem specifies the identifier of the control that receives the message.
The user can minimize any application by clicking on its Minimize button. This causes the application's Window to shrink to an icon, which appears with other icons in the upper left corner of the Desktop. An icon indicates that an application is running but is occupying very little space. Windows provides default icons for programs that do not have their own icons.
An icon is a resource. Unlike other resources, an icon is not stored as text script within the resource file. Since an icon consists of graphics information an icon resource is placed in a separate binary file with .ico extension. The binary file is then referenced in the .rc file. The icon resource definition statement specifies a bitmap that defines the shape of an icon to be used for a given application.
STEPS TO CREATE AN ICON
Choose Resource option from the Insert menu.
The Insert resource dialog box gets activated. From the dialog box choose the option Icon as shown in the following figure:
Click on the OK push button. This gives a screen display where the user
can draw the icon. The following figure displays that screen alongwith the drawn icon:
Save the icon in the resource script with the extension .rc.
All the resources that you create have default identifiers attached to them. An icon has a default identifier called ID_ICON1. A sample program given here illustrates the creation of an icon. This icon will allow you to move, size, minimize, maximize and close an application.
When the user clicks on the icon in the caption bar it displays the following window.
/*Sample program to create an icon */
#define STRICT
#include<windows.h>
#include"resource.h"
long FAR PASCAL WndProc(HWND hwnd, WORD wmessage, WORD wparam, LONG lparam);
int PASCAL WinMain(HANDLE hinst, HANDLE hprevinstance,LPSTR lpsz, int ncmd)
The WinMain creates a window with a caption Icon Creation. LoadIcon function loads the specified icon resource from the executable (.exe) file associated with an application instance. The resource identifier of the icon is ID_MYICON and is used with MAKEINTREESOURCE to retrieve the integer value for compatibility with Win32 functions. Windows allows a programmer to define a variable called STRICT which switches on a rigorous level of variable type checking. When STRICT is defined the arguments to all Windows functions must be of correct derived type. Specific handle types are defined so as to be mutually exclusive. Without STRICT, all handles are defined as integers so the compiler does not prevent you from using one type of handle when another type is expected.
LoadIcon FUNCTION
This function loads the specified icon resource from the executable file associated with an application instance. If the function succeeds it returns a handle to the new icons.
hinstance identifies an instance of the module whose executable file contains the icon to be loaded. This parameter must be NULL when a standard icon is to be loaded.
lpiconname points to a null terminated string that contains the name of the icon resource to be loaded. The MAKEINTRESOURCE macro is used for creating the value of resource identifier.
DIALOG BOX REVISITED
Given below is list of MFC classes from where the various controls are derived :
Control MFC class
Push button, Check box, Radio button CButton
Combo box CCombobox
Edit Control CEdit
List Box CListBox
Scroll Bar CScrollBar
CREATING A DIALOG BOX
Click on the Insert menu item and then click on Resource. On doing so the following screen is displayed:
Select Dialog control and press on OK.
A screen as shown in the following figure and appears and alongwith it also displayed is a window of different controls.
After creating the dialog box it is important to save it by choosing the Save As option from the File menu option.
COMPILING A DIALOG BOX
To make the dialog box template compatible with Windows, the dialog box must be compiled using the resource compiler. The compiled dialog box definition becomes part of the programs resourse data and is added to the finished program (.exe file). When a dialog box is to be displayed it is loaded into the memory, Windows then decodes the resource data and creates a dialog box with all its child controls from the information present in the resource data.
DIALOG BOX MESSAGES
With dialog box there are two types of messages that needs to be concentrated upon-WM_INITDIALOG and WM_COMMAND. The WM_INITDIALOG message is sent to trigger the initialization of a dialog box. The receipt of these messages indicates that all the controls in the dialog box are ready to be initialized. This is done by overriding the OnInitDialog member function, which is called in response to the WM_INITDIALOG message. The function must contain the code for initializing the controls in the dialog box.
The WM_COMMAND message is sent by the controls in the dialog box. Whenever a control is interacted with, this message is sent alongwith the ID of the control which identifies the object that has sent the message. Be careful not to overlap the IDs between menu items and dialog controls.
Creating a dialog box layout
The first step in creating a dialog box is to create a layout for it. The dialog box layout identifies the controls to be included in the dialog box. The layout describes the type and location of the controls in the dialog box. Layouts needs to be created for both modal and non-modal dialog boxes. They are stored as dialog resources. The dialog editor of the MDS allows you to define the contents of the dialog box. It reads the dialog layout and creates a dialog frame and the desired controls. The properties of the control are set in the resource editor.
Creating CDialog derived class
The next step is to create a CDialog object to handle the messages of the dialog box. This is done by deriving your own class from the CDialog class as illustrated in the following program:
class mydialog: public CDialog
This helps in the initialization and custom-handling of dialog control notifications.
CREATING DIALOG BOX WINDOWS
With the dialog box templates and the code for creating a dialog box in place you need to write the code for the dialog box to be displayed on the screen. All the controls defined in the dialog will appear with minimum functionalities. The amount of code you need to write depends on the desired amount of initialization and coordination between the activities of the different controls in the dialog box.
Creating a modal dialog box window
The creation of a modal dialog box is a two-step process. The first step is to create the CDialog class object and the second is to initialize the object. This may be done by creating an instance of the CDialog Class and passing the dialog template as a parameter as shown in the code below:
class mydialog: public CDialog
public:
mydialog(): Cdialog(MY_DIALOG) {} // MY_DIALOG is the dialog resource
Creating a dialog box is an involved process and is done by using the DoModal member function. This function creates a dialog box and displays it. Apart from this, it creates a message loop that will handle the messages dispatched by the various controls in the dialog box. The loop will be active until you click on the OK or Cancel buttons. The two basic return values from the DoModal member functions are IDOK and IDCANCEl. Since a modal dialog box runs in an exclusive mode, the dialog box owns the message loop and hence all the other user interfaces are disabled.
The following code segment creates a modal dialog box when the user selects the menu option identified by ID_SAMP. The code makes use of the derived class mydialog defined earlier:
class mywindow:public CFrameWnd
protected:
public:
mywindow();
void file();
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(mywindow, CFrameWnd)
ON_COMMAND(ID_SAMP, file)
END_MESSAGE_MAP()
mywindow::file()
mydialog sample;
sample.Domodal();
Creating a modal dialog box completes only half the assignment. The remaining half lies in the dismissal process of the dialog box. As a part of this process the dialog needs to inform the user whether the dialog was dismissed using the OK or the Cancel button. The control notification handlers handle this.
DISMISSING A MODAL DIALOG BOX WINDOW
All values in the dialog box are to be ignored if the dialog box is dismissed using the Cancel push button. The type of processing at the time of dismissal is determined by the return value of the DoModal function. IDOK is the return value when the dialog box is dismissed using the OK button and IDCANCEL is returned when the Cancel push button is pressed. The former conveys that value in the dialog box is to be considered and in the latter case the values are to be ignored. The OK button has its default handler OnOk. This can be overridden to incorporate customized processing at the time of dismissing the dialog box.
class mydialog: public CDialog
void OnOK()
EndDialog(IDOK);
The EndDialog member function receives the value to be returned to the caller of the DoModal member function.
Creating a modeless dialog box
The main difference between a modal and a modeless dialog box lies in the way they are created. The creation of a modeless dialog box is a two-way process. The first step involves creating the CDialogBox object and the second step is to initialize it. Since the dialog does not runs in an exclusive mode the object cannot be created locally. To handle this constraint, define a pointer to the CDialog derived class in the CFrameWnd class as shown in the segment below:
class mywindow: public CFrameWnd
protected:
mydialog*dlgmodeless;
public:
Mywindow();
void file();
The dialog box is then created using the Create member function as follows
void mywindow::file()
dlgmodeless= new mydialog; //memory allotment
dlgmodeless(Create(mydialog:: MY_DIALOG, this);
By virtue of being a modeless dialog box selecting a menu item is possible even after the dialog box has been created. Therefore, the next time the menu item is selected the existing dialog box should be activated rather than created again. This can be done by calling the SetActiveWindow member function of the CWnd class provided the Dialog box already exists. To check if a valid Window is attached to the dialog object, the Windows API IsWindow can be used as shown in the code below:
void mywindow::file()
if(dlgmodeless&&:IsWindow(dlgmodeless( hwnd)
dlgmodeless(SetActiveWindow());
dlgmodeless=new mydialog();
if(!::IsWindow(dlgmodeless(hwnd))
dlgmodeless(Create(MY_DIALOG, this));
dlgmodeless(ShowWindow(SW_SHOWNORMAL)); //activates and displays the
window to its original size
dlgmodeless(SetFocus()); //claims the input focus that directs keyboard input to this
Window
The assumption in the code is that the pointer dlgmodeless is set to NULL before this code is encountered for the first time. Initializing the pointer to NULL as in the code below can satisfy the assumption:
mywindow::mywindow()
dlgmodeless=NULL;
DISMISSING A MODELESS DIALOG BOX
This is quite a simple process. The dialog box being a regular window can be destroyed using the DestroyWindow member function of the CWnd class. The function returns a zero value if thewindow is not destroyed. Otherwise it returns a non-zero value. When a modeless dialog box is destroyed all the controls and the child windows are destroyed. Whether a dialog box exists or not can be checked by calling the IsWindow() function of the Win32 APU from any part of the program.
HANDLING THE DIALOG INITIALIZATION
Unless the initial values are given to controls, all controls in the dialog box will be empty. After the dialog box and controls are created, and before the appearance of the dialog box on the screen, the OnInitDialog member function is called in response to the WM_INITDIALOG message. One can override this function to initialize the dialog box's controls so that the initial text for an edit box radio button and the default check box can be set. The function is called during the DoModal call for a dialog box and the Create call for a modeless dialog box.
Apart from initializing the various controls, getting the pointers to the controls for both the dialog boxes is also part of the initialization process. In the following code segment WM_INITDIALOG message's default handler OnInitDialog has been overridden to initialize the controls of the dialog box mydialog.
The SetDlgItemText sets the initial text for controls such as the edit boxes. The CheckRadioButton selects the given radio button in a group and clears all the other radio buttons. The first two parameters in this function indicate the range of buttons and the third parameter indicates the button to be selected. The GetDlgItem member function retrieves a pointer to the specified control or child window in a dialog box or any wwWindow. This can be used for further manipulation of the dialog control. The SetCheck member function sets or resets the state of a radio button or check boxes. The parameter to the function is TRUE or FALSE which indicates whether control has to be selected or deselected.
The following code illustrates how a check box can be selected using the SetCheck member function. The same holds good for radio buttons as well.
class mydialog: public CDialog
public:
BOOL OnInitDialog()
CButton *radiobutton;
Radiobutton=(CButton *)GetDlgItem(IDC_RADIO1);
radiobutton(SetCheck(TRUE);
This member function has no effect on the push buttons.
HANDLING CONTROL NOTIFICATIONS
The last step in the creation of a dialog box is to handle the various control notifications. Just like the menu items dialog controls end the WM_COMMAND messages so can control notification be handled using the message maps. Whenever a user interacts with a control in a dialog box, control sends a notification message to the parent window. The notification sent depends on the type of control. For example, on choosing the OK or Cancel push buttons the window sends the BN_CLICKED control notification message to the dialog object with the ID of the button being either IDOK or IDCANCEl.
Consider the following code segment:
class mydialog: public CDialog
public:
void OnOK();
char string[50];
GetDlgItemText(IDC_EDIT!, string, 20);
MessageBox(string);
EndDialog(IDOK) ; // the parameter to this function is the return value of the
DoModal ()//
The GetDlgItemText member function retrieves the line or text associated with a control in the dialog box. The GetDlgItemInt function is used for edit boxes that accept the integer value; this retrieves the text from the control and translates it to an integer. This is done by removing any extra spaces encountered at the beginning and converting them into decimal digits. It stops the translation once it reaches the end of the text or when it encounters any non-digit characters. It also checks for the minus sign at the beginning of the text and translates the text into signed numbers. Otherwise it creates an unsigned value.
The following program incorporates all that you have learned in creating a dialog box:
#include<afxwin.h>
#include<afxdlgs.h>
#include<afxext.h>
#include"resource.h"
class mydialog: public CDialog
public:
mydialog(): Cdialog(MY_DIALOG) {} // MY_DIALOG is the dialog resource
A combo box consists of a list box combined with either a static control or an edit control. The list box portion of the control can be displayed at all times, or it may only drop-down when the user selects the drop-down arrow next to the control.
The following code segment illustrates the implementation of a combo box in a dialog box:
CFileDialog Lets the user select a filename to open or save a file
CFindReplace Lets the user initiate a find or replace operation in a text file
CFontDialog Lets the user specify the font
CPrintDialog Lets the user specify information for a print job
CREATING THE FILE DIALOG BOX
To use a CFileDialog object it is necessary to create an object using the constructor provided with the CFileDialog class. This constructor can be overloaded changing the first parameter to the constructor. If the parameter is 1, the File-> SaveAs dialog box is created. Whereas if the parameter is 0 then the File->Open dialog box is created. After the dialog box is created the values of the states of the dialog box controls can be modified or set by setting the value in the mfn structure. This structure is of the type OPENFILENAME. Once the dialog box has been initialized, the DoModal member function is called to display the dialog box and allow the user to enter the file and the path name. The DoModal function returns whether the user has selected the OK (IDOK) or the Cancel (IDCANCEL) button.
The destruction of this window is handled automatically and there is no need to call the EndDialog function separately. To allow the user to select multiple files you must set the OFN_ALLOWMULTISELECT flag before calling the DoModal, but only after constructing the CFileDialog. You need to supply your own filename buffer to accommodate the list of multiple filenames. This is done by replacing the mofn.lpstrfile with a pointer to the buffer allocated by you. This is done after constructing the CFileDialog.
The sample code given below illustrates the creation of a file dialog box:
ptr= "All Files (*.*)|*.*|C++ file(*.cpp)|*.cpp|";
CfileDialog d( 0, 0, "*.*" 0, ptr); //1 -> open file, 0-> save as
if(d.DoModal() == 1)
path = d.GetPathName(); // member of CfileDialog
MessageBox(path);
void mywindow:: exit()
PostQuitMesaseg(0);
class myapp: public CwinApp
public:
BOOL InitInstance()
mywindow *b;
b=new mywindow;
b->ShowWindow(SW_SHOWMAXIMIZED);
return TRUE;
BOOL ExitInstance()
// :: MessageBox(0, "Exit","!",0);
return TRUE;
myapp myapplicationobject;
The first parameter to the constructor of the CFileDialog class indicates whether a File Open or a Save as dialog box has to be created. The second parameter is the default filename extension to be included. If this parameter is NULL no filename extension is included. The third parameter is the initial value that should appear in the filename edit box. The fourth parameter is a combination of one or more flags that allows you to customize the dialog box. The fifth parameter is a pointer to a string that specifies the filters you can apply to the file. If you specify file filters, only selected files will appear in the files list box.
CControlBar CLASS
A number of classes are derived from the CControlBar class. They provide interesting implementations of this class. They are:
CStatusBar: a status bar broken into status bar panes, which contain text.
CToolBar: a toolbar, which displays the bitmap buttons aligned in a row.
CDialogBar: a toolbar like frame containing standard Windows controls (created from a dialog template resource).
CDockBar: a generalized docking area for other CControlBar derived objects.
CControlBar derived classes are used to implement the status bar, toolbar and dock bar. All control objects are child windows of a parent frame window. They are usually added as siblings to the client area of the frame. The CControlBar provides the standard implementation for:
Alignment of control at the top, bottom, or either side of the frame.
Allocation of control item arrays.
Support for the implementation of the derived classes.
Control bar objects are usually embedded as members of the CFrameWnd derived class and are to be cleaned up when window objects are constructed or destroyed.
CControlBar layout algorithm.
The frame window sends a WM_SIZEPARENT message to all the control bar objects. Alongwith this message, a pointer to the parent's client rectangle is also passed. Control bar objects use this information to position themselves thereby reducing the size of the parent-client area. An important point to note here is that all the controls are updated through messages. User Interface Command Handlers handle these messages.
CStatusBar CLASS
This is the base class from which status bars are derived. It provides functions to manipulate the status bar. It is a derived class of the CControlBar class. The status bar is a window that contains a row of sections called panes and gives visual feedback to the user. Hence, these panes cannot be used to accept the input or generate command messages; they are only used to display information usually in the form of text strings.
The status bar is normally placed at the bottom of the screen. Text can be placed in any pane of the status bar using the manipulation functions to place the text in a particular pane. Status bar panes are classified into two types-message line panes (displays menu and toolbar prompts or messages) and status indicator panes (used to display the status of the keyboard controls such as CapsLock, NumLock, etc.). Indicator panes are managed by the application framework. The left most pane on the status bar is known as the Stretchy Pane (generally used for message line pane) and this pane expands to fit the length of the status bar after space has been allocated to other panes.
The colors used for the status bars are also consistent with the recommendation of the Windows Interface Application Design Guide. These colors are not hard coded and are changed dynamically in response to user customization via the control panel. (The header file <afxext.h> must be included for creating a status bar.)
Item Windows COLOR value Default Color
Status bar background COLOR_BTNFACE RGB(192, 192, 192)
Status bar text COLOR_BTNTEXT RGB(000, 000, 000 )
Status bar top/left edges COLOR_BTNHIGHLIGHT RGB(255, 255, 255)
Status bar bottom/right edges COLOR_BTNSHADOW RGB(128, 128, 128)
Colors used in the status bars
To use the status bar to display application specific data the standard status bar must be overridden. This can be done by defining the status bar using a static array of indicators. This array should contain the command IDs to be associated with every pane of the status bar. The position of an element within the array determines its position on the status bar. The pane of the status bar is numbered 0, 1, 2
starting from the left most pane.
Given below is a sample definition of a static array of indicators:
The above code defines a status bar with five panes; the first being the stretchy pane is given the NULL ID temporarily. The fifth pane is the additional pane assigned NULL ID.
CREATING THE STATUS BAR
The CStatusBar is the base class for the status bar object. Only after the static array containing the IDs for the status bar panes has been defined can the CStatusBar object be created. Given below is the sample code:
class myWindow: public CFrameWnd
protected:
CStatusBar mystatus;
public:
mywindow();
After creating the CStatusBar object the Create member function should be called to create the status bar and its pane windows. The SetIndicators function must then be called to configure the status bar as per the contents of the static array. Configuration is done by establishing a relationship between the status bar panes and the IDs contained in the array. The following code segment illustrates this fact:
Mywindow: mywindow()
mystatus.Create(this);
mystatus.SetIndicators(status, sizeof(status));
After setting the indicators for the panes of the status bar every pane needs to be given an ID. This can be done by using the SetPaneInfo member function. Standard panes such as CAPSLOCK, NUMLOCK, SCROLLLOCK need not be given an ID. Consider the following code segment:
mywindow::mywindow()
SetPaneInfo(0, ID_MESSAGEPANE,SBPS_POPOUT, 480);
SetPaneInfo(4,ID_DUMMY, SBPS_STRETCH, 50);
The SetPaneInfo member function not only gives a new ID to a pane but also defines the style and the width for the specified pane.
Status bar styles
Style Description
SBPS_NOBORDERS No 3-D border around the pane.
SBPS_POPOUT Reverse border so that text pops out.
SBPS_DISABLED Does not draw text.
SBPS_STRETCH Stretch pane to fill unused space. Only one pane per status bar can have this style.
SBPS_NORMAL No stretch, borders or pop-out.
The ID given to a pane other than the standard indicator pane has to be defined in the string table using the string table resource editor. MDS does not have a special resource editor to edit the status bar. Hence, the creation of the status bar is a slightly more complicated process as compared to any other resource. After displaying the status bar the next step is to set the text for the individual panes of the status bar.
SETTING PANE TEXT
This is done by two methods-the first method uses the SetPaneText member function and the second uses the Update Command Handler. The first method writes the text to the pane immediately whereas the second updates the pane at leisure. This function returns a non-zero value if successful. Otherwise it returns a zero. Text can be written to any text pane using the SetPaneText member function as shown in the code below:
Mywindow:: mywindow()
mystatus.SetPaneText(0, "status bar"), TRUE);
Another member function, which handles the text, is the SetWindowText of the CWnd class. However, this function can only be used to set the text of the pane 0-the stretchy pane.
If it is not necessary to display the pane text immediately, the SetText member function of the CCmdUI class can be used. This function is used in the Update Command Handler of the status bar. You have to be careful when using this function since the pane does not resize itself. The following entry has to be made in the command handler for this:
void OnUpdateMessage(CCmdUi *cmd)
//. To activate the status bar pane
cmd->Enable(TRUE);
// to display the text in the pane
cmd->SetText(" Good Morning");
The message map entry for the above command handler will be:
The following is a summary of the steps involved in the creation of the status bar:
Define the static array containing the IDs for the various panes of the status bar.
Construct the CStatusBar object
Call the Create and SetIndicators member functions to create the status bar Window and its panes and associate a string ID to each indicator.
Set the style for the panes using the SetPaneInfo member function. Styles need not be defined for the standard indicator panes
Set the text for the panes using the SetPaneText member functions. The SetWindowText member function of the CWnd class can be used to set the text for pane 0 only.
All the above codes are combined into a single program as shown here:
The toolbar is a wwindow consisting of rows of command buttons. The buttons on the toolbar are represented as icons, which are pictorial representations of an object or a concept. Clicking on one of the buttons issues a pre-defined command. Buttons on the toolbar are usually represented as menu items. Well-designed icons can be recognized more quickly than words. Moreover, they occupy less space than words. Though the buttons appear as different bitmaps they are actually stored in a single bitmap. Each button on the toolbar is represented as a tile on the bitmap. The default size of the tile is 16 x 15 pixels.
Buttons on the toolbar can be grouped together and a separator separates group of button. The application framework supplies the border for every button. It modifies the border alongwith the tile color to reflect the current status of the button. A button can have three states-pressed up, pressed down, disabled, disabled down and intermediate. The style of the button determines how the button appears and how it responds to the user input. Buttons on the toolbar are of two types-push buttons and check boxes. There is no built-in interface to immediately change a button to its indeterminate state; this must be done using code.
The following table gives the values of the six Windows interface application guide button styles and their TBBS value:
Style TBBS value
Mouse down TBBS_PRESSED
Disabled TBBS_DISABLED
Down TBBS_CHECKED
Down disabled TBBS_CHECKED
Indeterminate TBBS_INDETERMINATE
The colors used in the toolbar are consistent with the recommendation of the Windows interface application design guide. These colors are not hard-coded and can be changed dynamically in response to user customization through the control panel.
Toolbar buttons top/ left edges COLOR_BTNHIGHLIGHT RGB (255, 255, 255)
Toolbar buttons bottom/ right edges COLOR_BTNSHADOW RGB(128, 128, 128)
Table 7.4: Colors used in the Toolbar
The toolbar bitmap buttons are recolored treating them as standard Window control buttons. This recoloring occurs when the bitmap is loaded from the resource or in response to a change in system c
olors for user customization.
The following table lists the colors in a toolbar bitmap that will be recolored automatically.
RGB Value Dynamically mapped COLOR value
RGB (0, 0, 0) COLOR_BTNTEXT
RGB (192, 192, 192) COLOR_BTNSHADOW
RGB (255, 255, 255) COLOR_BTNFACE
RGB (128, 128, 128) COLOR_BTNHIGHLIGHT
Color recolored in a toolbar bitmap
These colors should therefore be used with caution. If you do not wish to a have a particular portion of your bitmap recolored ,then use a color that closely resembles one of the mapped RGB values. The mapping is done on exact RGB values. Toolbars are of two types-docking and floating toolbars. A docking toolbar can be docked to any side of the parent window and floating can be floated in draggable mini-frame windows and users can resize them.
CREATING A TOOLBAR
The first step in the creation of a toolbar is to create the bitmap for the toolbar using the MDS's resource editor. After the creation of the toolbar bitmap, the CToolBar object must be created.
class mywindow:: public CFrameWnd
protected:
CToolbar mytoolbar;
public:
mywindow();
Once the object has been created, call the Create member function to create the toolbar and its buttons. Then call the LoadToolBar member function to load the toolbar resource into the application framework as shown in the following segment:
Toolbars are always updated using the ON_UPDATE_COMMAND_UI mechanism. When an icon on the toolbar is clicked on a command message is sent. A button on the toolbar is available only if it has an associated command handler. The UpdateCommandHandler is used to set the state of the button, which is implemented by the application framework to modify the graphical image of the button. The Update Command Handler is not called in the case of separators.
A toolbar button without a command handler is made unavailable by the application framework. On clicking on the toolbar button the ON_UPDATE_COMMAND_UI handler associated with the command ID of the button is called. The Enable member function of the CCmdUI class can be used to enable/disable a toolbar button. The SetCheck member function of the same class is used to select the toolbar button. The SetRadio member Function of the same class is similar to the SetCheck function.
The SetButton member function is used to set the style of a button, separator or group buttons. Customizing the API permits you to change the state of a given toolbar button. These states should preferably be changed in the Update Command Handler for the toolbar button. So any changes to these states made through the SetButtonStyle member functions may not be saved after the next idle processing
TBBS Value Description
TBBS_PRESSED Button is currently pressed
TBBS_DISABLED Button is currently disabled
TBBS_CHECKED Button is currently pressed
TBBS_INDETREMINATE Check box is currently indeterminate
The process of creating a toolbar can be summarized as:
Create the toolbar resource.
Construct the CToolBar object.
Call the Create function to create the window and attach it to the CToolBar object.
Call the LoadToolBar member function to load the toolbar resource.
Implement the toolbar buttons by adding a command handler for each button
These steps are carried out in the following program:
In the above program when the user selects the ID_NEW button on the toolbar, the message handler for it, newfile is called which displays the message box. When the toolbar button ID_GO is pressed the message handler go is called and a message box is displayed.
@WHAT ARE METAFILES
Metafiles are a collection of structures that store pictures in a device-independent format. They define images as a coded set of lines and shapes. A metafile can be compared to a cassette tape of graphics interface instructions. To play an audiocassette, a player or recorder is required and that is exactly what a metafile device context is. Each device context has an associated window device context identifiable by the handle of type HDC. Recording graphic information can be done either in the memory or onto a file. If a filename is specified while creating the metafile device context, the metafile will be created temporarily on the disk; otherwise it will be recorded in the memory. In either case, to be stored permanently the metafile has to be written to the disk.
WHY METAFILES
Images are produced by a sequence of calls to a collection of routines. A metafile stores this sequence of calls rather than the image that is generated as a result of making this sequence of calls. This is far more compact than storing the image itself. If the routines are simple redisplaying a metafile image is faster than redisplaying a pixel image (sometimes the user minimizes a window and would like to redisplay it later). Metafiles are also used for archiving and transferring pictures across sites.
CMetaFileDC CLASS
This class is inherited from the CDC class. This class inherits all the I/O functions from the CDC class. These functions allow drawing directly in the memory. When you use the CMetaFileDC class remember it does not support the this pointer.
A window metafile contains a sequence of graphics device interface commands that can be replayed to create an image or text. To implement a window metafile the first step is to create an object of CMetaFileDC class as shown in the example below:
CMetaFileDC metafile; // creates an instance of CMetaFileDC class
After creating the object of the CMetaFileDC class the Create member function is called to create a window metafile device context. This gets attached to the CMetaFileDC object.
Metafile.Create(); // is a call to the create function to create a device context in the memory
where drawing can be done
While writing on to the screen ensure that the same sequence of commands as that sent to the ClientDC function is maintained and sent to the CMetaFileDC object. When you create a metafile device context it will be created for writing on in very much the same manner as a file that has been opened in the write mode. In order to replay the contents that have been stored in the metafile-in memory or on the disk-the metafile has to be opened in the read mode. The file has to be closed using the Close function.
Close FUNCTION
This function closes the metafile device context and returns a metafile handle of type HMETAFILE as shown in the code below:
HMETAFILE hmetafile;
hmetafile=metafile.Close(); // is a call to the Close member function
The metafile handler can be used later to open the metafile in the read mode so that it can be replayed. Playback is done using the PlayMetaFile function.
PlayMetaFile FUNCTION
This function plays the content of the metafile specified by the metafile handle on the desired device context. The metafile handle is the handle returned by the close function of the CMetaFileDC class as shown in the code below:
CClientDc screendc; // creates a
Screendc.Create(this); // device context
Screendc.PlayMetaFile(hmetafile); // plays the metafile onto the device context created
CopyMetaFile FUNCTION
This function causes the metafile to be stored on the disk. The metafile must be closed before calling this function.
HMETAFILE hmetafile;
hmetafile=metafile.Close();
CopyMetaFile( hmetafile, "FileName");
DeleteMetaFile FUNCTION
When the metafile is no longer needed it is deleted from the memory using the DeleteMetaFile Windows API function as shown below:
DeleteMetaFile(hmetafile);
GetMetaFile FUNCTION
The metafile stored on the disk can be retrieved using the GetMetaFile function as shown below:
HMETAFILE hmetafile;
hmetafile=GetMetaFile( " FileName");
PRINTING THE METAFILE
Microsoft Windows implements device independent display. This means that function calls made for drawing on a device context associated with the window client area on the screen can also be used for other devices like printers. The device context to call GDI functions and the device driver associated with the particular device translates the call so that they are understood by the device.
CPrintDialog CLASS
This is inherited from the CDialog class and it encapsulates the services provided by the Windows common dialog box for printing. These common dialog boxes provide an easy method of implementing the print dialog boxes consistent with Windows standards.
The first step is to create an instance of the CPrintDialog class as shown below:
CPrintDialog myprint(FALSE);
FALSE is sent as a parameter to display the Windows print dialog box that is shown below:
On the other hand if TRUE is sent as a parameter then the standard Windows setup dialog box would be displayed
The next step is to call the DoModal function of the CPrintDialog class as shown below:
myprint.DoModal();
This function displays the Windows common print dialog box and allows the user to select various printing options such as the printer, the number of copies and the print range.
After the user enters the information in the dialog box that information needs to be retrieved.
The following table helps to retrieve these settings together with their purpose and return values.
Function
Return Value
Purpose
GetCopies
To retrieve the number of copies specified.
GetDeviceName
CString
To retrieve the name of the printer selected.
GetDriverName
DEVMODE data structure
To retrieve information about the environment of the printer driver.
GetFromPage
To retrieve the starting page number in the range of pages to be printed.
GetToPage
To retrieve the ending page number in the range of pages to be printed.
GetPortName
CString
To retrieve the name of the currently selected printer port.
PrintAll
BOOLEAN
To determine whether all the pages of the document have to be printed.
PrintRange
BOOLEAN
To determine if only the selected items have to be printed.
After retrieving the settings the next step is to create a printer device context. To do so you create an instance of the CDC class as shown in the segment below:
CDC *printdc;
printdc= new CDC;
Then you call the CreateDC function with the device mode, driver name, port name, mode and other required settings as parameters. These functions are retrieved using the functions listed in the table. If the creation of a device context is successful then the contents of the metafile should be displayed onto the device context. On completion of the printing process the CDC object needs to be deleted and the GlobalFree function invoked to free the memory allocated for the DEVMODE structure. This structure is used to hold the printer information and is returned by the GetDriverName function.
The following code segment illustrates the steps discussed so far:
MessageBox( "printing unsuccessful! Like to try again");
delete print;
GlobalFree(mode);
The above example makes use of the Escape function. This is required to allow the application to access the facilities of any particular device that is not available through GDI. This function of the CDC class is translated and sent to the device driver. The STARTDOC function parameter ensures that documents containing more than one page are not mixed with other print jobs. This is also used to inform the driver that a new print job is starting and that all the printing sequences that follow should be spooled under one print job until it encounters the ENDDOC. The Escape function is called again after the successful completion of a print job in order to stop a print job.
DESIGNING USER INTERFACE
All the components such as dialog box, device contexts, mouse cursor, toolbar, windows etc., learned so far, constitute what we call user interface objects. The following section describes some guidelines to be borne in mind while designing and constructing a user interface.
Provide menu-driven interface: A menu enables the user to work with what is known as recognition memory where visual images, text or icons are associated with familiar words and meanings. This is in direct contrast with recall memory where the user has to remember the correct syntax of the commands in order to bring out the desired result. So your projects must be menu based so that users can recognize options with the help of the icon displayed.
Be consistent: The basic purpose of consistency is to follow a set of rules and not have exceptions and special conditions. The basic purpose of consistency is to allow the user to generalize about one aspect of the system and apply it to another aspect. Consistency always helps in avoiding frustration that may arise when a system does not behave in an understandable and logical way. Consistency could be in terms of the output as well as the input.
Given below are some broad outlines which if followed can maintain consistency in your projects:
Colors are always coded in the same manner.
Menu items are displayed in the same relative position within a menu so that the user does not have to search for a new location to find the menu.
Global functions such as the Help, Status and Cancel can be invoked at any time.
Generic commands such as Cut, Copy and Paste are provided so that they can be generated at any point of time with predictable results.
Provide feedback: Feedback is a vital component of any type of communication because it tells you how you have performed. Similar to the role it plays in human communication. Feedback forms an important component of computer communication. The symbol used for denoting the type of feedback forms an important consideration of any project. Feedback should be effectively provided by the judicious use of status bars, menus, icons, progress control bars, dialog boxes, etc.
Screen layout principles: Three basic principles of any screen layout are balance, gridding and proportion. The Toolbar button should be designed so that the icon appears at the center of the bitmap, giving it a neat look. Gridding is the effective use of empty space between the different areas of the screen, resulting in a neat and aesthetic appeal. Proportion deals with the size of the rectangular layout in a grid.
Accommodate multiple level skills: Interactive graphics must be designed for a spectrum of users ranging from the completely new and experienced to the most experienced. The interface should provide all types of tools making a system usable at all levels of skills such as accelerators, prompts, help, extensibility and hiding of complexity.
Minimize possibility of errors
Do not offer menu options that display the message " Option not ready". This does not leave a good impact on the end-user.
Do not let the user select the Delete option if nothing has been selected for deletion.
Do not let the user select the Paste option when the clipboard is empty.
Do not let the user select the Copy option when there is nothing to be copied.
All the instances mentioned above are examples of context sensitivity. The system should provide only those commands that are valid in the context the user is currently working in. The system should not allow the user to perform operations that are not permissible when the user is in the context mode.
Provide for recovery from the errors: There is ample evidence supporting the fact that the people are more productive if mistakes are easily rectified. In your projects, error recovery is possible through Undo, Abort, Cancel or Correct for any application that performs an activity that is not permissible by the computer.
@OVERVIEW
Microsoft Foundation Classes (MFC) applications use a programming model that separates a program's data from the display of that data and from most user interaction with the data. In this model, an MFC document object reads and writes data to persistent storage. The document may also provide an interface to the data wherever it resides (such as in a database). A separate view object manages data display, ranging from rendering the data in a window to user selection and editing of data. The view obtains display data from the document and communicates any data changes back to the document.
While you can easily override or ignore the document/view separation, there are compelling reasons to follow this model in most cases. One instance is when you need multiple views of the same document, such as both a spreadsheet and a chart view. The document/view model lets a separate view object represent each view of the data, while the code common to all views (such as a calculation engine) can reside in the document. The document also takes on the task of updating all views whenever the data changes.
MFC's document/view architecture makes it easy to support multiple views, multiple document types, splitter windows, and other valuable user-interface features. At the heart of document/view are four key classes:
CDocument (or COleDocument) is an object used to store or control your program's data.
CView (or one of its many derived classes) is an object used to display a document's data and manage user interaction with the data.
CFrameWnd (or one of its variations) is an object that provides the frame around one or more views of a document.
CDocTemplate (or CSingleDocTemplate or CMultiDocTemplate) is an object that coordinates one or more existing documents of a given type and manages to create the correct document, view and frame window objects for that type.
MFC application makes use of document view architecture to create Single Document Interface (SDI) applications. MFC's Document View architecture provides the framework for developing applications such as the editors, file viewers and database query front ends, which manage disk-based data. The Document View architecture makes a single type of data available in multiple formats to the user. The Document View architecture separates programs into four main classes:
A document class derived from CDocument.
A view class derived from CView.
A frame class derived from CFrameWnd.
An application class derived from CWinApp.
Each class plays a specific role in a Document View application.
The parts of the MFC framework visible both to the user and the programmer, are the document and view. Most of your work in developing an application with the framework goes into writing your document and view classes. The document and the view together form the basis of the application framework. The document is responsible for the application's data. It is a data source and there is no limitation to the amount of data that the document can handle. Irrespective of the source, data management is encapsulated within the document object.
The view, on the other hand provides a visual display in a window or a printed page of the data to the user. It handles the interaction between the document and the user. The view can be a simple window, a SDI, an MDI or a class derived from CFormView, with the capabilities of the dialog box. The frame class contains the view and other user interface elements such as toolbars and menus. The application class is responsible for starting the application and interacting with Windows.
The document/view implementation in the class library separates the data itself from its display and from user operations on the data. All changes to the data are managed through the document class. The view calls this interface to access and update the data.
Documents, their associated views, and the frame windows that frame the views are created by a document template. The document template is responsible for creating and managing all documents of one type.
The base classes responsible for documents and views are:
The CDocument class provides the basic functionality for programmer-defined document classes. A document represents the unit of data that the user typically opens with the Open command on the File menu and saves with the Save command on the File menu.
The CView class provides the basic functionality for programmer-defined view classes. A view attached to a document acts as an intermediary between the document and the user. The view renders an image of the document on the screen and interprets user input as operations upon the document. The view also renders the image for both printing and print preview.
The following figure shows the relationship between a document and its view.
FEATURES OF DOCUMENT VIEW ARCHITECTURE
The most important feature of document view architecture is the manner in which it handles and displays data. Both these activities are handled separately as both are encapsulated in two different types of objects. Separating the data from the user interface allows each of these classes to concentrate on performing one job. Interfaces are defined for interaction with other classes in the application. Rather than mixing both types of codes together the functioning of each object stands alone.
Common document view actions for handling file operations such as selecting, opening and closing are handled separately by the framework. All that needs to be done is to read and write the required bytes from the data stream provided. Therefore less time is spent on writing the code for these functions and more time can be spent on application specific code.
Print preview is another important feature of this architecture. Users may want to see the result of their work on the screen before sending the output to the printer. MFC's built-in support saves time and effort required to code the print preview feature of the program. One of the important features of document view architecture is that it divides the work of a window's program into well-defined categories. Most classes fall into four main categories:
Controls and other user-interface elements related to a specific view
Data and handling classes belonging to a document.
Toolbar, status bar, and menu-handling classes belonging to the frame class.
Classes derived from the CWinApp, which handles the interaction between the application and Windows.
ADVANTAGES OF DOCUMENT VIEW ARCHITECTURE
The key advantage of using the MFC Document View architecture is that the architecture supports multiple views of the same document particularly well. Suppose your application lets users view numerical data either in spreadsheet form or in chart form. A user might want to simultaneously see the raw data, both in spreadsheet form and a chart that results from the data. You display these separate views in separate frame windows or in splitter panes within a single window. Now the user can edit the data in the spreadsheet and see the changes instantly reflected in the chart.
In MFC, the spreadsheet view and the chart view would be based on different classes derived from CView. Both views would be associated with a single document object. The document stores the data (or perhaps obtains it from a database). Both views access the document and display the data they retrieve from it.
When a user updates one of the views, that view object calls CDocument::UpdateAllViews. That function notifies all of the document's views, and each view updates itself using the latest data from the document. The single call to UpdateAllViews synchronizes the different views.
This scenario would be difficult to code without the separation of data from view, particularly if the views stored the data themselves. Document View makes this easy as the framework does most of the coordination work.
TYPES OF DOCUMENT VIEW APPLICATIONS
There are two basic types of Document View applications:
Single document interface applications: SDI applications allow only one document frame window to open at a time. In these kinds of applications the document frame window is also the main frame window for the application.
Multiple document interface applications: MDI applications allow multiple document frame windows to be open in the same instance of an application. An MDI application has a window within which multiple MDI child windows, which are frame windows themselves, can be opened, each containing a separate document. In some applications, the child windows can be of different types, such as chart windows and spreadsheet windows. In that case, the menu bar changes as MDI child windows of different types are activated.
Under Windows 95, applications are commonly SDI because the operating system has a
dopted a "document-centered" view.
DOCUMENTS, VIEWS AND THE FRAMEWORK
At the heart of the MFC framework are the concepts of document and view. A document is a data object with which the user interacts in an editing session. It is created by the New or Open command on the File menu and is typically saved in a file. (Do not confuse standard MFC documents, derived from class CDocument, with Active documents or OLE compound documents.) A view is a window object through which the user interacts with a document.
The key objects in a running application are:
The document(s)
Your document class (derived from CDocument) specifies your application's data.
If you want OLE functionality in your application, derive your document class from COleDocument or one of its derived classes, depending on the type of functionality you need.
The view(s)
Your view class (derived from CView) is the user's "window on the data". The view class controls how the user sees your document's data and interacts with it. In some cases, you may want a document to have multiple views of the data.
If you need scrolling, derive from CScrollView. If your view has a user interface that is laid out in a dialog-template resource, derive from CFormView. For simple text data, use or derive from CEditView. For a form-based data-access application, such as a data-entry program, derive from CRecordView (for ODBC). Also available are the CTreeView, CListView and CRichEditView classes.
The frame windows
Views are displayed inside "document frame windows". In an SDI application, the
document frame window is also the "main frame window" for the application. In an MDI application, document windows are child windows displayed inside a main frame window. The derived main frame window class specifies the styles and other characteristics of the frame windows that contain your views.
If you need to customize frame windows, derive from CFrameWnd to customize the document frame window for SDI applications. Derive from CMDIFrameWnd to customize the main frame window for MDI applications. Also derive a class from CMDIChildWnd to customize each of the distinct kinds of MDI document frame windows that your application supports.
The document template(s)
A document template orchestrates the creation of documents, views and frame windows. A particular document template class, derived from class CDocTemplate, creates and manages all open documents of one type. Applications that support more than one type of document have multiple document templates. Use class CSingleDocTemplate for SDI applications or class CMultiDocTemplate for MDI applications.
The application object
The application class (derived from CWinApp) controls all of the above objects above and specifies application behavior such as initialization and cleanup. The application's one and only application object creates and manages the document templates for any document types the application supports.
Thread objects
If your application creates separate threads of execution-for example, to perform calculations in the background-you will use classes derived from CWinThread. CWinApp itself is derived from CWinThread and represents the primary thread of execution (or the main process) in your application. You can also use MFC in secondary threads.
In a running application, these objects cooperate with user actions which are bound together by commands and other messages. A single application object manages one or more document templates. Each document template creates and manages one or more documents (depending on whether the application is SDI or MDI). The user views and manipulates a document through a view contained inside a frame window.
CDocument CLASS
The CDocument class provides the basic functionality for user-defined document classes. A document represents the unit of data that the user typically opens with the File Open command and saves with the File Save command. CDocument supports standard operations such as creating a document, loading it and saving it. The framework manipulates documents using the interface defined by CDocument.
An application can support more than one type of document. For example, an application might support both spreadsheets and text documents. Each type of document has an associated document template; the document template specifies what resources (for example, menu, icon or accelerator table) are used for that type of document. Each document contains a pointer to its associated CDocTemplate object.
Users interact with a document through the CView object(s) associated with it. A view renders an image of the document in a frame window and interprets user input as operations on the document. A document can have multiple views associated with it. When the user opens a window on a document, the framework creates a view and attaches it to the document. The document template specifies what type of view and frame windows are used to display each type of document.
Documents are part of the framework's standard command routing and consequently receive commands from standard user interface components (such as the File Save menu item). A document receives commands forwarded by the active view. If the document does not handle a given command, it forwards the command to the document template that manages it.
When a document's data is modified, each of its views must reflect those modifications. CDocument provides the UpdateAllViews member function for you to notify the views of such changes, so the views can repaint themselves as necessary. The framework also prompts the user to save a modified file before closing it.
To implement documents in a typical application, you must do the following:
Derive a class from CDocument for each type of document.
Add member variables to store each document's data.
Implement member functions for reading and modifying the document's data. The document's views are the most important users of these member functions.
Override the CObject::Serialize member function in your document class to write and read the document's data to and from disk.
CDocument supports sending your document via mail if mail support (MAPI) is present
TASKS PERFORMED IN A DOCUMENT'S LIFE CYCLE
Creating a new document
Sequence In creating a new Document:
SDI applications make use of the OnNewDocument function to create and initialize a document object. The OnNewDocument function is automatically executed whenever a new document is created. This happens either when you start the program or select the New option from the File menu. When you want to create the document with the default value of the document variables then the initialization should be done in this function, as this function is executed only under the above mentioned two conditions. As the document object is reusable, the default implementation of the function calls the DeleteContents function to reset any data that might be contained in the document.
Consider the following code segment
class mydocument: public CDocument
DECLARE_DYNCREATE (mydocument)
virtual BOOL OnNewDocument()
if(!CDocument :: OnNewDocument())
return FALSE;
return TRUE;
IMPLEMENT_DYNCREATE ( mydocument, CDocument)
OnNewDocument FUNCTION
This function is called by the framework as part of the File New command. The default implementation of this function calls the DeleteContents member function to ensure that the document is empty and then marks the new document as clean.
If the user chooses the File New command in an SDI application, the framework uses this function to reinitialize the existing document, rather than create a new one. If the user chooses File New in a multiple document interface (MDI) application, the framework creates a new document each time and then calls this function to initialize it. You must place your initialization code in this function instead of in the constructor for the File New command to be effective in SDI applications.
Syntax:
virtual BOOL OnNewDocument( );
returns non-zero if the document was successfully initialized; otherwise 0.
DECLARE_DYNCREATE FUNCTION
The DECLARE_DYNCREATE macro is used to enable objects of CObject-derived classes to be created dynamically at run time. The framework uses this ability to create new objects dynamically. For example, when it reads an object from the disk during serialization. Document, view and frame classes should support dynamic creation since the framework needs to create them dynamically.
Add the DECLARE_DYNCREATE macro in the .H module for the class. Then include that module in all .CPP modules that need access to objects of this class. If DECLARE_DYNCREATE is included in the class declaration, then IMPLEMENT_DYNCREATE must be included in the class implementation.
Syntax:
DECLARE_DYNCREATE( class_name )
class_name is the actual name of the class (not enclosed in quotation marks).
IMPLEMENT_DYNCREATE FUNCTION
The IMPLEMENT_DYNCREATE macro is used with the DECLARE_DYNCREATE macro to enable objects of CObject-derived classes to be created dynamically at run time. The framework uses this ability to create new objects dynamically. For example, when it reads an object from disk during serialization. (Add the IMPLEMENT_DYNCREATE macro in the class implementation file.)
class_name is the actual name of the class (not enclosed in quotation marks).
base_class_name is the actual name of the base class (not enclosed in quotation marks).
If you use the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros, you can then use the RUNTIME_CLASS macro and the CObject::IsKindOf member function to determine the class of your objects at run time.
Modifying the document
The CView derived class accepts data in the document. This is introduced in later sessions. However, the document has to keep track of whether the document has been modified before it is closed. The function IsModified determines this. This function is invoked to find whether the document has been modified since it has been last saved. Consider the following code segment.
if (IsModified() = TRUE)
AfxMessageBox ("Save changes");
The IsModified function tests the value of a modification plan, which is set when changes are made to the document. The flag is cleared when the document is saved or a new document is loaded. You can set the value of this flag by invoking the SetModifiedFlag function as shown below.
SetModifiedFlag(TRUE);
The function takes either TRUE or FALSE as a parameter when the document is changed or unchanged respectively. You need to call this function only when you want to mark the document as changed. The Document View framework takes care of resetting the flag after the document has been saved.
The user can save a document by either choosing the Save option or the Save As.. option from the File Menu. When the user chooses the File Save option, the OnFileSave member function of the CWinApp class is invoked. This, in turn, calls the OnOpenDocument function of the CDocument class to save the data in the document. This is referred to as serialization. The default working of the OnOpenDocument function is to create a CArchive object and pass it to the Serialize member function of the CDocument class. After the document has been saved, the modification flag is reset to mark the document as unmodified. When the user chooses the File Save As.. option, a common dialog box is displayed which accepts the name of the file in which the document is to be saved. Once the name of the file is obtained, the same CDocument functions as with the File Save option are invoked.
CArchive CLASS
The CArchive class allows you to save complex objects in simple binary format on the disk. An archive object is a binary stream that is associated with a file, and allows reading and writing data to and from the file.
The extraction (>>) and insertion (,,) operators are overloaded by the CArchive class to support loading and storing of CObject derived classes. When a CArchive object is created, it is defined as used either for input or for output, but never for both. The IsStoring and IsLoading functions are used to determine whether the CArchive object is used for input or output.
Serialize FUNCTION
Serialization is the means by which classes that are derived from CDocument store and retrieve data from an archive, which is usually a file. This ability to read and write to and from a data stream such as a disk file is called persistence and the technique of reading and writing is called serialization. Support for serialization is provided by MFC through the CArchive class. Actual implementation is done using the Serialize function. This function takes the address of CArchive as a parameter. The advantage of serialization is that a persistent object can be stored and retrieved using similar syntax, irrespective of the complexity of the internal structure of the object. The codes segment that follows illustrates this.
class mydocument : public CDocument
void Serialize(CArchive &ar)
ar<<data; //data stores the data that has to be stored or retrieved
ar>>data;
The above code segment however does not depict the actual storage of data in the memory. It only shows the storage and retrieval of data to and from the disk. There are various ways of storing data in memory. One such way is the use of linked lists.
CStringList CLASS
The CStringList class supports lists of CString objects. All comparisons are done by value, meaning that the characters in the string instead of the addresses are compared.
The member functions of CStringList are similar to the member functions of class CObList. (The CObList class supports ordered lists of non-unique CObject pointers accessible sequentially or by pointer value. CObList lists behave like doubly linked lists.) Because of this similarity, you can use the CObList reference documentation for member function specifics. Wherever you see a CObject pointer as a return value, substitute a CString (not a CString pointer). Wherever you see a CObject pointer as a function parameter, substitute an LPCTSTR.
For example,
CObject*& CObList::GetHead() const;
translates to
CString& CStringList::GetHead() const;
CStringList incorporates the IMPLEMENT_SERIAL macro to support serialization and dumping of its elements. If a list of CString objects is stored to an archive, either with an overloaded insertion operator or with the Serialize member function, each CString element is serialized in turn.
When a CStringList object is deleted, or when its elements are removed, the corresponding CString objects are also deleted.
Consider the following code segment:
class mydocument:public CDocument
void Serialize (CArchive &ar)
myview *viewptr;
viewptr=(myview*)list.GetHead();
if(ar.IsStoring())
POSITION start;
start=strlist.GetHeadPosition();
CString data;
while(start)
CString temp;
temp=strlist.GetNext(start);
data+=temp;
ar<< data;
int len;
ar>>a;
len=s.GetLength();
char temp[2];
for(int i=0; i<len; i++)
temp[0]=s[i];
temp[1]='\0';
strlist.AddList(temp);
GetHeadPosition FUNCTION
This function is a member of the CStringList class and its task is to get the position of the head element of any list. A return value is a position value that can be used for iteration or object pointer retrieval. It is NULL if the list is empty.
It gets the list element which is identified by rPosition, then sets rPosition to the POSITION value of the next entry in the list. You can use GetNext in a forward iteration loop if you establish the initial position with a call to GetHeadPosition or Find.
You must ensure that your POSITION value represents a valid position in the list. If it is invalid, then the Debug version of the Microsoft Foundation Class Library is asserted.
If the retrieved element is the last in the list, then the new value of rPosition is set to NULL.
Syntax:
POSITION GetHeadPosition( ) const;
GetNext FUNCTION
This is the derived member of the CStringList class. If the list is constant, GetNext returns a copy of the element at the head of the list. This allows the function to be used only on the right side of an assignment statement and protects the list from modification. If the list is not constant, GetNext returns a reference to an element of the list.
This allows the function to be used on either side of an assignment statement and thus allows the list entries to be modified.
Syntax:
TYPE& GetNext( POSITION& rPosition );
TYPE GetNext( POSITION& rPosition ) const;
TYPE Template parameter specifies the type of the elements in the list.
RPosition refers to a POSITION value returned by a previous GetNext, GetHeadPosition, or other member function call.
GetLength FUNCTION
Call this member function to get a count of the bytes in this CString object. The count does not include a null terminator. For multibyte character sets (MBCS), GetLength counts each 8-bit character; that is, a lead and trail byte in one multibyte character are counted as two bytes.
The return value is a count of the bytes in the string.
Syntax:
int GetLength( ) const;
AddTail FUNCTION
Adds a new element or list of elements to the tail of this list. The list can be empty before the operation. The first version returns the POSITION value of the newly inserted element.
Syntax:
POSITION AddTail( ARG_TYPE newElement );
ARG_TYPE Template parameter specifies the type of the list element (it can be a reference).
NewElement is the element to be added to this list.
pNewList is a pointer to another CList list. The elements in pNewList will be added to this list.
Example:
void AddTail( CList* pNewList );
Closing a document
When the user chooses the Close option (ID_FILE_CLOSE) from the menu, the document view framework calls the OnCloseDocument member function of the CDocument Class. The function checks for unsaved changes by calling the IsModified member function. If the document has not been notified since the last changes, the DeleteContents function is called and the view to the document is closed.
Loading a Document
Loading a document that has been previously saved is known as deserialization. This process is incorporated in the Serialize function. To determine whether the CArchive object is to be stored or loaded the functions IsStoring and IsLoading are used.
CView CLASS
The CView class provides the basic functionality for user-defined view classes. A view is attached to a document and acts as an intermediary between the document and the user. The view renders an image of the document on the screen or printer and interprets user input as operations upon the document.
A view is a child of a frame window. More than one view can share a frame window, as in the case of a splitter window. A CDocTemplate object establishes the relationship between a view class, a frame window class and a document class. When the user opens a new window or splits an existing one, the framework constructs a new view and attaches it to the document.
A view can be attached to only one document, but a document can have multiple views attached to it at once. For instance, if the document is displayed in a splitter window or in multiple child windows in a multiple document interface (MDI) application. Your application can support different types of views for a given document type. For example, a word-processing program might provide both a complete text view of a document as well an outline view that shows only the section headings. These different types of views can be placed in separate frame windows or in separate panes of a single frame window if you use a splitter window.
A view may be responsible for handling several different types of input, such as keyboard input, mouse input or input via drag-and-drop, as well as commands from menus, toolbars or scroll bars. A view receives commands forwarded by its frame window. If the view does not handle a given command, it forwards the command to its associated document. Like all command targets, a view handles messages via a message map.
A view is responsible for displaying and modifying the document's data but not for storing it. The document provides the view with the necessary details about its data. The view can access the document's data members directly, or you can provide member functions in the document class for the view class to call.
When a document's data changes, the view responsible for the changes typically calls the CDocument::UpdateAllViews function for the document. This function notifies all the other views by calling the OnUpdate member function for each. The default implementation of the OnUpdate function invalidates the view's entire client area. You can override it to invalidate only those regions of the client area that map to the modified portions of the document.
A view handles scroll-bar messages with the CWnd::OnHScroll and CWnd::OnVScroll member functions. You can implement scroll-bar message handling in these functions, or you can use the CView derived class CScrollView to handle the scrolling.
Besides CScrollView, the MFC Library provides nine other classes derived from CView:
CCtrlView, a view that allows usage of Document View architecture with tree, list, and rich edit controls.
CDaoRecordView, a view that displays database records in dialog box controls.
CEditView, a view that provides a simple multiline text editor. You can use a CEditView object as a control in a dialog box as well as a view on a document.
CFormView, a scrollable view that contains dialog box controls and is based on a dialog template resource.
CListView, a view that allows usage of Document View architecture with list controls.
CRecordView, a view that displays database records in dialog box controls.
CRichEditView, a view that allows usage of Document View architecture with rich edit controls.
CScrollView, a view that automatically provides scrolling support.
CTreeView, a view that allows usage of Document View architecture with tree controls.
The CView class also has a derived implementation class named CPreviewView, which is used by the framework to perform print previewing. This class provides support for the features unique to the print-preview window, such as a toolbar, single- or double-page preview, and zooming (enlarging the previewed image). You do not need to call or override any of CPreviewView's member functions unless you want to implement your own interface for print preview. For example, if you want to support editing in the print preview mode.
To use CView, derive a class from it and implement the OnDraw member function to perform screen display. You can also use OnDraw to perform printing and print preview. The framework handles the print loop for printing and previewing your document. In the following case the OnDraw() member function displays the contents of the linked list. The characters that the user keys in from the keyboard are added to the linked list. The data that is entered by the user is stored in the linked list in the OnChar function, which is invoked by the message map entry, WM_CHAR. This is illustrated in the following code segment:
This function is called to get a pointer to the view's document. It allows you to call the document's member functions. This function returns a pointer to the CDocument object associated with the view. It will be NULL if the view is not attached to a document.
Syntax:
CDocument* GetDocument( ) const;
OnDraw FUNCTION
This function is called by the framework to render an image of the document. The framework calls this function to perform screen display, printing and print preview, and it passes a different device context in each case. There is no default implementation.
Syntax:
virtual void OnDraw( CDC* pdc) = 0;
pdc points to the device context to be used for rendering an image of the document.
OnChar FUNCTION
The framework calls this member function when a keystroke is translated into a non-system character. This function is called before the OnKeyUp member function and after the OnKeyDown member function is called. OnChar contains the value of the key being pressed or released.
Since it is not necessary to have a one-to-one correspondence between keys pressed and OnChar calls generated, the information in nFlags is generally not useful to applications. The information in nFlags applies only to the most recent call to the OnKeyUp or the OnKeyDown member functions that precede the call to OnChar.
This member function is called by the framework to allow your application to handle a Windows message. The parameters passed to your function reflect the parameters received by the framework when the message was received. If you call the base-class implementation of this function, that implementation will use the parameters originally passed with the message and not the parameters you supply to the function.
nChar contains the character code value of the key.
nRepCnt contains the repeat count, which is the number of times the keystroke is repeated when user holds down the key.
nFlags contains the scan code, key-transition code, previous key state and context code, as shown in the following list:
Value
Meaning
Specifies the repeat count. The value is the number of times the keystroke is repeated as a result of the user holding down the key.
16-23
Specifies the scan code. The value depends on the original equipment manufacturer (OEM).
Specifies whether the key is an extended key, such as the right-hand Alt and Ctrl keys that appear on an enhanced 101- or 102-key keyboard. The value is 1 if it is an extended key; otherwise, it is 0.
25-28
Used internally by Windows.
Specifies the context code. The value is 1 if the Alt key is held down while the key is pressed; otherwise, the value is 0.
Specifies the previous key state. The value is 1 if the key is held down before the message is sent, or it is 0 if the key is up.
Specifies the transition state. The value is 1 if the key is released, or it is 0 if the key is pressed.
OnInitialUpdate FUNCTION
Called by the framework after the view is first attached to the document, but before the view is initially displayed. The default implementation of this function calls the OnUpdate member function with no hint information (that is, using the default values of 0 for the lHint parameter and NULL for the pHint parameter). Override this function to perform any one-time initialization that requires information about the document. For example, if your application has fixed-sized documents, you can use this function to initialize a view's scrolling limits based on the document size. If your application supports variable-sized documents, use OnUpdate to update the scrolling limits every time the document changes.
Syntax:
virtual void OnInitialUpdate( );
CWinApp CLASS
The CWinApp class is the base class from which you derive a Windows application object. An application object provides member functions to initialize an application (and each instance of it) and for running the application.
The code segment that follows illustrates the implementation of this class with respect to an SDI application:
class mywinapp : public CWinApp
public:
int InitInstance()
CSingleDocTemplate *template;
CRunTimeClass *doc, *frame, *view;
doc=RUNTIME_CLASS(mydocument);
frame=RUNTIME_CLASS(CFrameWnd);
view=RUNTIME_CLASS(myview);
template= new CSingle DocTemplate(IDR_MENU1, doc, frame, view);
AddDocTemplate(DocTemplate);
OnFileNew();
return1;
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(mywinapp, CWinApp)
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp:: OnFileOpen)
END_MESSAGE_MAP()
mywinapp CWinAppInstance;
FUNCTIONS, MACROS AND CLASSES
The following section deals with the functions, macros and classes that have been incorporated in the code segment we have just seen.
CSingleDocTemplate CLASS
The CSingleDocTemplate class defines a document template that implements the single document interface (SDI). An SDI application uses the main frame window to display a document; only one document can be opened at a time.
A document template defines the relationship between three types of classes:
A document class, which you derive from CDocument.
A view class, which displays data from the CDocument class. You can derive this class from CView, CScrollView, CFormView or CEditView. (You can also use CEditView directly.)
A frame window class, which contains the view. For a SDI document template, you can derive this class from CFrameWnd; if you do not need to customize the behavior of the main frame window, you can use CFrameWnd directly without deriving your own class.
An SDI application typically supports one type of document, so it has only one CSingleDocTemplate object. Only one document can be open at a time. You do not need to call any member functions of CSingleDocTemplate except the constructor. The framework handles CSingleDocTemplate objects internally.
AddDocTemplate FUNCTION
Call this member function to add a document template to the existing list of available document templates that the application maintains.
Syntax:
void AddDocTemplate( CDocTemplate* pTemplate );
pTemplate is a pointer to the CDocTemplate to be added.
CRunTimeClass STRUCTURE
The classes that are derived from the CObject Class make use of this structure to hold runtime information.
RUNTIME_CLASS MACRO
This macro is used to get the runtime class structure and it returns a pointer to a CRunTimeClass structure for the specified class. Classes derived using the DECLARE_DYNCREATE macro return the pointer to the CRunTime Class.
OnFileNew FUNCTION
You must add an
ON_COMMAND( ID_FILE_NEW, OnFileNew )
statement to your CWinApp class message map to enable this member function. If enabled, this function handles execution of the File New command.
Syntax:
afx_msg void OnFileNew( );
OnFileOpen FUNCTION
You must add an ON_COMMAND (ID_FILE_OPEN, OnFileOpen) statement to your CWinApp class message map to enable this member function. If enabled, this function executes the File Open command.
Syntax:
afx_msg void OnFileOpen( );
The program that follows summarizes the concepts learned so far:
template= new CSingle DocTemplate(IDR_MENU1, doc, frame, view);
AddDocTemplate(DocTemplate);
OnFileNew();
return1;
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(mywinapp, CWinApp)
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp:: OnFileOpen)
END_MESSAGE_MAP()
mywinapp CWinAppInstance;
In the above example we have not derived a class from the CFrameWnd class since the application does not require overriding of any function in the class. The RUNTIME_CLASS macro is incorporated to dynamically create a CFrameWnd class object with default member functions.
INCORPORATING THE PRINT AND PRINT PREVIEW FEATURES
After getting familiar with opening, closing, storing, loading and displaying the data of the document the next process to be learnt is how to implement the print and print preview features in the document view applications. The first step is to incorporate this is to include the print and print preview option in the File menu. These options can be named ID_FILE_PRINT and ID_FILE_PRINT_PREVIEW respectively.
OnPreparePrinting() FUNCTION
This is called by the framework before a document is printed or previewed. The default implementation does nothing. You must override this function to enable printing and print preview. Call the DoPreparePrinting member function, passing it the pInfo parameter, and then return its value. The function returns a non-zero value to begin printing and zero if the print job has been canceled.
pInfo points to a CPrintInfo structure that describes the current print job.
DoPreparePrinting FUNCTION
Call this function from your override of OnPreparePrinting to invoke the Print dialog box and create a printer device context. This function's behavior depends on whether it is being called for printing or prints preview (specified by the m_bPreview member of the pInfo parameter). If a file is being printed, this function invokes the Print dialog box, using the values in the CPrintInfo structure that pInfo points to. After the user has closed the dialog box, the function creates a printer device context based on settings specified by the user in the dialog box and returns this device context through the pInfo parameter. This device context is used to print the document.
If a file is being previewed, this function creates a printer device context using the current printer settings; this device context is used for simulating the printer during preview. It returns non-zero if printing or print preview can begin and zero if the operation has been cancelled.
Syntax:
BOOL DoPreparePrinting( CPrintInfo* pInfo );
pInfo points to a CPrintInfo structure that describes the current print job.
OnBeginPrinting() FUNCTION
This is called by the framework at the beginning of a print or print preview job, after the OnPreparePrinting function has been called. The default implementation of this function does nothing. Override this function to allocate any GDI resources, such as pens or fonts, needed specifically for printing. Select the GDI objects into the device context from within the OnPrint member function for each page that uses them. If you are using the same view object to perform both screen display and printing, then use separate variables for the GDI resources needed for each display. This allows you to update the screen during printing.
You can also use this function to perform initializations that depend on properties of the printer device context. For example, the number of pages needed to print the document may depend on settings specified by the user from the Print dialog box (such as page length). In such a situation, you cannot specify the document length in the OnPreparePrinting member function, where you would normally do so. You must wait until the printer device context has been created based on the dialog box settings. OnBeginPrinting is the first overridable function that gives you access to the CDC object representing the printer device context, so you can set the document length from this function. Note that if the document length is not specified by this time, a scroll bar is not displayed during print preview.
PInfo points to a CPrintInfo structure that describes the current print job.
OnEndPrinting () FUNCTION
This is called by the framework after a document has been printed or previewed. The default implementation of this function does nothing. Override this function to free any GDI resources you allocated in the OnBeginPrinting member function.
pInfo points to a CPrintInfo structure that describes the current print job.
CFile CLASS
CFile is the base class for MFC files. It directly provides unbuffered, binary disk input/output services and it indirectly supports text and memory files through its derived classes. CFile works in conjunction with the CArchive class to support serialization of MFC objects. The hierarchical relationship between this class and its derived classes allows a program to operate on all file objects through the polymorphic CFile interface. A memory file, for example, behaves like a disk file. CFile and its derived classes are used for general-purpose disk I/O. ofstream or other Microsoft iostream classes are used for sending formatted text to a disk file. Normally, a disk file is opened automatically on CFile construction and closed when a file is destroyed. Static member functions permit you to interrogate a file's status without opening the file.
MFC uses an object of the CArchive class as an intermediary between the object to be serialized and the storage medium. This object is always associated with a CFile object, from which it obtains the necessary information for serialization, including the filename and whether the requested operation is a read or write. The object that performs a serialization operation can use the CArchive object without regard to the nature of the storage medium.
The primary difference between the CArchive object and the CFile object is that CArchive does its own buffering, whereas CFile directly calls the Windows file I/O functions which do minimal amount of buffering.
To incorporate a CFile object in the serialization function of the document you need to:
Call the GetFile() of the CArchive class to get a CFile pointer.
Call the Seek() of the CFile class to position the file pointer.
Call the Read() of the CFile class to read the data from the current file position.
Call the Write() of the CFile class to write data to the current file position.
Call the Flush() of the CFile class to ensure that the last byte has been written.
The code segment that follows incorporates the above mentioned features:
class mydocument: public CDocument
DECLARE_DYNCREATE(mydocument)
..............
................
void Serialize(CArchive &ar)
myview *viewptr;
void Serialize(CArchive & ar)
myview *viewptr;
viewptr = (myview*) viewlist.GetHead();
CFile *fileptr;
CObject::Serialize(ar);
if(ar.IsStoring())
POSITION start;
ar.Flush();
fileptr=ar.GetFile(); // get the pointer to the file//
start=strlist.GetHeadPosition();
CString data;
while(start)
CString temp;
temp=strlist.GetNext(start);
data+=temp;
fileptr->Seek(0, CFile::begin);
int len =strlist.GetCount();
fileptr->Write(data, len);
if(ar.IsLoading())
ar.Flush();
fileptr=ar.GetFile();
char temp[2];
strlist.RemoveAll();
while(fileptr
Read(temp, 1))
strlist.AddTail;(temp);
The overall logic of the above program is to check whether the Serialize function has been invoked or not. If it has been invoked for storing then the pointer to the CFile object is obtained by invoking the GetFile member function of the CArchive class. Before retrieving the pointer the Flush function of the CArchive class needs to be invoked to ensure that all the unwritten data is updated. The data stored in the linked list is gathered in a string, the file pointer is placed at the beginning of the file, the number of elements retrieved using the GetCount member function of the CStringList class and the string written on to the disk. If the Serialize function was invoked for loading purposes the Flush function needs to be invoked and the file pointer retrieved as in the case of storing. Invoking the RemoveAll member function of the CStringList class empties the linked list. The data is read from the file character by character using the Read function and every character is stored in the linked list.
The whole process of reading and saving data from the document onto the file can be summarized as in the diagram that follows:
@When an application runs under Microsoft Windows, the user interacts with documents displayed in frame windows. A document frame window has two major components: the frame and the contents that it frames. A document frame window can be a Single Document Interface (SDI) frame window or a Multiple Document Interface (MDI) child window. Windows manages most of the user's interaction with the frame window: moving, resizing, closing, minimizing and maximizing. The user manages the contents inside the frame.
FRAME WINDOWS AND VIEWS
The MFC framework uses frame windows to contain views. The two components-frame and contents-are represented and managed by two different classes in MFC. A frame-window class manages the frame and a view class manages the contents. The view window is a child of the frame window. Drawing and other user interaction with the document take place in the client area of view and not of frame. The frame window provides a visible frame around a view, complete with a caption bar and standard window controls such as a control menu, buttons to minimize and maximize the window and controls for resizing the window. The "contents" consist of the window's client area, which is fully occupied by a child window-the view.
There are four child classes in the CFrameWnd class. They are:
CMDIChildWnd: this is used to make another frame enclosing the view window.
CMDIFrameWnd: forms the frame of the main window. When several view windows appear in one program each CMDIChildWnd encloses its own window.
CMiniFrameWnd
ColeFrameWnd
CREATING AN APPLICATION
In the last session you learned that the document and view are closely related objects. The view can access the document's contents, which it must do to display the document's data and to inform the document of editing changes. The document also has a way of informing the view when the display needs to be updated. This might seem redundant, except that documents sometimes support multiple views of the same data. For example, the New Window command in the window menu in many applications supports the opening of a second frame window on the same document.
Another example is splitter windows, such as those in many applications, including Microsoft Excel. When view informs the document of an edit, you can call the document's UpdateAllViews member function. This is done to alert all related views that they must update themselves to obtain the changes. Multiple views usually show the same data in different ways. For example, one view of a hypothetical document might show its data in tabular form, while another shows the data as a graph.
Structurally, a view is a child window embedded in the client area of a frame window. Picture a child window in a SDI application, that covers the entire client area of the application frame window. In an MDI application, views of documents are embedded in MDI child window frames. (The MDI child frames, in turn, are embedded in a special child window, itself embedded in the application's main frame window. This child window is called MDICLIENT and it manages the currently open MDI children.) When splitter windows are used, the client area real estate is divided among multiple view windows. The document keeps a list of pointers to all of the views of its data.
The life cycle of a document, its view and its frames in an application has been summarized in the following five steps:
The document constructor is called during dynamic creation.
For each new document the document's OnNewDocument or OnOpenDocument member function is called.
The user interacts with the document throughout its lifetime.
The framework calls DeleteContents to delete data specific to the document.
The document destructor is called.
In an SDI application step 1 is performed once when the
document is first created. Then, step 2 till 4 are performed repeatedly each time a new document is opened. The new document reuses the existing document object. Finally, step 5 is performed when the application ends. Whereas in an MDI application, steps 1 till 4 are performed each time a new document is created. This is because an MDI application can have multiple documents opened at the same time.
SINGLE DOCUMENT INTERFACE VS. MULTIPLE DOCUMENT INTERFACE
SDI applications allow only one open document frame window at a time. MDI applications allow multiple document frame windows to be open in the same instance of an application. An MDI application has a window within which multiple MDI child windows, which are frame windows themselves, can be opened, each containing a separate document. In some applications, the child windows can be of different types, such as chart windows and spreadsheet windows. In such cases, the menu bar can change as MDI child windows of different types are activated.
In SDI you can work only with one document where the data of the application is stored. So you can store only one set of data of an application program and save it. If you now open a new document with New option in the File menu then the previously stored values of the data are lost and a document to contain the new storage is opened. In MDI applications these limitations have been overcome. You can store and retrieve different sets of data, which are stored in different files for the same application program.
All the facilities that are provided by the SDI are also provided by the MDI. Only if your application is a simple one and can be solved by using SDI, should you go ahead with SDI. For the sake of simplicity, the application can be solved by using MDI but it will be less efficient.
CEditView CLASS
A CEditView object is a view that, like the CEdit class, provides the functionality of a Windows edit control and can be used to implement simple text editor functionality. The CEditView class provides the following additional functions:
Printing
Find and replace
Because class CEditView is a derivative of class CView, objects of class CEditView can be used with documents and document templates. Each CEditView control's text is kept in its own global memory object. Your application can have any number of CEditView objects. Create objects of type CEditView if you want an edit window with the added functionalities listed above, or if you want simple text editor functionality.
A CEditView object can occupy the entire client area of a window. Derive your own classes from CEditView to add or modify the basic functionality, or to declare classes that can be added to a document template. The default implementation of class CEditView handles the following commands:
ID_EDIT_SELECT_ALL
ID_EDIT_FIND
ID_EDIT_REPLACE
ID_EDIT_REPEAT
ID_FILE_PRINT
Objects of type CEditView (or of types derived from CEditView) have the following limitations:
CEditView does not implement true WYSIWYG (what you see is what you get) editing. Where there is a choice between readability on the screen and matching printed output, CEditView opts for screen readability.
CEditView can only display text in a single font. No special character formatting is supported.
The amount of text a CEditView can contain is limited. The limits are the same as for the CEdit control.
CREATING AN SDI APPLICATION
As discussed earlier, an SDI is a user interface architecture that allows the user to work on only one application at a time. For example, WordPad. An SDI application can be basically created in four main steps. They are:
Creation of view class (CView)
Creation of a document class (CDocument)
Creation of application class ( CWinApp)
Creation of mainframe class (CMainFrame)
Consider the following code for creating an SDI application:
The CWinApp class handles the ID_FILE_NEW and ID_FILE_OPEN messages. The OnFileNew and OnFileOpen member functions of the CWinApp class handle these messages, respectively.
The PreCreateWindow function of the CWnd class has to be overridden in order to set the style of the view. This function takes the CreateStruct structure as the parameter and the style elements of the structure defines the style of the frame of the view. The function returns zero if it fails to set the style of the window. A number of styles can be ORed to obtain the required style.
After defining the view a CDocument derived class is created. This class must be created dynamically at the runtime. The implementation of this class is different from that of the CEditView. The DECLARE_SERIAL macro is used instead of the IMPLEMENT_DYNCREATE macro for the class to be dynamically created. This is because the macro generates the header code necessary to serialize a CObject derived class. This macro includes all the functionality of the DECLARE_DYNCREATE macro.
The IMPLEMENT_SERIAL macro must also be included along with the DECLARE
_DYNCREATE macro when the class is being implemented. This macro generates the code necessary for a dynamic CObject object derived class with runtime access to the class name and position within the hierarchy. This macro takes two parameters-one is the name of the class being serialized and the second is the base class of the class being serialized.
The SerializeRaw member function of the CEditView is called to enable the CArchive object to read or write text in the CEditView object to a text file.
The OnNewDocument function of the CDocument class handles the opening of the new document. This function returns 1 if successful. Otherwise it returns 0.
DeleteContents, the member function of the CDocument class, is called to delete the contents of a document so that the document object can be reused. You need to override this function if you want to delete the document's data without destroying the document object itself so that it can be reused. The DeleteContents member function is called by default every time the document is opened or created. The default implementation of the function, however, does nothing. Therefore, this function is overridden to delete the data in the document.
After creating the view a pointer to the view is stored in the object mview. The function GetCount member of the CObList is used to count the number of object addresses stored in the list mview. CObList is a class that supports ordered lists of non-unique CObject pointers that can be accessed sequentially or by pointer value. GetHead returns the first element of the list. Here, this would be a pointer to the view that is currently active. The return type is of type CObject and has to be typecast suitably. The SetWindowText member function of the CWnd class sets the window's text to NULL.
After creating the document and the view the next step is to create a CWinApp derived class. The InitInstance member function of the class needs to be overridden to initialize each new instance of the application running under Windows. It is in this function that the main window object is created. The return value of the function is a non-zero value if the initialization is successful. Otherwise it returns a zero.
CSingleDocTemplate CLASS
The CSingleDocTemplate class defines a document template that implements the single document interface (SDI). An SDI application uses the main frame window to display a document; only one document can be open at a time.
A document template defines the relationship between the following three types of classes:
A document class, which you derive from CDocument.
A view class, which displays data from the document class listed above. You can derive this class from CView, CScrollView, CFormView, or CEditView. (You can also use CEditView directly.)
A frame window class, which contains the view. You can derive this class from CFrameWnd for an SDI document template. If you do not need to customize the behavior of the main frame window, you can use CFrameWnd directly without deriving your own class.
An SDI application typically supports one type of document, so it has only one SingleDocTemplate object. Only one document can be open at a time. You do not need to call any member functions of CSingleDocTemplate except the constructor. The framework handles CSingleDocTemplate objects internally.
The constructor of the CSingleDocTemplate takes four parameters:
The ID of the resource used within the document type.
A pointer to the CRuntimeClass object of the document class.
A pointer to the CRuntimeClass of the frame window class.
A pointer to the CRuntimeClass of the view class.
In an SDI application the frame window is a CFrameWnd derived class itself.
The last step is to let the framework know about the document class. This can be done by adding the document template to the list of available templates that the document maintains, using the AddDocTemplate function. The CSingleDocTemplate class provides the document template in our application. A pointer to the object of this class is passed as a parameter to this function. This function ensures smooth interaction between the document, the window and the view class. After this a call is made to the OnFileNew member function in order to open a new document when the application starts. This is essential because the application cannot start if no document is open.
MULTIPLE DOCUMENT INTERFACE
A multiple document interface is a user interface architecture that enables the user to work with more than one document at the same time. Each time the document is displayed within the client area of the application's main window. An MDI child window looks very much like a typical frame window except that it appears within the MDI frame window rather than on the Desktop as in case of an SDI application. MDI child windows do not have a menu bar of their own. They share the menu of the MDI frame window. The framework automatically changes the MDI frame menu depending on the currently active MDI child window.
CMDIFrameWnd CLASS
The CMDIFrameWnd class provides the functionality of a Windows multiple document interfaces (MDI) frame window, alongwith members for managing the window.
The CMDIFrameWnd class inherits much of its default implementation from CFrameWnd. The CMDIFrameWnd class has the following additional features:
An MDI frame window manages the MDICLIENT window, repositioning it in conjunction with control bars. The MDI client window is the direct parent of MDI child frame windows. The WS_HSCROLL and WS_VSCROLL window styles specified on a CMDIFrameWnd apply to the MDI client window rather than the main frame window so the user can scroll the MDI client area (as in the Windows Program Manager, for example).
An MDI frame window owns a default menu that is used as the menu bar when there is no active MDI child window. When there is an active MDI child, the MDI frame window's menu bar is automatically replaced by the MDI child window menu.
An MDI frame window works in conjunction with the current MDI child window, if there is one. For instance, command messages are delegated to the currently active MDI child before the MDI frame window.
An MDI frame window has default handlers for the following standard Window menu commands:
ID_WINDOW_TILE_VERT
ID_WINDOW_TILE_HORZ
ID_WINDOW_CASCADE
ID_WINDOW_ARRANGE
An MDI frame window also has an implementation of ID_WINDOW_NEW, which creates a new frame and view on the current document. An application can override these default command implementations to customize MDI window handling.
You can construct an MDI frame window by calling the Create or LoadFrame member function of CFrameWnd.
CMDIChildWnd CLASS
The CMDIChildWnd class provides the functionality of an MDI child window, along with members for managing the window.
An MDI child window looks much like a typical frame window, except that the MDI child window appears inside an MDI frame window rather than on the Desktop. An MDI child window does not have a menu bar of its own, but instead shares the menu of the MDI frame window. The framework automatically changes the MDI frame menu to represent the currently active MDI child window.
The CMDIChildWnd class inherits much of its default implementation from CFrameWnd. The CMDIChildWnd class has the following additional features:
In conjunction with the CMultiDocTemplate class, multiple CMDIChildWnd objects from the same document template share the same menu, saving Windows system resources.
The currently active MDI child window menu entirely replaces the MDI frame window's menu, and the caption of the currently active MDI child window is added to the MDI frame window's caption.
MULTIPLE VIEWS
Many documents require only a single view, but it is possible to support more than one view of a single document. When you watch the same document in different views and you change one of the views it then becomes compulsory that all the views should be updated. This is done to ensure consistency remains in all the views and its corresponding documents.
For this two member functions of CView are used:
UpdateAllViews ();
OnUpdate();
To help you implement multiple views, a document object keeps a list of its views, provides member functions for adding and removing views, and supplies the UpdateAllViews member function for letting multiple views know when the document's data has changed.
Usually when you change one view the change goes to the document and the function UpdateAllView() is called automatically. Then the function OnUpdate() updates the remaining views of the document.
MFC supports three common user interfaces requiring multiple views on the same document. These models are:
View objects of the same class, each in a separate MDI document frame window.
You might want to support creating a second frame window on a document. The user could choose a New Window command to open a second frame with a view of the same document and then use the two frames to view different portions of the document simultaneously. The framework supports the New Window command on the Window menu for MDI applications by duplicating the initial frame window and view attached to the document.
View objects of the same class in the same document frame window.
Splitter windows split the view space of a single document window into multiple separate views of the document. The framework creates multiple view objects from the same view class.
View objects of different classes in a single frame window.
In this model, which is a variation of the splitter window, multiple views share a single frame window. The views are constructed from different classes, each view providing a different way to view the same document. For example, one view might show a word processing document in normal mode while the other view shows it in outline mode. A splitter control allows the user to adjust the relative sizes of the views.
CREATING A MDI APPLICATION
An MDI application allows the user to open multiple documents at the same time. The creation of an MDI application is similar to the creation of an SDI application with a few differences.
The CWinApp class handles the ID_FILE_NEW and ID_FILE_OPEN messages. The OnFileNew and OnFileOpen member functions of the CWinApp class handle these messages, respectively.
Three pointers to the RuntimeClass objects are created. These are of type CDocumnet, CMDIChildWnd and CEditView. These pointers are passed as parameters to the constructor of the CMultiDocTemplate alongwith the ID of the resource used with the document.
CMultiDocTemplate CLASS
The CMultiDocTemplate class defines a document template that implements the multiple document interface (MDI). An MDI application uses the main frame window as a workspace in which the user can open one or more document frame windows, each of which displays a document.
A document template defines the relationships among the following three types of classes:
A document class, which you derive from CDocument.
A view class, which displays data from the document class listed above. You can derive this class from CView, CScrollView, CFormView or CEditView. (You can also use CEditView directly.)
A frame window class, which contains the view. For an MDI document template, you can derive this class from CMDIChildWnd, or, if you do not need to customize the behavior of the document frame windows, you can use CMDIChildWnd directly without deriving your own class.
An MDI application can support more than one type of document, and documents of different types can be open at the same time. Your application has one document template for each document type that it supports. For example, if your MDI application supports spreadsheets and text documents, the application has two CMultiDocTemplate objects.
The application uses the document template(s) when the user creates a new document. If the application supports more than one type of document, then the framework gets the names of the supported document types from the document templates and displays them in a list in the File New dialog box. Once the user has selected a document type, the application creates a document class object, a frame window object and a view object, and attaches them to each other.
You do not need to call any member functions of CMultiDocTemplate except the constructor. The framework handles CMultiDocTemplate objects internally.
In the code segment we have already seen the CMultiDocTemplate object is passed as a parameter to the AddDocTemplate member function of the CWinApp class and a pointer to the object of the class CMDIFrameWnd is created. The MDI frame window is created using the LoadFrame member function of the CMDIFrameWnd class. The parameter to the LoadFrame function is the ID of the resource associated with the frame window.
A point to note here is that the ID of the menu resource passed as a parameter to the CMultiDocTemplate constructor is different from the one passed to the LoadFrame function. This is because an MDI frame window uses a menu owned by it when there is no active MDI child window. When there is an active MDI child, the menu of the MDI child window is used in place of the main frame window menu.
A call to the OnFileNew member function ensures that a document is open when the application starts. It is not necessary to call the OnFileNew member function. If no call is made to the OnFileNew member function the application framework window will be empty.
MULTITEMPLATE CONFIGURATION
The important concept in MDI applications is that we can load several views with several properties and this concept is called multitemplate configuration. There are many methods of configuring multitemplates. One of them is listed below:
Multitemplate registration: This is the method of registering several templates to App classes. The code in InitInstance has the template that is registered by executing the AddDocTemplate function. Usually one template is registered. If you register another record, document, frame or a view you must use the AddDocTemplate function again.
CMultiDocTemplate *pdoctemplate;
pdoctemplate=new CmultiDocTemplate(
AddDocTemplate(pdoctemplate); // this creates one template//
To find where the first template is located get the template that was registered for the product.
Syntax:
POSITION pos= GetFirstDocTemplatePosition();
We then obtain the template from the position with the following statement:
CDocTemplate *ptemplate;
ptemplate=GetNextDocTemplate(pos);
If you want to obtain the another view of the second template execute the GetNextDocTemplate function once more.
CDocTemplate *ptemplate;
ptemplate=GetNextDocTemplate(pso);
ptemplate=GetNextDocTemplate(pos);
After obtaining the desired template, you can create the frame by executing the member functions of the template OpenDocumentFile:
ptemplate->OpenDocumentFile(NULL);
A new child frame is created with the creation of a document and a view by executing the OpenDocumentFile() function.
@FormView CLASS
The CFormView class is the base class used for form views. A form view is essentially a view that contains controls. These controls are laid out based on a dialog template resource. Use CFormView if you want forms in your application. These views support scrolling, using the CScrollView functionality. When you create an application with the MFC AppWizard, you can base its view class on CFormView, making it a forms-based application.
You can also insert new forms into Documen View-based applications. Even if your application did not initially support forms, Visual C++ will add this support when you insert a new form. The MFC AppWizard and the New Form command are the preferred methods for creating forms-based applications.
CREATING THE FORM DIALOG RESOURCE
Creating a form based on CFormView is similar to creating a dialog box. You first have to design the dialog template using the dialog editor. The style of the dialog box that you create has to be set since the dialog is not a pop-up, as in the case of a dialog used with the CDialog class, but a child dialog. To change the style suitably, the following steps have to be executed:
Right-click on the dialog in the dialog editor. A short cut menu will be displayed.
Select the option Properties. The Dialog Properties dialog box will be displayed.
Click on the tab Style.
Select Child from the combo box Style.
Select None from the combo Box Border.
Clear the Titlebar checkbox.
Click on the tab More Styles.
Clear the visible check box.
These changes are necessary because a form view is not a true dialog box.
After creating the dialog, which is the most important resource of a forms-based application, the next step is to create the menu and the toolbar. Although this is not essential, it increases the user-friendliness of an application.
In the sample program we are going to create we shall make use of the toolbar resource in addition to the menu resource. The IDs for the dialog box, the various controls in the dialog box, the menu and the toolbar are given in the table below:
Form Component Identifier
Toolbar IDR_MAINFRAME
Menu IDR_MAINFRAME
Dialog box IDD_SAMPLE_FORM
Edit box for name ID_NAME
Edit box for address ID_ADDRESS
Edit box for phone number ID_PHONE
Radio button for sex (male) ID_MALE
Radio button for sex (female) ID_FEMALE
Combo box for salary ID_SALARY
Submit button ID_SUBMIT
Clear button ID_CLEAR
The FormView like any other MFC program requires a certain number of header files to be included in it. The first step in writing a program to display a form is to include these header files as shown below:
#include <afxwin.h>
#include <afxext.h>
#include <afxcmn.h>
#include "resource.h"
#include <stdio.h>
DEFINING THE DOCUMENT
The next step is to derive your own class from the CDocument class for storing the data that the user inputs, as shown in the sample code below:
class csample : public CDocument
protected:
DECLARE_DYNCREATE(csample)
public:
virtual void csample :: Serialize (CArchive &ar)
if(ar.IsStoring())
IMPLEMENT_DYNCREATE(csample, CDocument)
CREATING THE FORM VIEW
We have not mapped any messages for the derived class CSampleFormDoc. This is because all user interactions in the Document View architecture are done with the view and CFormView is no exception. When the user enters data or make changes to the data in the form dialog, the messages that are generated are directed to the view. The view should ensure that it responds to these messages suitably. For example, if the user enters the data in an edit box and tabs out, a message is generated, this message should be trapped so that the data entered in the edit box can be stored in a variable for later use. The data in the variable can be stored to a file using the Serialize function.
The DECLARE_DYNCREATE macro is used to enable objects of CObject derived classes, CDocument in our case, to be created dynamically at runtime. CDocument is derived from CCmdTarget which in turn is derived from the CObject class. After defining the document, the next step is to define the form view.
Consider the following code segment:
class csample: public CFormView
public:
csample():CFormView(IDD_SAMPLEFORM)
salary=0; // initialisation//
DECLARE_DYNCREATE(csample)
public:
void OnNameKillFocus();
void OnAddkillFocus();
void OnPhoneKillFocus();
void OnSalSelect();
void OnSalKillFocus();
void OnMaleClkd();
void OnFemaleClkd();
void OnClear();
void OnSubmit();
void OnExit();
protected:
virtual csample::~csample()
DECLARE_MESAGE_MAP()
IMPLEMENT_DYNCREATE(csample, CFormView)
In classes defined from the CFormView it is mandatory to define a void constructor and call the constructor of the base class, CFormView. This is necessary since the view makes use of the resource, the dialog box, which has to be loaded, when the view is created. The ID of the resource, IDD_SAMPLEFORM, is passed as the parameter to the constructor.
VALIDATING THE DATA ENTRY
When data is entered in a control, it is necessary to validate it. This can be done by trapping the messages generated when the user navigates the form. For example, when the user tabs or clicks out of control a KILLFOCUS message is generated. The message can be coded to determine whether the user has typed anything in the control that has been exited. In addition to the KILLFOCUS, many other messages are generated depending on where the user has made a selection from.
These messages have to be mapped to handle functions as shown in the code segment below:
ON_LBN_SELCHANGE is generated when a user makes a selection from the combo box.
ON_LBN_KILLFOCUS is generated when a user selects from a list and goes out.
ON_EN_KILLFOCUS is generated when a user tabs or clicks out of control
ON_EN_CHANGE is generated when a user makes a change to the list box
The text data that is stored in the dialog control can be retrieved using the GetDlgitemtext function and storing it in a CString type of variable. IsEmpty, a member function of the CString, is invoked to determine whether the variable has any contents or not. This function returns a non-zero value if the CString object is empty. Otherwise it returns zero.
Another function Setfocus can also be used to set focus on a particular dialog box. This is a member function of the CWnd class. It returns control to the dialog control for which it is invoked and requires a pointer to the control to which focus has to be set. The GetDlgItem determines a pointer to the control.
Consider the following sample code:
void csample :: OnNameKillFocus()
GetDlgItemText(ID_NAME, csname); //csname is globally dafined//
if(csname.IsEmpty())
MessageBox("cannot be left empty");
CEdit *ceptr;
ceptr=(CEdit*)GetDlgItem(ID_NAME);
ceptr->SetFocus();
void csample :: OnAddKillFocus()
GetDlgItemText(ID_ADDRESS, csadd); //csadd is globally dafined//
if(csadd.IsEmpty())
MessageBox("cannot be left empty");
CEdit *ceptr;
ceptr=(CEdit*)GetDlgItem(ID_ADDRESS);
ceptr->SetFocus();
The last function performs an empty check on the address edit box.
ACCESSING DATA FROM A COMBO BOX
A combo box consists of a list box combined with either a static control or an edit control. The list box portion of the control may be displayed at all times or may only drop-down when the user selects the drop-down arrow next to the control.
The currently selected item (if any) in the list box is displayed in the static or edit control. In addition, if the combo box has the drop-down list style, the user can type the initial character of one of the items in the list. The list box, if visible, will highlight the next item with that initial character.
The following table compares the three combo box styles.
Style When is list box visible? Static or edit control?
Simple Always Edit
Drop-down When dropped down Edit
Drop-down list When dropped down Static
Table 11.2: Comparison of various combo box styles
The ON_LBN_SELCHANGE message occurs when the user makes a selection from the drop-down list. The second message ON_LBN_FOCUS is generated when the user tabs out or clicks out of a combo box. Consider the following sample code
void csample::OnSalSelect()
int sel;
CComboBox *cptr;
cptr=(CComboBox*)GetDlgItem(ID_SALARY);
sel=cptr->GetCurSel(); // gets the number of item selected from the combo box drop down list//
CString cs;
cptr->GetLBText(sel, cs); //gets the text out of the specif
ied combo box drop down list entry//
sscanf(cs, "%d", &Salary); // converts str into integer (can also use atoi())
To make use of sscanf the header file stdio.h has to be included.
void csample::OnSalKillFocus()
Salary=GetDlgItemInt(ID_SALARY, NULL, TRUE);
This function handles input into the text box area.
ACCESSING A RADIO BUTTON OR A CONTROL BUTTON (PUSH BUTTON OR A CHECK BOX)
The message ON_BN_CLICKED is generated whenever the user clicks on a radio button or a control box.
When the user selects the male and female radio buttons the following happens:
void csample :: OnMaleClkd()
cssex= "M";
void csample :: OnMaleClkd()
cssex= "F";
When the user clicks on the clear button the following code happens:
void csample :: OnClear()
SetDlgItemText(ID_NAME, NULL);
SetDlgItemText(ID_ADDRESS, NULL);
SetDlgItemText(ID_PHONE, NULL);
SetDlgItemText(ID_SALARY, NULL);
UpdateData(FALSE);
When the user clicks on the menu option exit the following code happens.
void csample::OnExit()
PostQuitMessage(0);
class frame:public CFrameWnd
protected:
DECLARE_DYNCREATE(frame);
CStatusBar mstatus;
CToolBar mtool;
int OnCreate(LPCREATESTRUCT LPCREATESTRUCT);
DECLARE_MESSAGE_MAP()
IMPLEMENT_DYNCREATE(frame, CFrameWnd)
BEGIN_MESSAGE_MAP(frame, CFrameWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()
In the above code segment, the OnCreate function has been overridden and a message map entry has been added to invoke the default handler OnCreate
static UINT indicators->=
ID_SEPERATOR; \\ status line indicator\\
ID_INDICATOR_CAPS;
ID_INDICATOR_NUM;
ID_INDICATOR_SCRL;
TheOnCreate function loads the toolbar resource IDR_MAINFRAME and customizes the status bar using the status line indicators defined in the array indicators. The bar style is set using the SetbarStyle function as shown below:
int frame:: OnCreate (LPCREATESTRUCT lpcreatestruct)
if (CframeWnd:: OnCreate (LPCREATESTRUCT lpcreatestruct)== -1)
This function is called to set the desired CBRS_ styles for the control bar. Does not affect the WS_ (window style) settings.
Syntax:
void SetBarStyle( DWORD dwStyle );
dwStyle is the desired styles for the control bar. It can be one or more of the following:
CBRS_ALIGN_TOP allows the control bar to be docked to the top of the client area of a frame window.
CBRS_ALIGN_BOTTOM allows the control bar to be docked to the bottom of the client area of a frame window.
CBRS_ALIGN_LEFT allows the control bar to be docked to the left side of the client area of a frame window.
CBRS_ALIGN_RIGHT allows the control bar to be docked to the right side of the client area of a frame window.
CBRS_ALIGN_ANY allows the control bar to be docked to any side of the client area of a frame window.
CBRS_BORDER_TOP causes a border to be drawn on the top edge of the control bar when it would be visible.
CBRS_BORDER_BOTTOM causes a border to be drawn on the bottom edge of the control bar when it would be visible.
CBRS_BORDER_LEFT causes a border to be drawn on the left edge of the control bar when it would be visible.
CBRS_BORDER_RIGHT causes a border to be drawn on the right edge of the control bar when it would be visible.
CBRS_FLOAT_MULTI allows multiple control bars to be floated in a single miniframe window.
CBRS_TOOLTIPS causes tool tips to be displayed for the control bar.
CBRS_FLYBY causes message text to be updated at the same time as tool tips.
CBRS_GRIPPER causes a gripper, similar to that used on bands in a CReBar object, to be drawn for any CControlBar-derived class.
GetBarStyle FUNCTION
This function is called to determine which CBRS_ (control bar styles) settings are currently set for the control bar. Does not handle WS_ (window style) styles. The return value is the current CBRS_ (control bar styles) settings for the control bar.
Syntax:
DWORD GetBarStyle( );
EnableDocking and DockControlBar FUNCTION
EnableDocking is called to enable a control bar to be docked. The sides specified must match one of the sides enabled for docking in the destination frame window, or the control bar cannot be docked to that frame window.
Syntax:
void EnableDocking( DWORD dwStyle );
dwStyle specifies whether the control bar supports docking and the sides of its parent window to which the control bar can be docked, if supported. It can be one or more of the following:
CBRS_ALIGN_TOP allows docking at the top of the client area.
CBRS_ALIGN_BOTTOM allows docking at the bottom of the client area.
CBRS_ALIGN_LEFT allows docking on the left side of the client area.
CBRS_ALIGN_RIGHT allows docking on the right side of the client area.
CBRS_ALIGN_ANY allows docking on any side of the client area.
CBRS_FLOAT_MULTI allows multiple control bars to be floated in a single miniframe window.
If it is 0 (that is, indicating no flags), the control bar will not dock.
DockControlBar causes a control bar to be docked to the frame window. The control bar will be docked to one of the sides of the frame window specified in the calls to both CControlBar::EnableDocking and CFrameWnd::EnableDocking. The side chosen is determined by nDockBarID.
NDockBarID determines which sides of the frame window to consider for docking. It can be 0, or one or more of the following:
AFX_IDW_DOCKBAR_TOP docks to the top side of the frame window.
AFX_IDW_DOCKBAR_BOTTOM docks to the bottom side of the frame window.
AFX_IDW_DOCKBAR_LEFT docks to the left side of the frame window.
AFX_IDW_DOCKBAR_RIGHT docks to the right side of the frame window.
If it is 0, the control bar can be docked to any side enabled for docking in the destination frame window.
LpRect determines, in screen coordinates, where the control bar will be docked in the non-client area of the destination frame window.
CREATING THE APPLICATION OBJECT
Finally, like in all MFC applications you have to derive your own CWinApp class and create an object of the class.
class csample: public CWinApp
public:
virtual BOOL InitInstance();
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(csample, CWinApp)
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
END_MESSAGE_MAP()
csample theapp;
The RUNTIME_CLASS macro and the CSingleDocTemplate function need to be used to create a document template. The document template is then added to the list of available document templates that the application maintains using the CWinApp function AddDocTemplate. A new document is created based on the template using the OnFileNew function. In order to enable the OnFileNew function, you must add the ON_COMMAND (ID_FILE_NEW, OnFileNew) statement to your CWinApp class message map.
BOOL csample :: InitInstance()
CSingleDocTemplate *pdoc;
pdoc= new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(csampleformdoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(csample));
AddDocTemplate(pdoc);
OnFileNew();
return TRUE;
STORING THE CONTENTS OF THE FORM
In an editing session the user can enter sets of information before saving the information on the disk. The information has to be stored somewhere until it is saved on the disk. We shall now write a message handler for the submit button so that every time the user completes entering data into the form and clicks on submit, the data will be stored in a linked list. The data can then be stored to the disk using the Serialize function.
In the function OnSubmit the details from the form are concatenated and stored in a single CString variable. The colon is used as a field seperator and the | used as a record separator. The function sprintf is used to convert the numeric data stored in the variable salary to character datatype.
SAVING THE CONTENTS TO A FILE
The serialize function has to be modified in order to save the contents of the linked list to a file.
A Win32-based application consists of one or more processes. A process, in the simplest of terms, is an executing program. One or more threads run in the context of the process. A thread is the basic unit to which the operating system allocates processor time. A thread can execute any part of the process code, including parts currently being executed by another thread. A fiber is a unit of execution that must be manually scheduled by the application. Fibers run in the context of the threads that schedule them.
A job object allows groups of processes to be managed as a unit. Job objects are namable, securable, sharable objects that control attributes of the processes associated with them. Operations performed on the job object affect all processes associated with the job object.
The Microsoft Foundation Class Library (MFC) provides support for multithreaded applications. A "process" is an executing instance of an application. For example, when you double-click on the Notepad icon, you start a process that runs Notepad.
A thread is a path of execution within a process. When you start Notepad, the operating system creates a process and begins executing the primary thread of that process. When this thread terminates, so does the process. This primary thread is supplied to the operating system by the startup code in the form of a function address. Usually, it is the address of the main or WinMain function that is supplied.
You can create additional threads in your application if you wish. You may want to do this to handle background or maintenance tasks that do not require the user to wait till they are completed. CWinThread represents all threads in MFC applications objects. In most situations, you do not even have to explicitly create these objects; instead call the framework helper function AfxbeginThread, which creates the CWinThread object for you.
MFC distinguishes two types of threads:
User interface threads
Worker threads
User interface threads are commonly used to handle user input and respond to events and messages generated by the user. Worker threads are commonly used to complete tasks such as recalculation that do not require user input. The Win32 API does not distinguish between types of threads; it just needs to know the thread's starting address so it can begin to execute the thread. MFC handles user interface threads specially by supplying a message pump for events in the user interface. CWinApp is an example of a user interface thread object, as it derives from CWinThread and handles events and messages generated by the user.
User interface threads are commonly used to handle user input and respond to events and messages generated by the user. Worker threads are commonly used to complete tasks such as recalculation that do not require user input. The Win32 API does not distinguish between types of threads; it just needs to know the thread's starting address so it can begin to execute the thread. MFC handles user interface threads specially by supplying a message pump for events in the user interface. CWinApp is an example of a user interface thread object, as it derives from CWinThread and handles events and messages generated by the user.
Special attention should be given to situations where more than one thread may require access to the same object. As writing and debugging multithreaded programming is inherently a complicated and tricky undertaking, you must ensure that objects are not accessed by more than one thread at a time.
SINGLE -THREADED APPLICATIONS
Applications that have only one thread are called single-threaded applications. This thread is also known as the main thread of the application. In these applications all the processing is done in a linear manner. When there are no messages received by the system, background tasks cannot be done during that time since there is only one thread. If the applications are not able to switch effectively, then switch between various independent tasks. Such applications take more time to be executed.
MULTITHREADED APPLICATIONS
Applications that have more than one thread are called multithreaded applications. These types of threads are created when the user wants to work on more than one task at a time. Such applications can make full use of resources even if the application is running in a multiprocessor environment. A well-designed multithreaded application runs more effectively than a normal application since different activities can be done at the same time using multiple threads. Examples are: printing multiple copies of a document while you continue to edit the document, recalculating cell entries automatically when a cell value is updated, etc.
FUNCTIONING OF THREADS
A thread is basically a path of execution through a program. It is also the smallest unit of execution that Win32 schedules. A thread consists of a stack, the state of the CPU registers, and an entry in the execution list of the system scheduler. Each thread shares all resources of the processes.
A process consists of one or more threads and the code, data and other resources of a program in memory. Typical program resources are open files and dynamically allocated memory. A program executes when the system scheduler gives execution control to one of its threads. The scheduler determines which threads should run and when they should run. Threads of lower priority may have to wait while higher priority threads complete their tasks. On multiprocessor machines, the scheduler can move individual threads to different processors to "balance" the CPU load.
Each thread in a process operates independently. Unless you make them visible to each other, the threads execute individually and are unaware of the other threads in a process. Threads sharing common resources, however, must coordinate their work by using semaphores or another method of interprocess communication.
A thread can only access the object it creates i.e. every object created becomes local to the thread in which it is created. Changes made by one thread to the application are not reflected in the other threads. Also, a thread must acquire a lock on the resource before processing, so that the integrity of an application resource can be maintained. MFC provides many facilities for locking and related issues which are automatically taken care of while the threads are being used.
CREATING WORKER THREADS
A worker thread is commonly used to handle background tasks that the user should not have to wait for to continue using an application. Tasks such as recalculation and background printing are good examples of worker threads.
Programming includes two steps:
Creating the thread: The AfxBeginThread function is called to create threads.
Implementing the controlling function: This function specifies the code to be executed when the thread starts.
There are two overloaded versions of AfxBeginThread: one for user interface threads and one for worker threads. To begin execution of your worker thread, call AfxBeginThread providing the following information:
The address of the controlling function.
The parameter to be passed to the controlling function.
(Optional) The desired priority of the thread. The default is normal priority.
(Optional) The desired stack size for the thread. The default is the same size stack as the creating thread.
(Optional) CREATE_SUSPENDED if you want the thread to be created in a suspended state. The default is 0, or starts the thread normally.
(Optional) The desired security attributes. The default is the same access as the parent thread.
AfxBeginThread creates and initializes a CWinThread object for you, starts it, and returns its address so you can refer to it later. Checks are made throughout the procedure to make sure all objects are deallocated properly should any part of the creation fail.
The function returns a pointer to the newly created thread object. This pointer can be used to suspend or resume the application while your application is running. The CWinThread member functions SuspendThread and ResumeThread can be used to suspend and resume threads. The pointer can also be used to suspend and resume the thread.
SetThreadPriority FUNCTION
Every thread has a base priority level determined by the thread's priority value and the priority class of its process. The system uses the base priority level of all executable threads to determine which thread gets the next slice of CPU time. Threads are scheduled on a priority basis, and only when there are no executable threads at a higher level does scheduling of threads at a lower level take place.
The SetThreadPriority function enables setting the base priority level of a thread relative to the priority class of its process. For example, specifying THREAD_PRIORITY_HIGHEST in a call to SetThreadPriority for a thread of an IDLE_PRIORITY_CLASS process sets the thread's base priority level to 6.
For IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, and HIGH_PRIORITY_CLASS processes, the system dynamically boosts a thread's base priority level when events occur that are important to the thread. REALTIME_PRIORITY_CLASS processes do not receive dynamic boosts.
All threads initially start at THREAD_PRIORITY_NORMAL. The GetPriorityClass and SetPriorityClass are used to get and set the priority class of a process.
The priority class of a process is used to differentiate between applications that are time-bound and those that have normal or below normal scheduling requirements. The thread priority values are used to differentiate the relative priorities of the tasks of a process. For example, a thread that handles input for a window could have a higher priority level than a thread that performs intensive calculations for the CPU.
The SetThreadPriority function sets the priority value for the specified thread. This value, together with the priority class of the thread's process, determines the thread's base priority level.
Syntax:
BOOL SetThreadPriority(
HANDLE hThread, // handle to the thread
int nPriority // thread priority level
hThread is the handle to the thread whose priority value is to be set.
In case of Windows NT the handle must have the THREAD_SET_INFORMATION access right associated with it.
nPriority specifies the priority value for the thread. This parameter can be one of the following values:
Priority
Meaning
THREAD_PRIORITY_ABOVE_NORMAL
Indicates 1 point above normal priority for the priority class.
THREAD_PRIORITY_BELOW_NORMAL
Indicates 1 point below normal priority for the priority class.
THREAD_PRIORITY_HIGHEST
Indicates 2 points above normal priority for the priority class.
THREAD_PRIORITY_IDLE
Indicates a base priority level of 1 for IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, or HIGH_PRIORITY_CLASS processes, and a base priority level of 16 for REALTIME_PRIORITY_CLASS processes.
THREAD_PRIORITY_LOWEST
Indicates 2 points below normal priority for the priority class.
THREAD_PRIORITY_NORMAL
Indicates normal priority for the priority class.
THREAD_PRIORITY_TIME_CRITICAL
Indicates a base priority level of 15 for IDLE_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, or HIGH_PRIORITY_CLASS processes, and a base priority level of 31 for REALTIME_PRIORITY_CLASS processes.
Return Values
If the function succeeds, the return value is non-zero.
If the function fails, the return value is zero.
When manipulating priorities, be very careful to ensure that a high-priority thread does not consume all of the available CPU time. A thread with a base priority level above 11 interferes with the normal operation of the operating system. Using REALTIME_PRIORITY_CLASS may cause disk caches to not flush, hang the mouse, and so on.
A thread with a priority level of THREAD_PRIORITY_TIME_CRITICAL will execute until it explicitly yields processing to other threads. Processing of these threads is not yielded to other threads with the THREAD_PRIORITY_TIME_CRITICAL priority level. Such a thread can interfere with the normal operation of the operating system if the thread does not explicitly and quickly yield processing.
CONTROLLING FUNCTION
The controlling function defines the thread. When this function is entered, the thread starts, and when it exits, the thread terminates. This function should have the following prototype:
UINT MyControllingFunction( LPVOID pParam );
The parameter is a single 32-bit value. The value the function receives in this parameter is the value that was passed to the constructor when the thread object was created. The controlling function can interpret this value in any manner it chooses. It can be treated as a scalar value, or a pointer to a structure containing multiple parameters, or it can be ignored. If the parameter refers to a structure, the structure can be used not only to pass data from the caller to the thread, but also to pass data back from the thread to the caller.
If you use such a structure to pass data back to the caller, the thread will need to notify the caller when the results are ready
When the function terminates, it should return a UINT value indicating the reason for termination. Typically, this exit code is 0 to indicate success with other values indicating different types of errors. This is purely dependent on implementation. Some threads may maintain usage counts of objects, and return the current number of uses of that object.
This example shows how to define a controlling function and use it from another portion of the program.
UINT MyThreadProc( LPVOID pParam )
CMyObject* pObject = (CMyObject*)pParam;
if (pObject == NULL ||
!pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))
return 1; // if pObject is not valid
// do something with 'pObject'
return 0; // thread completed successfully
// inside a different function in the program
pNewObject = new CMyObject;
AfxBeginThread(MyThreadProc, pNewObject);
A user interface thread is commonly used to handle user input and respond to user events independently of threads executing other portions of the application. The main application thread (provided in your CWinApp-derived class) is already created and started for you.
The first thing you must do when creating a user interface thread is derive a class from CWinThread. You must declare and implement this class, using the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros. This class must override some functions.
The functions and what they should do are presented in the following list.
Function Name Purpose
ExitInstance Performs clean-up when a thread terminates. Usually overridden.
InitInstance Performs thread instance initialization. Must be overridden.
OnIdle Performs thread-specific idle-time processing. Not usually overridden.
PreTranslateMessage Filters messages before they are dispatched to TranslateMessage and DispatchMessage. Not usually overridden.
ProcessWndProcException Intercepts unhandled exceptions thrown by the thread's message and command handlers. Not usually overridden.
Run Controlling function for the thread. Contains the message pump. Rarely overridden.
There are two overloaded versions of AfxBeginThread-one for user interface threads and one for worker threads. To begin the execution of your worker thread, call AfxBeginThread providing the following information:
The address of the controlling function.
The parameter to be passed to the controlling function.
(Optional) The desired priority of the thread. The default is normal priority.
(Optional) The desired stack size for the thread. The default is the same size stack as the creating thread.
(Optional) CREATE_SUSPENDED if you want the thread to be created in a suspended state. The default is 0, or starts the thread normally.
(Optional) The desired security attributes. The default is the same access as the parent thread.
AfxBeginThread creates and initializes a CWinThread object for you, starts it, and returns its address so you can
refer to it later. Checks are made throughout the procedure to make sure all objects are deallocated properly should any part of the creation fail.
TERMINATING THREADS
Two normal situations cause a thread to terminate: The controlling function exits or the thread is not allowed to run to completion. If a word processor used a thread for background printing, the controlling function would terminate normally if printing is successfully completed. Should the user wish to cancel the printing, however, the background printing thread would have to be terminated prematurely.
NORMAL THREAD TERMINATION
For a worker thread, normal thread termination is simple: Exit the controlling function and return a value that signifies the reason for termination. You can use either the AfxEndThread function or a return statement. Typically, 0 signifies successful completion, but that is up to you.
For a user interface thread the process is just as simple. From within the user interface thread call :PostQuitMessage. The only parameter that ::PostQuitMessage takes is the exit code of the thread. As for worker threads, 0 typically signifies successful completion.
PREMATURE THREAD TERMINATION
Terminating a thread prematurely is almost as simple. Just call AfxEndThread from within the thread and pass the desired exit code as the only parameter. This stops the execution of the thread, deallocates the thread's stack, detaches all DLLs attached to the thread and deletes the thread object from memory.
AfxEndThread must be called from within the thread to be terminated. If you want to terminate one thread from another, you must set up a communication method between the two threads.
RETRIEVING THE EXIT CODE OF A THREAD
To get the exit code of either the worker or the user interface thread, call the ::GetExitCodeThread function. This function takes the handle to the thread (stored in the m_hThread data member of CWinThread objects) and the address of a DWORD.
If the thread is still active, ::GetExitCodeThread will place STILL_ACTIVE in the supplied DWORD address; otherwise, the exit code is placed in this address.
The following code illustrates retrieving the exit code of a thread:
CWinThread *cwthread;
DWORD word;
Cwthread=AfxBeginThread(mythread, GetSafeHwnd());
::GetExitCodeThread(cwthread
mthread, &word);
if(word == STILL_ACTIVE)
MessageBox(0, "Thread is still alive", "Information", MB_OK);
MessageBox(0, "Thread is completed", "Information", MB_OK);
Retrieving the exit code of CWinThread objects goes a step further. By default, when a CWinThread thread terminates, the thread object is deleted. This means you cannot access the m_hThread data member because the CWinThread object no longer exists. To avoid this situation, do one of the following two things:
Set the m_bAutoDelete data member to FALSE. This allows the CWinThread object to survive after the thread has been terminated. You can then access the m_hThread data member after the thread has been terminated. If you use this technique, however, you are responsible for destroying the CWinThread object as the framework will not automatically delete it for you. This is the preferred method.
Store the thread's handle separately. After the thread is created, copy its m_hThread data member (using ::DuplicateHandle) to another variable and access it through that variable. This way the object is deleted automatically upon termination and you can still find out why the thread terminated. Be careful that the thread does not terminate before you can duplicate the handle. The safest way to do this is to pass CREATE_SUSPENDED to AfxBeginThread, store the handle, and then resume the thread by calling Resume Thread.
Either method allows you to determine why a CWinThread object terminated.
ExitThread Function
Another function called ExitThread can also be used to end a thread. This function does not return a value. ExitThread is the preferred method of exiting a thread. When this function is called (either explicitly or by returning from a thread procedure), the current thread's stack is deallocated and the thread terminates. The entry point function of all attached Dynamic Link Libraries (DLLs) is invoked with a value indicating that the thread is detaching from the DLL. If the thread is the last thread in the process when this function is called, the thread process is also terminated. The state of the thread object is signaled, releasing any other threads that had been waiting for the thread to terminate. The thread's termination status changes from STILL_ACTIVE to the value of the dwExitCode parameter.
Syntax:
VOID ExitThread(
DWORD dwExitCode // exit code for this thread
dwExitCode specifies the exit code for the calling thread. Use the GetExitCodeThread function to retrieve a thread's exit code.
Terminating a thread does not necessarily remove the thread object from the operating system. A thread object is deleted when the last handle to the thread is closed.
The ExitProcess, ExitThread, CreateThread, CreateRemoteThread functions, and a process that is starting (as the result of a CreateProcess call) are serialized between each other within a process. Only one of these events can happen in an address space at a time. This means the following restrictions hold:
During process startup and DLL initialization routines, new threads can be created, but they do not begin execution until DLL initialization is done for the process.
Only one thread in a process can be in a DLL initialization or detach routine at a time.
ExitProcess returns only when there are no more threads in the DLL initialization or detach routines.
A thread that uses functions from the C runtime libraries should use the _beginthread and _endthread C runtime functions for thread management rather than CreateThread and ExitThread. Failure to do so results in small memory leaks when ExitThread is called.
@RDBMS AND FEATURES OF OBJECT DATABASE CONNECTIVITY (ODBC)
A relational database management system is a collection of related information organized into one or two tables. Tables are visually represented as a two-dimensional array, with rows representing record ands columns representing fields. The records within a table are identical to each other in structure and in the type of data they contain. Once the table is filled with data numerous operations such as addition, deletion and modification can be performed on a table.
FEATURES OF ODBC
In traditional database terminology the term application means a program that can perform a specific database task for specific applicationS such as payroll, financial analysis or inventory management. Such applications are usually written using embedded SQL. This is programming style wherein SQL statements are embedded within the program statements. In this kind of SQL the source code needs to be recompiled for each new environment.
Embedded SQL is not suitable for applications that need to analyze data in stored in databases such as DB2 and Oracle. ODBC offers a structured approach for the construction of such an interface. There are two tasks that can be performed using ODBC. They are:
Extracting database information
Importing data into the application
ODBC INTERFACE
ODBC provides an interface that ensures that the user's direct interaction with the data source is minimized. Most of the data access routines are encapsulated by the user from the MFC library. The ODBC interface includes:
A library of ODBC function calls that allows an application to log on and connect to a DBMS, execute SQL statements and retrieve results.
A standard set of error codes.
A standard representation for data types.
Strings containing SQL statements can be explicitly included in the source code or constructed at the run time.
The same object code can be used to access different DBMS products.
An application can ignore underlying data communication protocols between it and a DBMS product.
Data values can be sent and retrieved in a format convenient to the application.
The Microsoft ODBC defines standards not only for the SQL grammar but also for the C language-programming interface to an SQL database. The ODBC Software Development Kit (SDK) included with Visual C++, contains 32-bit drivers for DBF files, Microsoft Access MDB databases, Microsoft Excel XLS Files, Microsoft FoxPro files, ASCII text files and Microsoft SQL Server databases.
Other database companies including Oracle, Informix, Progress, Ingress and Gupta provide ODBC drivers for their own database. You can also write a C++ program to update a SQL server database and then can use an off-shelf ODBC compatible report writer to format and print data. ODBC separates the user interface from the actual database management process.
ODBC ARCHITECTURE
ODBC has four components-the application, the driver manager, the driver and the data source.
Application: This is the application program which performs all the processing and calls ODBC functions to submit SQL statements and retrieve results.
Driver Manager: This routine loads drivers on behalf of an application.
Driver: This routine processes ODBC function calls, submits SQL requests to a specific data source and return results to the application. If necessary, the driver modifies an application's request and conforms it to the syntax supported by the associated DBMS.
Data Source: This consists of the data that the user wants to access and the associated operating system, DBMS, and network platform (if any) used to access the DBMS.
The driver manager and the driver appear to an application as one unit that processes ODBC function calls.
The following diagram shows the relationship between the four components.
ODBC defines conformance levels for drivers in two areas: the ODBC API and the ODBC SQL grammar (which includes the ODBC SQL data types). Conformance levels help both the application and the driver developers by establishing standard sets of functionality. Applications can easily determine if a driver provides the functionality they need. Drivers can be developed to support a broad selection of applications without being concerned about the specific requirements of each application and the data source on which it operates.
CONNECTING TO A DATA SOURCE
An ODBC data source contains a specific set of data, the information required to access that data and the location of the data source, which can be described using a data source name. To access data provided by data source the program must first establish a connection to the data source. The class CDatabase encapsulates data source connections.
CDatabase CLASS
A CDatabase object represents a connection to a data source, through which you can perform operations on the data source. A data source is a specific instance of data hosted by some database management system (DBMS). Examples include Microsoft SQL Server, Microsoft Access, Borland dBASE, and xBASE. You can have one or more CDatabase objects active at a time in your application.
To use CDatabase, construct a CDatabase object and call its OpenEx member function. This opens a connection. You can then construct CRecordset objects for operating on the connected data source and pass the recordset constructor as a pointer to your CDatabase object. When you finish using the connection, call the Close member function and destroy the CDatabase object. The Close function closes any recordsets youmay not have closed previously.
Once a CDatabase object is connected to a data source you can:
Construct recordsets, which select records from tables or queries
Manage transactions and updates batches so that all are committed to the data source at once.
Execute SQL statements.
When you have finished working on the data source you must close the CDatabase object and destroy it or reuse it for a new connection.
SELECTING AND MANIPULATING CLASS
Normally when you select records from the data source using an SQL statement you get a result set, which is a set of records from a table or a query. You use a recordset object to select and access the result set. The object is created from the class CRecordset. When you define the CRecordset class you specify the data source to associate it with, the table to use and the columns of the table you want to access.
CRecordset CLASS
A CRecordset object represents a set of records selected from a data source. Known as "recordsets," CRecordset objects are typically used in two forms: dynaset and snapshots. A dynaset is always synchronized with data updates made by other users. A snapshot is a static view of the data. Each form represents a set of records fixed at the time the recordset is opened. When you scroll to a record in a dynaset, it reflects changes subsequently made to the record, either by other users or by other recordsets in your application.
To work with either kind of recordset, you typically derive an application-specific recordset class from CRecordset. Recordsets select records from a data source, and you can then:
Scroll through the records.
Update the records and specify a locking mode.
Filter the recordset to constrain which records it selects from those available on the data source.
Sort the recordset.
Parameterize the recordset to customize its selection with information not known until runtime.
To use your class, open a database and construct a recordset object, passing the constructor as a pointer to your CDatabase object. Then call the recordset's Open member function, where you can specify whether the object is a dynaset or a snapshot. Calling Open selects data from the data source. After the recordset object is opened, use its member functions and data members to scroll through the records and operate on them.
The operations available depend on whether the object is a dynaset or a snapshot, whether it is updatable or read-only (this depends on the capability of the ODBC (data source), and whether you have implemented bulk row fetching.
To refresh records that may have been changed or added since the Open call, call the object's Requery member function. Call the object's Close member function and destroy the object when you are through with it.
DISPLAYING AND MANIPULATING DATA IN A FORM
Form is a user interface containing controls in which the user examines, enters or edit data. To make your application form-based use the CRecordView class, which gives you a CFormView object directly connected to a recordset object.
CRecordView CLASS
A CRecordView object is a view that displays database records in controls. The view is a form view directly connected to a CRecordset object. The view is created from a dialog template resource and displays the fields of the CRecordset object in the dialog template's controls. The CRecordView object uses dialog data exchange (DDX) and record field exchange (RFX) to automate the movement of data between the controls on the form and the fields of the recordset. CRecordView also supplies a default implementation for moving to the first, next, previous or last record, and an interface for updating the record currently on view.
The dialog resource is to be created with the style Child and not pop-up like you would normally do for CRecordView to be created.
THE COMPLETE PICTURE
The CDatabase class encapsulates a data source and provides a connection to operate on the data source.
The RFX mechanism exchanges data between field data members of the recordset object and corresponding table colum
ns of the data source.
The CRecordset class encapsulates a set of records from a data source.
In case of form-view the DDX mechanism exchanges data between the form-view and the recordset object and is used to display the current record.
The DDX mechanism applies only to the CRecordView class and not to the CScrollView class. This is because in scroll view all operations are directed towards the client area of the window and in case of record view they are directed towards the form.
CONNECTING TO A DATA SOURCE
There are two ways to connect to a data source:
By creating a database object and then calling its Open member function. This approach is normally used when the user needs to open more than one data source at a time.
By using the GetDefaultConnect member function of the CRecordset class. The framework calls this member function to get the default connect string for the data source on which the recordset is based. ClassWizard implements this function for you by identifying the same data source you use in ClassWizard to get information about tables and columns.
Consider the code below:
Class myrecordset : public CRecordset
public:
CString GetDefaultConnect()
// returns pointer to a string that contains the default connect string//
In the above example the first parameter is for compatibility, the second returns the data source name, the third returns the user-id and the last returns the password.
You will probably find it convenient to rely on this default connection while developing your application. But the default connection may not be appropriate for users of your application. If that is the case, you should reimplement this function, discarding ClassWizard's version.
_T MACRO
One major difference between applications running on WinNT and Win95 is that the former supports the UNICODE character sets. UNICODE provides for multilingual characters. However, most of the development is done using the ASCII or MBCS (MultiByte Character Set) encoding character sets. To simplify code development for various international markets, the Microsoft runtime library provides Microsoft-specific "generic-text" mappings for many data types, routines and other objects.
These mappings are defined in TCHAR.H. You can use these mappings to write generic code that can be compiled for any of the three kinds of character sets: ASCII (SBCS), MBCS, or Unicode, depending on a manifest constant you define using a #define statement. Generic-text mappings are Microsoft extensions that are not ANSI compatible.
GetDefaultSQL FUNCTION
Once the data source and the application program has been connected the next step is to select a default table, set a default query and execute it. This operation can be done using the GetDefaultSQL member function of the CRecordset class.
The framework calls this member function to get the default SQL statement on which the recordset is based. This might be a table name or an SQL SELECT statement. The GetSQL is a purely virtual function and has to be overridden in the derived class.
You can indirectly define the default SQL statement by declaring your recordset class with Class Wizard, and Class Wizard performs this task for you.
If you need the SQL statement string for your own use, call GetSQL. This returns the SQL statement used to select the recordset's records when it was opened. You can edit the default SQL string in your class's override by using GetDefaultSQL. For example, you could specify a call to a predefined query using a CALL statement.
Consider the following code:
CString myrecordset::GetDefaultSQL()]{
return_T("[Employees]");
} (Name of the table on which query can be done)
Once the table has been set for the application program there are two kinds of operations that can be performed on it. One is any kind of querying and the other is update operation such as adding, deleting and modifying records.
For the update operations the data in the data source has to be refreshed dynamically since changes have been made. For this two kind of data types are used - snapshot and dynashot.
Snapshots
A snapshot is a recordset that reflects a static view of the data as it existed at the time the snapshot was created. Once you open the snapshot and move to all the records, the set of records it contains and their values do not change until you rebuild the snapshot by calling Requery. You can create updateable or read-only snapshots with the database classes.
Unlike a dynaset, an updateable snapshot does not reflect changes to record values made by other users, but does reflect updates and deletions made by your program. Records added to a snapshot do not become visible to the snapshot until you call Requery.
A snapshot is an ODBC "static cursor." Static cursors do not actually get a row of data until you scroll to that record. To ensure that all records are immediately retrieved, you can scroll to the end of your recordset and then scroll to the first record you are interested in. Note, however, that scrolling to the end entails extra overheads and can lower performance.
Snapshots are most valuable when you need the data to remain fixed during your operations. For instance as when you are generating a report or performing calculations. Even so, the data source can diverge considerably from your snapshot, so you may want to rebuild it from time to time. Snapshots are available only if the ODBC Cursor Library was loaded when the CDatabase object was constructed or the ODBC driver you are using supports static cursors.
Dynasets
A "dynaset" is a recordset with dynamic properties. During its lifetime, a recordset object in dynaset mode (usually called simply a dynaset) stays synchronized with the data source in the following way. In a multiuser environment, other users may edit or delete records that are in your dynaset or add records to the table your dynaset represents. Records your application adds to or deletes from the recordset are reflected in your dynaset. Records that other users add to the table will not be reflected until you rebuild the dynaset by calling its Requery member function. When other users delete records, MFC code skips over the deletions in your recordset. Editing changes made by other users to existing records are reflected in your dynaset as soon as you scroll to the affected record.
Similarly, other users reflect edits you make to records in a dynaset that is in use. Records you add are not reflected in dynasets of other users until they requery their dynasets. Records you delete are marked as "deleted" in the recordsets of other users'. If you have multiple connections to the same database (multiple CDatabase objects), recordsets associated with those connections have the same status as the recordsets of other users.
Dynasets are most valuable when data must be dynamic, as, for example, in an airline reservation system.
In ODBC terminology, dynasets and snapshots are referred to as "cursors." A cursor is a mechanism used for keeping track of its position in a recordset.
INITIALIZING A CRecordset OBJECT
The recordset data members are explicitly initialized usually in the constructor of the CRecordset object. The values of these variables affect the behavior of the recordset object.
The Data Members of CRecordset object which can be set are:
m_hstmt contains the ODBC statement handle for the recordset.
m_nFields contains the number of field data members in the recordset.
m_nParams contains the number of parameter data members in the recordset.
m_pDatabase contains a pointer to the CDatabase object through which the recordset is
connected to a data source.
m_strFilter contains a CString that specifies a Structured Query Language (SQL)
WHERE clause. Used as a filter to select only those records that meet
certain criteria. For example, if the condition is WHERE class =
"SALESMAN" the corresponding ODBC filter condition for the recordset
would be myrecordsetobject.m_strFilter= " CLASS = 'SALES MAN'";
The statement m_strFilter = "EmployeeID=?"; specifies that the filter
condition can be specified at runtime.
m_strSort contains a CString that specifies an SQL Order By clause. m_strSort is used
to control how the records are sorted. For example, you can order the
Employee recordset by ID and then according to the salary. The statement
Would then be:->myrecordsetobject.m_strSort = "Employee ID, Salary";
RFX MECHANISM
The MFC ODBC database classes automate moving data between the data source and a recordset object. When you derive a class from CRecordset without using bulk row fetching, data is transferred via the record field exchange (RFX) mechanism. RFX is similar to dialog data exchange (DDX). Moving data between a data source and the field data members of a recordset requires multiple calls to the recordset's DoFieldExchange function and considerable interaction between the framework and ODBC. The RFX mechanism is type-safe and saves you the work of calling ODBC functions such as ::SQLBindCol. RFX is mostly transparent to you.
If you declare your recordset classes with AppWizard or ClassWizard, RFX is built into them automatically. Your recordset class must be derived from the base class CRecordset supplied by the framework.
AppWizard lets you create an initial recordset class. ClassWizard lets you add other recordset classes, as you need them. Use ClassWizard to map recordset field data members to table columns on the data source. Besides exchanging data between the data source and the recordset's field data members, RFX manages binding parameters. When the recordset is opened, any parameter data members are bound in the order of the "?" placeholders in the SQL statement that CRecordset::Open constructs.
Overriding the DoFieldExchange function does all the work, moving data in both directions. Like DDX, RFX needs information about the data members of your class. ClassWizard provides this necessary information by writing a recordset-specific implementation of DoFieldExchange for you, based on the field data member names and data types you specify with the wizard.
RFX function
Description
RFX_Text
Transfers string data
RFX_Long
Transfers long int data
RFX_Int
Transfers int data
RFX_Byte
Transfers a single byte of data
RFX_Single
Transfers float data
RFX_Double
Transfers double precision float data
RFX_Date
Transfers time and date data using CTime
RFX_Binary
Transfers array of byte of type CByteArray
RFX_LongBinary
Transfers binary-large object(BLOB) data
RFX Functions and their data types
Consider the following example:
class myrecordset : public CRecordset
protected:
long employeeID; // specifies the field to be of long int type//
CString name; // specifies the variable to be of character type//
int sal; // specifies the variable to be of integer type//
//transfers the data between the fields of the data source//
RFX_Long(pfx, _T("[EmployeeID]"), employeeID);
RFX_Text(pfx, _T("[Name]", name); // RFX function does the actual
transfer//
RFX_Int(pfx, _T("[Salary]", sal);
The DoFieldExchange function takes a parameter to the CFieldExchange class. This class is responsible for the flow of data between the CDatabase class and the CRecordset class. The SetFieldType (CFieldExchange :: output column ) specifies that the data exchange is for the field data members. The next RFX statement makes the data exchange from the current record of the data source go to the top of the corresponding variables in the class myrecordset.
ODBC AND DOCUMENTVIEW ARCHITECTURE
There are two ways of implementing Document View architecture for ODBC. They are:
Using the CDocument class in conjunction with the CScrollView class to display the records in the client area of the window.
Using the CDocument class in conjunction with CRecordView class to display information in a form-based dialog.
The algorithm for implementing the Document View Architecture is:
Create a single document template using the user-defined Document, View and Frame.
Construct a recordset object in the class derived from the CDocument class. This ensures that the recordset is created alongwith the document.
In the class derived from CScrollView, declare a pointer to the recordset object. The constructor of the class derived from the CRecordset is designed to accept a parameter-a pointer to the CDatabase class. This pointer in turn is passed to the base class - CRecordset's constructor.
Initialize all the variables for the recordset object, which correspond to the field members of the data source, and set the recordset type, filter condition, sort order and number of fields. This is done using the various data members of the CRecordset class.
Use the GetDefaultConnect function to connect to the data source and the GetDefaultSQL to set default SQL/Table.
Use the GetDocument function in the OnInitialUpdate member of your view class to obtain a pointer to the document object.
Using the pointer obtained, access the recordset data member of the View class and assign it to the recordset object.
Open the recordset in the OnInitialUpdate member function of the View class using the recordset class data member and the Open member function. If the recordset is already open close it using the Close member function and open it again. The IsOpen member function can be used to check if the recordset is open or not.
CScrollView CLASS
The CScrollView class is a CView with scrolling capabilities. You can handle standard scrolling in any class derived from CView by overriding the message-mapped OnHScroll and OnVScroll member functions. But CScrollView adds the following features to its CView capabilities:
It manages window and viewport sizes, and mapping modes.
It scrolls automatically in response to scroll-bar messages.
It scrolls automatically in response to messages from the keyboard, or a non-scrolling mouse.
You can handle the mouse wheel scrolling yourself by overriding the message-mapped OnMouseWheel and OnRegisteredMouseWheel member functions. As they are for CScrollView, these member functions support the recommended behavior for WM_MOUSEWHEEL, which is the wheel rotation message.
To take advantage of automatic scrolling, derive your view class from CScrollView instead of from CView. When the view is first created, if you want to calculate the size of the scrollable view based on the size of the document, call the SetScrollSizes member function from your override of either CView::OnInitialUpdate or CView::OnUpdate.
The call to the SetScrollSizes member function sets the view's mapping mode, the total dimensions of the scroll view, and the amounts to scroll horizontally and vertically. All sizes are in logical units. The logical size of the view is usually calculated from data stored in the document, but in some cases you may want to specify a fixed size. You specify the amounts to scroll horizontally and vertically in logical units.
By default, if the user clicks on a scroll bar shaft outside of the scroll box, CScrollView scrolls a "page." If the user clicks on a scroll arrow at either end of a scroll bar, CScrollView scrolls a "line." By default, a page is 1/10 of the total size of the view and a line is 1/10 of the page size. You can override these default values by passing custom sizes in the SetScrollSizes member function. For example, you might set the horizontal size to some fraction of the width of the total size and the vertical size to the height of a line in the current font.
Instead of scrolling, CScrollView can automatically scale the view to the current window size. In this mode, the view has no scroll bars and the logical view is stretched or shrunk to exactly fit the window's client area. To use this scale-to-fit capability, call CScrollView::SetScaleToFitSize. (Call either SetScaleToFitSize or SetScrollSizes, but not both.)
Before the OnDraw member function of your derived view class is called, CScrollView automatically adjusts the viewport origin for the CPaintDC device-context object that it passes to OnDraw.
To adjust the viewport origin for the scrolling window, CScrollView overrides CView::OnPrepareDC. This adjustment is automatic for the CPaintDC device context that CScrollVie
w passes to OnDraw, but you must call CScrollView::OnPrepareDC yourself for any other device contexts you use, such as a CClientDC. You can override CScrollView::OnPrepareDC to set the pen, background color, and other drawing attributes, but call the base class to do scaling.
Scroll bars can appear in three places relative to a view, as shown in the following cases:
Standard window-style scroll bars can be set for the view using the WS_HSCROLL and WS_VSCROLL styles.
Scroll-bar controls can also be added to the frame containing the view, in which case the framework forwards WM_HSCROLL and WM_VSCROLL messages from the frame window to the currently active view.
The framework also forwards scroll messages from a CSplitterWnd splitter control to the currently active splitter pane (a view). When placed in a CSplitterWnd with shared scroll bars, a CScrollView object will use the shared scrolls rather than creating its own.
Consider the following code segment, which illustrates the usage of CScrollView Class:
class myview:public CScrollView
protected:
myrecordset *ptr; // pointer to the recordset object//
public:
void OnInitialUpdate()
CScrollView:: OninitialUpdate()
CSizeTotal(7000, 11000); // create a CSize object of size width, height//
SetScrollSizes(MM_ENGLISH, sizetotal);//set the view range//
ptr= &(GetDocument()->pset);// get the recordset object
if(ptr->IsOpen())// checks if the recordset is already open or not//
ptr->Close();
ptr->Open();
A CSSize object with width as 7000 pixels and height as 11000 pixels is created and the scroll size within the view is set at .01 inch.
The various possible values of a mapping mode to be set for any view are as follows:
Mapping Mode
Logical Unit
Positive y-axis Extends...
MM_TEXT
1 pixel
Upward
MM_HIMETRIC
0.01 mm
Upward
MM_TWIPS
1/1440"
Upward
MM_HIENGLISH
.001"
Upward
MM_LOMETRIC
0.1 mm
Upward
MM_LOENGLISH
0.01"
Upward
ADMINISTERING ODBC SUPPORT
Let us now look at the steps involved in administering ODBC support for Microsoft Access on a WinNT machine.
Click on the ODBC icon in the control panel.
Select the data sources available in the dialog box.
Click on the data source MS Access 97 after which you get the following screen. Then press the Select
tab to get a list of files from where you are going to access the data.
From the dialog box displayed, select the database on which you want to work upon and press OK.
After pressing OK the following box displays the database selected.
Again press OK and you come back to your first data source dialog box.
@CRecordView CLASS
A CRecordView object is a view that displays database records in controls. The view is a form view directly connected to a CRecordset object. The view is created from a dialog template resource and displays the fields of the CRecordset object in the dialog template's controls. The CRecordView object uses dialog data exchange (DDX) and record field exchange (RFX) to automate the movement of data between the controls on the form and the fields of the recordset. CRecordView also supplies a default implementation for moving to the first, next, previous, or last record and an interface for updating the record currently on view.
CRecordView keeps track of the user's position in the recordset so that the record view can update the user interface. When the user moves to either end of the recordset, the record view disables user interface objects such as menu items or toolbar buttons. This enables the user to move further in the same direction.
The most common way to create your record view is with AppWizard. AppWizard creates both the record view class and its associated recordset class as part of your skeleton starter application. If you do not create the record view class with AppWizard, you can create it later with ClassWizard. If you simply need a single form, the AppWizard approach is easier.
ClassWizard lets you decide to use a record view later in the development process. Using ClassWizard to create a record view and a recordset separately and then connect them is the most flexible approach. This is because it gives you more control in naming the recordset class and its .h/.cpp files. This approach also lets you have multiple record views on the same recordset class.
To make it easy for end-users to move from record to record in the record view, AppWizard creates menu (and an optional toolbar) resources for moving to the first, next, previous, or last record. If you create a record view class with ClassWizard, you need to create these resources yourself with the menu and bitmap editors.
The first step towards creating a CRecordView object is to create a dialog template. For every field the user wants to display a corresponding control is created in the dialog resource template. (The dialog box style is set to child; this is because the template is passed to the constructor of the base class who expects a dialog box with style child).
Once the dialog resource's template has been created, application coding begins. The code is more or less the same as that in the last session. The only difference is that all the operations are directed towards the view which are handled by the CRecordView class and not the CScrollView class.
The first step towards handling the CRecordView class derived from the CRecordView base class. The constructor of the class derived from the CRecordView class must pass the resource-id to the base class constructor. This prepares the dialog as the standard form view. The data member mpset, which is the pointer to the user-defined recordset, is then set to NULL.
Consider the code below:
class myrecordview:public CRecordView
myrecordset *mpset;
public:
myrecordview():CRecordView(IDD_DIALOG1)
mpset=NULL;
CRecordset * OnGetRecordset()
return mpset;
Once the form view has been initialized the next step is to ensure the exchange of data between the record view and the recordset. This is done using the DDX mechanism.
DDX MECHANISM
DDX is an easy way to initialize the controls in your dialog box and to gather data input by the user. Dialog Data Validation (DDV) is an easy way to validate data entry in a dialog box. To use DDX, you define member variables in the dialog box, form view or record view class, and associate each of them with a dialog box control. The framework transfers any initial values to the controls when the dialog box is displayed. When you click on OK, it updates the variables using the data that you entered.
What happens when the DDX mechanism is used
If you use the DDX mechanism, you set the initial values of the dialog object's member variables, typically in your OnInitDialog handler or the dialog constructor. Immediately before the dialog is displayed, the framework's DDX mechanism transfers the values of the member variables to the controls in the dialog box, where they appear when the dialog box itself appears in response to DoModal or Create. The default implementation of OnInitDialog in CDialog calls the UpdateData member function of class CWnd to initialize the controls in the dialog box.
The same mechanism transfers values from the controls to the member variables when the user clicks on the OK button (or whenever you call the UpdateData member function with the argument TRUE). The dialog data validation mechanism validates any data items for which validation rules have been specified.
The following figure illustrates the dialog data exchange process
UpdateData works in both directions, as specified by the BOOL parameter passed to it. To carry out the exchange, UpdateData sets up a CDataExchange object and calls the override of the DoDataExchange member function. DoDataExchange takes an argument of type CDataExchange. The CDataExchange object passed to UpdateData represents the context of the exchange, defining such information as the direction of the exchange.
When you (or ClassWizard) override DoDataExchange, you specify a call to one DDX function per data member (control). Each DDX function knows how to exchange data in both directions based on the context supplied by the CDataExchange argument passed to the DoDataExchange by UpdateData.
MFC provides many DDX functions for different kinds of exchange. The following example shows a DoDataExchange override in which two DDX functions and one DDV function are called:
The DDX_ and DDV lines between the //{{AFX_DATA_MAP and //}}AFX_DATA_MAP delimiters are a data map. The sample DDX and DDV functions shown are for a check box control and an edit-box control, respectively.
If the user cancels a modal dialog box, the OnCancel member function terminates the dialog box and DoModal returns the value IDCANCEL. In that case, no data is exchanged between the dialog box and the dialog object.
When implicitly called by the framework exchanges data the DoDataExchange function between the recordset and its form view controls. The DoDataExchange accepts a pointer to the CDataExchange class as a parameter.
SOME COMMONLY USED DDX_ FUNCTIONS
The DDX_Text function manages the transfer of int, UINT, long, DWORD, CString, float or double data between an edit control in a dialog box, form view, or control view and a CString data member of the dialog box, form view or control view object.
The DDX_FieldText function manages the transfer of int, short, long, DWORD, CString, float, double, BOOL or BYTE data between an edit box control and the field data members of a recordset. An empty edit box control indicates a NULL value. On a transfer from the recordset to the control, if the recordset field is NULL, the edit box is set to empty. On a transfer from control to recordset, if the control is empty, the recordset field is set to NULL.
The DDX_Check function manages the transfer of int data between a check box control in a dialog box, form view or control view object and a int data member of the dialog box, form view or control view object. When DDX_Check is called, the value is set to the current state of the check box control.
The DDX_Control function manages the transfer of data between a sub classed control in a dialog box, form view or control view object and a CWnd data member of the dialog box, form view or control view object.
The DDX_FieldRadio function associates a zero-based int member variable of a record view's recordset with the currently selected radio button in a group of radio buttons in the record view. When transferring from the recordset field to the view, this function turns on the nth radio button (zero-based) and turns off the other buttons. In the reverse direction, this function sets the recordset field to the ordinal number of the radio button that is currently on (checked). On a transfer from the recordset to the control, if the recordset field is NULL, no button is selected. On a transfer from control to recordset, if no control is selected, the recordset field is set to NULL if the field permits.
The DDX_Radio function manages the transfer of int data between a radio control group in a dialog box, form view or control view object and a int data member of the dialog box, form view or control view object. When DDX_Radio is called, the value is set to the current state of the radio control group.
The DDX_FieldScroll function synchronizes the scroll position of a scroll bar control in a record view and an int field data member of a recordset associated with the record view (or with whatever integer variable you choose to map it to). When moving data from the recordset to the control, this function sets the scroll position of the scroll bar control to the value specified. On a transfer from the recordset to the control, if the recordset field is NULL, the scroll bar control is set to 0. On a transfer from control to recordset, if the control is empty, the value of the recordset field is 0.
The DDX_FieldCheck function manages the transfer of int data between a check box control in a dialog box, form view or control view object and an int data member of the dialog box, form view or control view object. When DDX_FieldCheck is called, value is set to the current state of the check box control, or the control's state is set to the value, depending on the direction of the transfer.
The parameters that these functions usually take:
A pointer to the CDataExchange class
The edit-control's id in the dialog box resource template.
The recordset data member name.
A pointer to the recordset.
BROWSING RECORDS THROUGH A FORM VIEW
Let us assume that you have developed a menu-based application, which has the option of scrolling from one record to another, in either the reverse or in the forward direction. Options are available to move directly to the first or to the last record. A toolbar is also available to execute the above mentioned options. The foremost task would be to declare handlers for the menu option derived from the CFrameWnd. The statements for these would be as follows:
BEGIN_MESSAGE_MAP(mywindow, CFrameWnd)
ON_COMMAND(ID_MY_PREV, myprev)
ON_COMMAND(ID_MY_NEXT, mynext)
ON_COMMAND(ID_MY_LAST, mylast)
END_MESSAGE_MAP()
The CRecordset uses the following functions to move to the corresponding records:
MovePrev: This member function is called to make the first record in the previous row set the current record. If you have not implemented bulk row fetching, your recordset has a row set size of 1. In this case MovePrev simply moves to the previous record. When moving through a recordset, deleted records must not be skipped.
MoveNext: Call this member function to make the first record in the next row set the current record. If you have not implemented bulk row fetching, your recordset has a row set size of 1. In this case MoveNext simply moves to the next record. When moving through a recordset, deleted records must not be skipped.
MoveLast: Call this member function to make the first record in the last complete row set the current record. If you have not implemented bulk row fetching, your recordset has a row set size of 1. In this case MoveLast simply moves to the last record in the recordset.
MoveFirst: Call this member function to make the first record in the first row set the current record. Regardless of whether bulk row fetching has been implemented, this will always be the first record in the recordset. You do not have to call MoveFirst immediately on opening the recordset. At that time, the first record (if any) is automatically becomes the current record.
myview->UpdateData(FALSE); //reinitialize the form//
The next steps of operations that can be performed are the update operations. In these operations data entered by the user constantly changes. This means that the recordset has to be dynamically constructed or refreshed and the data source has to be updated whenever changes are made. (In other words, the recordset to be created has to be a dynaset.)
ADDING THE RECORDS TO A DATABASE
Adding records to the recordset can be done using the AddNew member function of the CRecordset class. The changes done are not reflected in the recordset immediately. For this you need to perform the following tasks:
Call the Update member function of the CRecordset class. This updates the record(s) added by the user.
Reconstruct the recordset by calling the Requery function of the CRecordset class.
myset->MoveLast(); // pointer is moved to the last record as the new records are
to added at the end//
long int temp=myset->myID+1;
myview->SetDlgItemInt(IDC_MY_EDIT1, temp, TRUE);
myview->SetDlgItemText(IDC_MY_TEXT, " ");
myset->AddNew();
myview->UpdateData("True");
if(!myset->Update())
Message Box(" no updation");
if(myset->Requery()==0)
return;
myview->UpdateData(FALSE):
The code segment gets the current view and a pointer to the recordset. It then generates the myID by moving to the last record and adds one to the ID giving us the next ID. The new record is added using the AddNew function. After the record has been added, the data is updated and a new recordset is constructed using the Requery function. The form is then updated using the UpdateData member function.
MODIFYING THE RECORDS IN THE DATABASE
The only difference between the addition and the modification processes is that in addition new records are added whereas in modification existing records are edited. The Edit member function of the CRecordset is used for modification.
The code segment edits the current record in the employee table. The functions used are the same as those used for adding records. Before updation is performed, the CanUpdate member function is called to check if the recordset can be updated.
DELETING RECORDS FROM A DATABASE
The Delete member function is used to delete records from the database. Consider the following code segment.
In the above code the current record displayed is deleted. A check is made to see if the user has deleted the last record. Once this is confirmed a check is made to see if the end of file has been encountered. If this is so the recordset is set to NULL. Finally the form is refreshed.
STARTING AN APPWIZARD PROJECT
AppWizard is one of the wizards provided by MFC through which you create Windows-based applications. This interface is not only simple and easy to understand but also powerful and flexible enough to generate Windows-based applications. The skeleton of a new C++ application can be configured using the AppWizard.
STEPS INVOLVED IN CREATING AN APPLICATION USING APPWIZARD
An AppWizard project is started from the New Project Workspace. Then, from the pop-up file menu choose New. The new dialog box is displayed as follows.
From the list select the option MFC AppWizard (exe), type the name of the project in the Project name box and choose a path from the path name box. After doing so say OK.
After pressing OK the following dialog box is displayed:
The dialog box prompts you to select the type of application you would like to create- SDI, MDI or dialog-based and the language in which you would like your resource. Each dialog box that is displayed helps you organize the application's features and requirements. (In our case we select Multiple Document Interface)
Click on Next. The following dialog box appears:
This dialog box talks about the database you would like to include in your application. (In this case we say None and then press Next).
On clicking Next the following dialog box appears:
In the above dialog box you are allowed to include various types of compound document- support alongwith other supports such as ActiveX Controls.
Click on Next and the following window is displayed:
In the above dialog box a series of check boxes are listed under the first question - What features would you like to include?
Option Function
Docking toolbar Determines whether the toolbar created by MFC for your applications is movable and dockable or not.
Initial status bar Determines whether your application will initially display and use the status bar.
Printing and print preview Determines whether your application handles these two features and adds them to the menu.
Context-sensitive help Determines whether your application provides a context-sensitive help feature.
3-D Controls Determines whether the visual interface used in your application has shading, giving it a three-dimensional appearance.
Then WOSA support (Windows Open Services Architecture) support like MAPI and Windows Sockets is also provided, which adds communication support for your application.
MAPI (Microsoft's Messaging Program Interface) allows e-mail creation, transmission and handling
Windows Sockets provides an interface to various communication protocols such as TCP/IP and AppleTalk.
The last selection allows you to set the number of files that will be displayed in the Most Recently Used (MRU) list in your application.
Click on the Advanced button to display the Advanced Options dialog box. This dialog box has two tabs - Document Template and Windows Style.
The other tab, Windows Styles, sets the styles that the application uses when it generates windows.
The dialog box also has a check box called Use split window which generates an application that supports split windows. Split windows allow a user to divide a window into horizontal and vertical sections.
Click on the Close button to return to the dialog box. Clicks on Next to proceed. The following dialog box is displayed.
Select Yes, please if you want AppWizard to add comments to the programs it generates. Otherwise click No, thank you
For the second question - How would you like to use the MFC library?
Select the button As a shared DLL if your application uses only MFC and C++. Your application can share DLL code with any other application that is running at the same time, using less memory and resources.
Select As a statically linked library, if your application is using any non-C++ code, has non-C++ and non-MFC C++ code, or is targeted for any other platform.
Click on the Next button to proceed to the final step in the creation of an MFC application. The following dialog box is displayed:
The dialog box displays five classes: CExampleApp, CMainframe, CChildFrame, CExampleDoc and CExampleView.
Select the view class CExampleView from the list of application classes.
Scroll down to the Base class list box and select CScrollView as the base class for the CExample class.
Consider the following figure:
After selecting the CScrollView select the Finish button. The following screen will be displayed.
Click on OK to generate your skeleton application.
AppWizard creates the files for your skeleton application and places them in the directory that you have selected. It then returns you to the developer studio, where it automatically opens the project workspace file (example.mdp). The screen that will appear is as follows:
When the project is loaded, the developer studio scans the project to establish the links between all the elements of your new application. The output generated on executing the application (select Build All from the Build option in the menu and after the .exe file is built choose the Execute Example.exe option ) is shown in the following figure.
CLASSWIZARD
Like a programmer's assistant, ClassWizard makes it easier for you to do certain routine tasks such as creating new classes, defining message handlers, overriding virtual functions, and gathering data from controls in a dialog box, form view, or record view. ClassWizard also makes it easy to add properties, methods and events to automation objects. ClassWizard works with programs that use the MFC Library or the Active Template Library (ATL).
ClassWizard and AppWizard insert specially formatted comment delimiters in your source code to mark the places in your files to which the ClassWizard can write.
AFX_MSG is used to mark the beginning and end of ClassWizard entries in your header files as shown below:
//{{ AFX_MSG (class name)
//}}AFX_MSG
AFX_MSG_MAP is used to mark the beginning and end of message map entries in a class's message map in your source files as shown below:
//{{AFX_MSG_MAP (class name)
//}}AFX_MSG_MAP
ClassWizard can be used to modify the code of the application that you created. This can be done in the following way:
Select the View-ClassWizard option from the menu bar and the following MFC dialog box is displayed
Click on the message Maps tab and double-click on the message WM_CHAR from the Messages list box.
The member function OnChar is highlighted in the Member functions list as shown in the above figure.
Double-click on the function OnChar and it will take you to the project workspace where the ClassWizard generates the prototype of the OnChar function.
The code has to be altered suitably to perform the task of accepting user input and displaying the same on the screen. To do so alter the OnChar function as follows:
//TODO: add your message handler code here and /or call default
static int x=0, y=0, ht=0, wd=0;
TEXTMETRIC tm;
CClientDC cclient(this);
cclient.GetTextMetrics(&tm);
cclient.GetCharWidth(char, char, &wd);
ht=tm.tmExternalLeading+tm.tmheight;
client.textOut(x, y, char);
x+=wd;
if(char == 13)
y+=ht;
CScrollView::OnChar(char, cnt, flag)
The above sample code segment displays the user's input on the screen. When the user presses the Return key, the text that follows is displayed on the screen.
STANDARD APPWIZARD FILES
AppWizard creates the following files:
Project Files and MAKEFILES: PRJNAME.MAK is the project makefile for your MFC project and is used to compile the code stored in the .CPP file that the AppWizard creates. PRJNAME.CLW contains information used by the ClassWizard to edit existing classes or add new classes. The ClassWizard also uses this file to store information needed to create and edit message maps and dialog data maps and to create prototype member functions.
Application Source and Header files: Depending on the type of application-SDI, MDI or dialog-based, AppWizard generates the following source files and the header files:
PRJNAME.H: This is the main include file for the application. This file contains all global symbols and # include directives for other program files that constitute the project.
PRJNAME.CPP: This file is the main application source file. It creates one object of the class CPrjnameApp and overrides the InitInstance member function.
MAINFRAME.CPP AND MAINFRAME.H: These files derive the CMainframe calls from either CFrameWnd (SDI) or CMDIFrameWnd '(MDI). The CMainFrame handles the creation of toolbars and the status bar, if the corresponding options are selected in AppWizard's Application options (step 4 of 6) dialog box while creating the application
CHILDFRAME.H AND CHILDFRAME.CPP: These files derive the application's CChildFrame class from CMDIChildWnd class. The ChildFrame is used for MDI document frame windows. These files are created when you select the MDI option while generating the application using the AppWizard.
PRJNAMEDOC.H AND PRJNAMEDOC.CPP: These files derive and implement the document class CProjnameDoc and include skeleton member functions to initialize and serialize a document, and implement debugging diagnostics.
PRJNAMEVIEW.CPP AND PRJNAMEVIEW.H: Together these files derive and implement the view class CPrjnameView that is used to display and print the document's data. If support for printing is enabled, message amp entries call the corresponding member functions in the view base class.
RESOURCE FILES: AppWizard creates a number of resource files with extension .rc. These files contain a default menu definition, accelerator and string tables for a generic MFC application. They also specify the default About Box, which displays the program version number and an icon file (RES\PROJNAME.ICO). The resource file includes the AFXRES.RC for standard MFC. The resource file for toolbar is RES\TOOLBAR.BMP.
PRECOMPILED HEADER FILES: When you see AppWizard, PROJNAME.PCH sets up your project to use a precompiled header (.PCH) file. These kind of files help speed up the compilation when a group of files in a project do not change. Because of the compiler option used by projects generated using AppWizard anything up to and including "stdafx.h" in a .CPP file is considered by the compiler to be a part of the precompiled header.
CONTEXT-SENSITIVE HELP FILES
APPWIZARD FILES ADDED BY OPTIONS
APPWIZARD DATABASE SUPPORT
When you set options for your new skeleton application in AppWizard, you can specify database options in addition to the general AppWizard options. You can set database options from AppWizard's Database Options page (step2 of 6) for a document-based application (SDI/MDI).
The following section explains the database options that can be set for ODBC (Open Database Connectivity).
STEPS TO CREATE AN APPLICATION WITH DATABASE SUPPORT
Create an MFC AppWizard project.
From AppWizard's database option page (step 2 of 6) choose the database support options you require.
If you choose database view without file support or database view with file support, then the data source button is enabled as shown in the following figure:
Click on the Datasource tab. The Database Options dialog box is displayed.
In the Database Options dialog box select ODBC as shown in the following figure:
Select the ODBC data source from the drop-down list box, which contains the names of the data sources registered on your machine.
Click on OK. The Select database dialog box is displayed.
From this dialog box, select the names of one or more tables in the datasource, which have the columns you want to bind to your recordset and click on OK.
After completing the database options, click on Next and complete your non-database AppWizard selections. AppWizard creates files for your project.
@INTRODUCTION
It usually happens that even the best of written programs fails. Rather than failing in an operation the Visual C++ environment gives you the opportunity to try an operation and provide a safety net if the operation fails. This is where Exception Handling comes into the picture. Microsoft C++ supports two kinds of exception handling, C++ exception handling (try, throw, catch) and structured exception handling (__try/__except, __try/__finally). If possible, you should use C++ exception handling rather than structured exception handling. Although structured exception handling works with C and C++ source files, it is not specifically designed for C++. For C++ programs, you should use C++ exception handling.
In this session, the terms "structured exception handling" and "structured exception" (or "C exception") refers exclusively to the structured exception handling mechanism provided by Win32. All other references to exception handling (or "C++ exception") refer to the C++ exception handling mechanism.
WHEN TO USE EXCEPTIONS
Three categories of outcomes can occur when a function is called during program execution:
Normal Execution: this occurs when a function executes normally and returns to the calling program. Some functions return a result code to the caller. This code indicates the outcome of the execution. The result codes are structurally defined for the function and represent the range of possible outcomes of the function. The result code can indicate success or failure or even can indicate failure which is within the range of expectations.
Erroneous Execution: this occurs when the caller makes a mistake in passing arguments, or calls the function out of context. This situation causes an error and this error should be detected by an assertion during program development.
Abnormal Execution: this includes situation where conditions outside the programs influence the outcome of the function. One such situation can be insufficient memory. Another is abnormality in I/O outcome of a function. Such a situation cannot be handled with a simple check to see if the file exists; catching and throwing exceptions best handle it.
C++ EXCEPTIONS
The C++ language provides built-in support for handling anomalous situations, known as exceptions, which may occur during the execution of your program. The try, throw, and catch statements have been added to the C++ language to implement exception handling. With C++ exception handling, your program can communicate unexpected events to a higher execution context that is better able to recover from such abnormal events. These exceptions are handled by code that is outside the normal flow of control. The Microsoft C++ compiler implements the C++ exception handling model based on the ISO WG21/ANSI X3J16 working papers which are developed towards the involving standard for C++.
The compound-statement after the try clause is the guarded section of code. The throw-expression throws (raises) an exception. The compound-statement after the catch clause is the exception handler, and catches (handles) the exception thrown by the throw-expression. The exception-declaration statement indicates the type of exception the clause handles. The type can be any valid data type, including a C++ class. If the exception-declaration statement is an ellipsis (...), the catch clause handles any type of exception, including a C exception. Such a handler must be the last handler for its try block. The operand of throw is syntactically similar to the operand of a return statement.
Execution proceeds as follows:
Control reaches the try statement by normal sequential execution. The guarded section (within the try block) is executed.
If no exception is thrown during execution of the guarded section, the catch clauses that follow the try block are not executed. Execution continues at the statement after the last catch clause following the try block in which the exception was thrown.
If an exception is thrown during execution of the guarded section or in any routine the guarded section calls (either directly or indirectly), an exception object is created from the object created by the throw operand. (This implies that a copy constructor may be involved.) At this point, the compiler looks for a catch clause in a higher execution context that can handle an exception of the type thrown (or a catch handler that can handle any type of exception). The catch handlers are examined in order of their appearance following the try block. If no appropriate handler is found, the next dynamically enclosing try block is examined. This process continues until the outermost enclosing try block is examined.
If a matching handler is still not found, or if an exception occurs while unwinding, but before the handler gets control, the predefined run-time function terminate is called. If an exception occurs after throwing the exception, but before the unwind begins, terminate is called.
If a matching catch handler is found, and it catches by value, its formal parameter is initialized by copying the exception object. If it catches by reference, the parameter is initialized to refer to the exception object. After the formal parameter is initialized, the process of "unwinding the stack" begins. This involves the destruction of all automatic objects that were constructed (but not yet destructed) between the beginning of the try block associated with the catch handler and the exceptions throw site. Destruction occurs in reverse order of construction. The catch handler is executed and the program resumes execution following the last handler (that is, the first statement or construct which is not a catch handler). Control can only enter a catch handler through a thrown exception; never via a goto statement or a case label in a switch statement.
The following is a simple example of a try block and its associated catch handler. This example detects failure of a memory allocation operation using the new operator. If new is successful, the catch handler is never executed:
#include <iostream.h>
int main()
char *buf;
buf = new char[512];
if( buf == 0 )
throw "Memory allocation failure!";
catch( char * str )
cout << "Exception raised: " << str << '\n';
// ...
return 0;
The operand of the throw expression specifies that an exception of type char * is being thrown. A catch handler that expresses the ability to catch an exception of type char * handles it. In the event of a memory allocation failure, this is the output from the preceding example:
Exception raised: Memory allocation failure!
The real power of C++ exception handling lies not only in its ability to deal with exceptions of varying types, but also in its ability to automatically call destructor functions during stack unwinding, for all local objects constructed before the exception was thrown.
The following example demonstrates C++ exception handling using classes with destructor semantics:
cout<< "In MyFunc(). Throwing CTest exception.\n";
throw CTest();
int main()
cout << "In main.\n";
cout << "In try block, calling MyFunc().\n";
MyFunc();
catch( CTest E )
cout << "In catch handler.\n";
cout << "Caught CTest exception type: ";
cout << E.ShowReason() << "\n";
catch( char *str )
cout << "Caught some other exception: " << str << "\n";
cout << "Back in main. Execution resumes here.\n";
return 0;
This is the output from the preceding example:
In main.
In try block, calling MyFunc().
Constructing CDemo.
In MyFunc().
Throwing CTest exception.
Destructing CDemo.
In catch handler.
Caught CTest exception type: Exception in CTest class.
Back in main. Execution resumes here.
Note that in this example, the exception parameter (the argument to the catch clause) is declared in both catch handlers:
catch( CTest E )
// ...
catch( char *str )
// ...
You do not need to declare this parameter; in many cases it may be sufficient to notify the handler that a particular type of exception has occurred. However, if you do not declare an exception object in the exception-declaration, you will not have access to that object in the catch handler clause.
A throw-expression with no operand re-throws the exception currently being handled. Such an expression should appear only in a catch handler or in a function called from within a catch handler. The re-thrown exception object is the original exception object (not a copy). For example:
throw CSomeOtherException();
catch(...) // Handle all exceptions
// Respond (perhaps only partially) to exception
// ...
throw; // Pass exception to some other handler
STRUCTURED EXCEPTION HANDLING
An exception is an event that occurs during the execution of a program, and requires the execution of code outside the normal flow of control. There are two kinds of exceptions: hardware exceptions and software exceptions. The CPU initiates hardware exceptions. They can result from the execution of certain instruction sequences, such as division by zero or an attempt to access an invalid memory address. Software exceptions are initiated explicitly by applications or the operating system. For example, the system can detect when an invalid parameter value is specified.
Structured Exception Handling is a mechanism for handling both hardware and software exceptions. Therefore, your code will handle hardware and software exceptions identically. Structured exception handling enables you to have complete control over the handling of exceptions, provides support for debuggers, and is usable across all programming languages and machines.
The system also supports termination handling, which enables you to ensure that whenever a guarded body of code is executed, a specified block of termination code is also executed. The termination code is executed regardless of how the flow of control leaves the guarded body. For example, a termination handler can guarantee that clean-up tasks are performed even if an exception or some other error occurs while the guarded body of code is being executed.
The __try/__except and __try/__finally statements are a Microsoft extension to the C language that enables applications to gain control of a program after events that would normally terminate execution.
Note Structured exception handling works with C and C++ source files. However, it is not specifically designed for C++. Although destructors for local objects will be called if you use structured exception handling in a C++ program, you can ensure that your code is more portable by using C++ exception handling. The C++ exception handling mechanism is more flexible, in that it can handle exceptions of any type.
Syntax
try-except-statement :
__try compound-statement
__except ( expression ) compound-statement
try-finally-statement :
__try compound-statement
__finally compound-statement
If you have C modules that use structured exception handling, they can be mixed with C++ modules that use C++ exception handling. When a C (structured) exception is raised, the C handler can handle it, or it can be caught by a C++ catch handler, whichever is dynamically closest to the exception context. One of the major differences between the two models is that when a C exception is raised, it is always of type unsigned int, whereas a C++ exception can be of any type. That is, C exceptions are identified by an unsigned integer value, whereas C++ exceptions are identified by data type.
However, while a C++ catch handler can catch a C exception (for example, via an "ellipsis" catch handler), a C exception can also be handled as a typed exception by using a C exception wrapper class. By deriving from this class, each C exception can be attributed a specific derived class. (To use a C exception wrapper class, you install a custom C exception translator function which is called by the internal exception handling mechanism each time a C exception is thrown. Within your translator function, you can throw any typed exception, which can be caught by an appropriate matching C++ catch handler).
MFC EXCEPTION SUPPORT
Whether you use the C++ exceptions directly or use the MFC exception macros, you will use CException or CException-derived objects that may be thrown by the framework or by your application.
MFC provides several predefined exceptions classes:
EXCEPTION CLASS
CMemoryException
CFileException
CArchiveException
CNotSupportedException
CResourceException
CDBException
COleException
COleDispatchException
CUserException
MEANING
Out-of-memory
File exception
Archive/Serialization exception
Response to request for unsupported service
Windows resource allocation exception
Database exceptions (ODBC classes)
OLE exceptions
Dispatch (automation) exceptions
Exception that alerts the user with a message
box, then throws a generic CException
CException is the base class for all exceptions in the Microsoft Foundation Class Library. The derived classes of CException and their descriptions are listed below:
DERIVED CLASSES
CMemoryException
CNotSupportedException
CArchiveException
CFileException
CResourceException
COleException
CDBException
COleDispatchException
CUserException
CDaoException
CInternetException
DESCRIPTION
Out-of-memory exception
Request for an unsupported operation
Archive-specific exception
File-specific exception
Windows resource not found or not createable
OLE exception
Database exception (that is, exception conditions arising for MFC database classes based on Open Database Connectivity)
OLE dispatch (automation) exception
Exception that indicates that a resource could not be found.
Data access object exception (that is, exception
conditions arising for DAO classes)
Internet exception (that is, exception conditions
arising for Internet classes).
These exceptions are intended to be used with the THROW, THROW_LAST, TRY, CATCH, AND_CATCH, and END_CATCH macros.
To catch a specific exception, use the appropriate derived class. To catch all types of exceptions, use CException, and then use CObject::IsKindOf to differentiate among CException-derived classes. Note that CObject::IsKindOf works only for classes declared with the IMPLEMENT_DYNAMIC macro, in order to take advantage of dynamic type checking. Any CException-derived class that you create should use the IMPLEMENT_DYNAMIC macro, too.
You can report details about exceptions to the user by calling GetErrorMessage or ReportError, two member functions that work with any of CException's derived classes.
If an exception is caught by one of the macros, the CException object is deleted automatically; do not delete it yourself. If using a catch keyword catches an exception, it is not automatically deleted.
CException is an abstract base class. You cannot create CException objects; you must create objects of derived classes. If you need to create your own CException type, use one of the derived classes listed above as a model. Make sure that your derived class also uses IMPLEMENT_DYNAMIC. #include <afx.h>
When an exception is thrown, control passes to the first catch block whose exception-declaration matches the type of the exception. You can selectively handle different types of exceptions with sequential catch blocks as listed below:
// Execute some code that might throw an exception.
catch( CMemoryException* e )
// Handle the out-of-memory exception here.
catch( CFileException* e )
// Handle the file exceptions here.
catch( CException* e )
// Hand:
le all other types of exceptions here.
The cause for an exception can be identified by the values returned by the exception objects. Consider the following code below:
In the sample code above an attempt is made to open a file example within the try block. CFile class constructor has been made use of and two parameters are passed the name of the file and the mode in which the file has to be opened. This is because only the overloaded constructor of the CFile classes throws an exception if it occurs. If the file cannot be opened, an exception is thrown, which is caught by the CFileException handler. CFileException has a data member cause, which contains an enumerated type that specifies the cause of the file exception. In the sample code a check is made to see whether this value is equal to CFileException::fileNotFound and a message is displayed using the TRACE macro.
CFileException Return Value Meanings
none No error occurred
badPath Path is invalid
tooManyFiles Permitted number of open files exceeded
invalidFile Attempt to use an invalid file handle
directoryFull No more directory entries
badSeek Error setting file pointer
hardIO Hardware error
diskFull Disk full
endOfFile End of file was reached
Table 15.1: CFileException returns values
ODBC EXCEPTION HANDLING
The most pragmatic approach to catching database exceptions is to test your application with exception scenarios. Determine the likely exceptions that might occur for an operation in your code, and force the exception to occur. Then examine the trace output to see what exception is thrown, or examine the returned error information in the debugger. This lets you know which return codes you'll see for the exception scenarios you are using.
ERROR CODES USED FOR ODBC EXCEPTIONS
In addition to return codes defined by the framework, which have names of the form AFX_SQL_ERROR_XXX, some CDBExceptions are based on ODBC return codes. The return codes for such exceptions have names of the form SQL_ERROR_XXX.
The return codes - both framework-defined and ODBC-defined - that the database classes can return are documented under the m_nRetCode data member of class CDBException.
The following example attempts to construct a CRecordset-derived object and then open the recordset (for an ODBC data source).
The Open member function could throw an exception (of type CDBException for the ODBC classes), so this code brackets the Open call with a try block. The subsequent catch block will catch a CDBException.
You could examine the exception object itself, called, but in this case it is enough to know that the attempt to create a recordset has failed. The catch block displays a message box and cleans up by deleting the recordset object.
CRecordset* CSectionView::OnGetRecordset()
if ( m_pSet != NULL )
return m_pSet; // Recordset already allocated
m_pSet = new CSectionSet( NULL );
m_pSet->Open( );
catch( CDBException* e )
AfxMessageBox( e->m_strError,
MB_ICONEXCLAMATION );
// Delete the incomplete recordset object
delete m_pSet;
m_pSet = NULL;
e->Delete();
return m_pSet;
ASSERT MACRO
This topic explains how to check assumptions made by your functions, using the ASSERT macro. As of MFC version 4.0, MFC uses the same assertion mechanisms as the C Run-time Library. This means that the message format has changed somewhat.
The most typical use of the ASSERT macro is to identify program errors during development. The argument given to ASSERT should be chosen so that it holds true only if the program is operating as intended. The macro evaluates the ASSERT argument and, if the argument expression is false (0), alerts the user and halts program execution. No action is taken if the argument is true (nonzero).When an assertion fails, a message box appears with the following text:
assertion failed in file <name> in line <num>
Abort Retry Ignore
where <name> is the name of the source file and <num> is the line number of the assertion that failed.
If you choose Abort, program execution terminates. If you choose Ignore, program execution continues. It is possible to break into the debugger after an ASSERT by clicking Retry. Neither Abort nor Ignore will activate a debugger, so they provide no way to examine the call stack.
If you are running under the debugger and choose Retry, a call to AfxDebugBreak embedded in the code causes a break into the debugger. At this point, you can examine the call stack. To view the call stack, on the View menu, click Debug Windows and Call Stack. If you have enabled Just-in-time debugging, this will work even if the application is not being debugged.
The following example shows how the ASSERT macro could be used to check the validity of a function's return value:
int x = SomeFunc(y);
ASSERT(x >= 0); // Assertion fails if x is negative
ASSERT can also be used in combination with the IsKindOf function to provide the extra checking for function arguments, as shown in the following example:
The liberal use of assertions throughout your programs can catch errors during development. A good rule of thumb is that you should write assertions for any assumptions you make. For example, if you assume that an argument is not NULL, use an assertion statement to check for that condition.
The ASSERT macro will catch program errors only when you are using the debug version of the Microsoft Foundation Class Library during development. It will be turned off (and produce no code) when you build your program with the release version of the library.
The expression argument to ASSERT will not be evaluated in the release version of your program. If you want the expression to be evaluated in both debug and release versions, use the VERIFY macro instead of ASSERT. In debug versions, VERIFY is the same as ASSERT. In release versions, VERIFY evaluates the expression argument but does not check the result.
USING THE ASSERT_VALID MACRO
The ASSERT_VALID macro can be used to perform a run-time check of an object's internal consistency. The ASSERT_VALID macro is a more robust way of accomplishing
pObject->AssertValid();
Like the ASSERT macro, ASSERT_VALID is turned on in the debug version of your program, but turned off in the release version.