Make a new Win32 GUI project in Code::Blocks and select a frame-based layout. Code::Blocks will generate a lot of code for you which can seem daunting at first, but you will also see a lot of comments along with the code, read those and calm your nerves. The WinMain function is the actual main function for a Win32 Project. This is the entry point for your program. The specific signature of this function (A WINAPI modifier and the arguments) is required for the compiler to know which function is the main function.
We generally also have a WindowProcedure function with its own specific signature. This function is used for all the callbacks, that is whenever an event occurs this function is called. An event can be the key press on a keyboard or moving of mouse or closing of window etc...
All the data types that start with H are handles or pointers to some types. These are required as it saves memory and moreover you won't have to deal with freeing memory as windows will take care of it for you.
First we have to register a class (or a model) for how the window (GUI) will look and then we can create a window (hwnd) using CreateWindowEx that create a window from the provided model and some other parameters. All the parameters are explained in the comments in the code.
Then we will go into a while loop that will call GetMessage which return 0 only when the window has been destroyed/closed. In the loop we use the API to pass the event to the WindowProcedure that we defined. After the loop we exit with an exit-code. This is generally 0 to signify a normal exit and a non-zero value to signify exit on error. We will use the wParam set by windows API for this.
At first, code of this tutorial can seem much, but believe me this will get exponentially easier in the later steps on the tutorial. Happy Coding! :)
In this tutorial lets add menubar and toolbar to the app. It is always a good practice to keep your code organised into multiple files. Let's create utilities.h and utilities.cpp to hold our utility functions. The header file will hold constants and function declarations while the cpp file will hold the actual implementation of the functions. In utilites.h you can see some constants which are used to identify each button on toolbar and each menu separately. We will identify the file->new menu with same constant as that for the new button on toolbar. Goto utilities.cpp to see the implementation of the functions. Since HWND provides a handle to the original variable in memory, so we can add anything to our parent window if we have its handle.
The commands in addMenuBar and addToolBar functions are properly explained in comments. You can google the API functions used to know more in detail about them.
In main.cpp we have added a few more things in the WindowProcedure. Firstly, we are adding menubar and toolbar to our window once it is created. Actually, when a window is created, it issues a WM_CREATE signal once. This can be used to initialize stuff by the programmer. We have also added a case for WM_COMMAND which is issued on most events including keystrokes and clicks. We check the lower word (LOWORD), i.e., the last byte of the wParam as it contains the ID of the object which issued the command. We don't handle most of the menus now, but have placed placeholders for future. Also, note that the toolbar clicks will be caught here too since we are using same IDs for menus and toolbar buttons.
If you find undefined reference to addMenuBar and similar errors then please right click on the project in CodeBlocks and add "utilities.h & utilities.cpp" to your project using "Add files" option.
The commit for this step contains a minor error as I renamed the addToolBar (with a capital B) function in one of the files but not in others. You can easily fix this.
We will create 2 more files painter.h and painter.cpp to hold the code for actual painting on the screen. In win32 API, whenever the window has to be redraw it receives a WM_PAINT event which we will catch to draw on the window. But windows automatically clears the window before sending WM_PAINT, so we need to store the previous state of the window. For this, we will maintain a buffer, a copy of the actual window and draw on our copy. When we have to draw on the window we will simply copy our buffer onto the window, this way we will be safe from windows clearing our screen.
Every window has a DC (Device Context) which can be thought of as a board for holding the canvas/paper. Every DC has a BITMAP which acts as the paper. We always issue commands on the DC but everything is draw on the BITMAP. In out painter object we create a DC and a BITMAP compatible with the actual window and then put the bitmap into the DC. We always draw on our local DC. When we have to update the actual window, we simply use BitBlt to copy our DC's content onto the screen's DC.
Also, windows are not drawn regularly to avoid performance loss, hence we will have to force it to be drawn again and again. We do this by first invalidating the whole window and then asking windows to update it. Only the invalidated windows are updated by the API. For updating, the window is sent a WM_PAINT event where we can draw on the actaul window.
Most of the Whys of the code have been explained in comments. But the actual format of every command and all other possibilities have not been explained. Search the MSDN documentation to learn those. Also, our pencil tool is quite buggy currently which we will improve later. Once you have a grasp over drawing graphics on windows, it will be easy to add other tools for drawing shapes which we will do in the next step of the tutorial.
In this step we will add 5 tools that can be used to draw. These tools can be changed using the number pad. In order, 1 will change the tool to pencil. 2 will be rectangle. 3 will be ellipse. 4 will be filled rectangle. 5 will be filled ellipse. We will also add this info on a new menu item under Help menu. We will replace our addPoint method now with 3 different functions startPaint continuePaint and endPaint. These will be called when left button is pressed, when mouse is dragged and when left button is up respectively.
We will also use the concept of virutal functions and make a purely virtual base class PaintTool that will define how a painting tool must look like. We then derive 3 different classes PencilTool RectangleTool EllipseTool from it. All the tools will work differently. In the Painter class we will store a pointer of base class which will be initialised to the pencil tool in the constructor. Whenever user switches the tool using a key, we will destroy the current tool and create a new tool. Since we are using pointer of the base class, we can use the same to point to any tool. This way we will save ourselves the headache of doing different things when different tools are selected. Instead the tools will do their own things and we will just call them to do whatever they have to do. The powerful tool of inheritance and pointers are very useful in this sense.
Most of the code is straightforward and some comments are left to explain the code. The brush is used to fill objects and hence it needs to be set to a NULL_BRUSH if we don't want the shape to be filled. Also, since dragging the rectangle and ellipse must remove the previously drawn shape (before mouse button is left) hence we will store the state of the hdc when startPaint of such shapes is called and when we have to draw a new shape, we will first paint (BitBlt) the start state and paint the shape upon it. When the drawing is to stop, we simply clear the memory of the additional copy.
Out software is now looking like a paint tool. In the next step we will try to add the save/open functions and maybe the color changing options for brush and pen colors. Till then, happy coding! :)
In this step we will use the standard dialog boxes of windows, the file selecter and color picker dialogs. We put 3 functions in out utilites header which will do this. The code is explained in comments. We also make a function to actually write the image data into a file since the dialog boxes only return a file name. The BMP file format is standard and we follow its rules to write it to a file. The file reading part in IDM_FILE_OPEN is easier as the windows API already has a function named LoadImage that can load the image into a HBITMAP object for us.
In Painter we select DC_PEN and DC_BRUSH in hdcMem now because those pens and brushes allow us to set their color directly using SetDCPenColor and SetDCBrushColor functions. In the handleKeyPress function we do the same by using the color picker dialog from utility header. We define 2 more functions for loading image and saving image which just BitBlt the HBITMAp onto/from hdcMem. We also define a destructor to clean up memory.
Now our paint app is nearly complete. In the next and final step we will do some cleanup and add the missing functionalities. Hopefully the results of this step would motivate the readers to learn the API and develop their own projects. Happy Coding! :)
In this step we will add some cosmetic changes to our app. This will be the last step of the tutorial.
First, we add a clearScreen to our Painter which will be called in IDM_FILE_NEW to clear the screen with white rectangle. We will also change the screen to white in Painter's constructor as black pen over white seems better than white pen over black. We will also store the current file name if the screen has been saved and also store if user changes something. These will be used to define if Save or Save As has to be done when user clicks the Save button. Moreover we will add an askForSaveIfReq function to painter which will show a message box to ask the user if they want to save the changes (if there are any). This will be used when user uses either New/Open menu or closes the window. The code is self explainatory.
We will also add accelerators to our window. These are tables which map some key presses to WM_COMMAND messages. Here we will map Ctrl+ N/O/S/Q to New, Open, Save and Quit menu items. The message dispatch will now first go through the accelerator and if the message is not related to accelerator we will dispatch it normally. The code for this and accelerator creation has been explained in comments.
Finally we will fix our pencil tool too. You can't receive the mouse position at every pixel if the user moves the mouse swiftly. To get through this problem and make it look like the pencil doesn't draw distant dots and instead connected curves, we will make lines between 2 points for the pencil. Since the two points will usually be close we will get smooth results. Hence we will store the cordinate of last point where the pencil was placed.
This concludes this tutorial. Hopefully you found enough information via this tutorial to jump-start your journey into the wonderful world of GUI programming. Happy Coding! :)