Learning to use the F3C

I have tried to make the F3C as easy to use as possible, but all things have a learning curve, and this is no exception.

Before we start, verify that you meet the standards on this checklist:

  1. Are you familiar with C++? If you said no, then you'll need to learn. Don't worry - there are plenty of books on the subject, and plenty more websites. I'd recommend the CPlusPlus.com tutorial - although it's a little technical, it's very indepth.
  2. Are you familiar with the Windows API? You don't have to know it all, but an understanding of the API is good to understand how the classes work, and how to extend their use beyond what is presented to you. To make this painless, just hop on over to http://www.winprog.org and read the tutorials there. This will have you working with the API in no time!

Some important notes

There are several things which I refer to which I should mention right now, to avoid confusion.

And that's about it for the definitions department!


The structure of the F3C

The F3C is unlike most other classes in one distinct way: there are no object files to link to. If you want the include a component, just include the appropriate header file in the appropriate place. Inside the header file is all the code required to make that component work (unless the particular component needs to be linked to something else - but that is mentioned, if required).

This makes the compiling process a little slower than it might be, but it saves trying to precompile all of the classes and then fiddling to get it to work.


A sample program, and a description

Probably the best way to show how things work is with a heavily commented sample program. If you are familiar with the API (or even just have an understanding) then you'll see how this works very quickly.


//Must have the next line, it's all windows things.
#include <windows.h>

//Change the next line to the directory where
//your F3C headers are...
//(you can do this any way that you want. This is just the
//way that I am using.)
#define F3CDIR "F3C/"

//Include the string class. You'll need it for the fFORM class
//and everything else.
#include F3CDIR"bstring.h"

//Uncomment the following line if you want to
//have the GUI components automatically
//ID themselves.
//This can be very handy, and it means that you don't have
//to assign ID's to controls. I would recommend using only
//with the GUI controller.
//You will still have to manually ID all of your menus
//and accelerators.
//#define F_AUTOID

//Uncomment the following line if you want to
//use the GUI controller.
//The GUI controller automatically passes along events.
//It saves you having to intercept them.
//You'll need to include this file before any other GUI control
//header files.
//#include F3CDIR"gui_controller_start.h"

//Include some basic header files...
#include F3CDIR"application.h"
#include F3CDIR"form.h"

//Uncomment the following line if you want to
//use the GUI controller.
//You will need to include this after you have included all
//other files if you want the GUI controller to work.
//#include F3CDIR"gui_controller_end.h"

//Declaration for Message Queue...
//You don't have to have a message queue - just pass NULL for
//the WindowProceedure when you create your application with
//the CreateApp() function of APPLICATION.
LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

//A global object. I would make all GUI object global classes,
//so they can be accessed anywhere.
fFORM Window;

//The usual WinMain - yes, you still have to use it.
int  WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInstance,
                    LPSTR lpszArgument, int nShowWindow)
     {
     //Register your application. You can pass NULL for WIndowProceedure,
     //which means that you don't have to have a message queue (but
     //if you don't, be sure to use the GUI controller).
     APPLICATION.CreateApp("YourClassName", asNormal | asRedrawOnResize, WindowProcedure);
     //Before you CreateApp(), you might like to change the
     //icon and other things. You have to do these things before
     //you CreateApp().

     //Create Window and set it up
     Window.CreateForm("Your Window Title Here", fsUsual, fsNormal);
     Window.SetSize(460, 360);
     Window.UpdateForm();
     Window.ShowForm(nShowWindow);
     Window.Center();
    
     //*****************************************
     //Your other initialisation code goes here.
     //*****************************************

     //Start the message queue...
     //This is here, regardless of GUI CONTROLLER or not.
     //Even if you don't have a message queue, you still need
     //this.
     F3C_START_MODAL
     };

//The actual message queue...
//Remember, you don't actually need this if you have the
//GUI controller.
LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
     {
     //Handle the messages like normal.
     switch (Message)   // Handle the messages
            {
            case WM_DESTROY:
                 PostQuitMessage(0);   //Send a WM_QUIT to the message queue
                                      //And thus quit.
                 break;
            default:                  //For messages that we don't deal with
                 return DefWindowProc(hWnd, Message, wParam, lParam);
            }
     return DefWindowProc(hWnd, Message, wParam, lParam);
     };

That is a very simple program to just display a Window. Let's show it again, but this time, create a buttton (the new code is red). When the button is clicked, it will show a message box.


#include <windows.h>

#define F3CDIR "F3C/"

#include F3CDIR"bstring.h"

//#define F_AUTOID

//#include F3CDIR"gui_controller_start.h"

#include F3CDIR"application.h"
#include F3CDIR"form.h"

//#include F3CDIR"gui_controller_end.h"

LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

fFORM Window;

fBUTTON Button;

//A function to call when the button is clicked...
//(This will only get called with the GUI controller)
void ButtonClicked(fBUTTON* Sender)
     {
     MessageBox(Window.GetHandle(), "The button was clicked! Neat!", "The F3C", 0);
     };

int  WINAPI WinMain(HINSTANCE hThisInstance, HINSTANCE hPrevInstance, 
LPSTR lpszArgument, int nShowWindow)
     {
     APPLICATION.CreateApp("YourClassName", asNormal | asRedrawOnResize, WindowProcedure);

     Window.CreateForm("Your Window Title Here", fsUsual, fsNormal);
     Window.SetSize(460, 360);
     Window.UpdateForm();
     Window.ShowForm(nShowWindow);
     Window.Center();

     Button.CreateWin(Window.GetHandle(), "Click Me!", 1001, bsUsual, bsNormal);
     Button.SetPos(10,10);
     //If Using GUI controller, do this:
     Button.OnClick = &ButtonClicked;
     //If not using GUI controller, see the message
     //queue.
    
     //*****************************************
     //Your other initialisation code goes here.
     //*****************************************

     //Start the message queue...
     //This is here, regardless of GUI CONTROLLER or not.
     //Even if you don't have a message queue, you still need
     //this.
     F3C_START_MODAL
     };

//The actual message queue...
//Remember, you don't actually need this if you have the
//GUI controller.
LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
     {
     //Handle the messages like normal.
     switch (Message)   // Handle the messages
            {
            case WM_DESTROY:
                PostQuitMessage(0);   //Send a WM_QUIT to the message queue
                                      //And thus quit.
                break;
            case WM_COMMAND:          //Manually handling clicks...
                                      //You would only do this
                                      //without the GUI controller...
                 if (Button.CheckClicked(wParam, lParam))
                    {
                    //If we got here, button was clicked!
                    MessageBox(Window.GetHandle(), "The button was clicked! Neat!", "The F3C", 0);
                    }
                 break; 
            default:                  //For messages that we don't deal with
                return DefWindowProc(hWnd, Message, wParam, lParam);
            }
     return DefWindowProc(hWnd, Message, wParam, lParam);
     };

Many other controls can be added in the same way. Dealing with controls is up to you. For a list of event for each control, just see the documentation for a particular control.


Menus

Menus are a funny subject, that require some special thought. There are two types of menus: main menus and popup menus. Main menus are like that found at the top of the window - the File, Edit, etcetera menus. When you click on one of those items, it shows a popup menu. Popup menus can be shown anywhere - commonly, they are shown when you right click on something.

When you create a menu for a window, you add items (File, Edit, etcetera.) and then you add popup menus to these items (to make the file menu, the edit menu, etcetera). Here is the code to add a menu to a window (using the fMAINMENU component):


//Firstly, make an instance of fMAINMENU...
fMAINMENU MainMenu;

//Now some popup menus...
fPOPUPMENU FileMenu;
fPOPUPMENU EditMenu;

//Now create the popup menus...
FileMenu.AddItem("&New",        ID_NEW);
FileMenu.AddItem("&Open...",    ID_OPEN);
FileMenu.AddItem("&Save",       ID_SAVE);
FileMenu.AddItem("S&ave As...", ID_SAVEAS);
FileMenu.AddSeperator();
FileMenu.AddItem("E&xit",       ID_EXIT);

EditMenu.AddItem("&Copy",  ID_COPY);
EditMenu.AddItem("&Paste", ID_PASTE);

//Now add them to the main menu...
MainMenu.AddPopup(FileMenu.GetHandle(), "&File");
MainMenu.AddPopup(EditMenu.GetHandle(), "&Edit");

//Now apply this to a window (ie. make a window show this menu)
MainMenu.ApplyMenu(Window.GetHandle());

//If you change the menu, you don't have to remove it
//and add it again, you can just update it:
MainMenu.UpdateMenu(Window.GetHandle());
//Changes include changing a sub-menu, which you can also do
//without having to re-add the menu (although changing a sub
//menu does not require you to do anything except update
//the main menu).

You may have noticed the & sign in the names. That ampersand means to underline the next letter, and that letter becomes a shortcut key for that item (but only when viewing a menu).

You can also create menus (complete menus) in resources and load them with the fMAINMENU class (and the fPOPUPMENU class).

You can also add normal items to a main menu - items which do not show a popup menu. Events for those are handled like any other fully-qualified menu items. Generally, though, they do not look good, so people avoid them mostly.

To trap events - is not hard - just read onto the "Advanced use of the GUI CONTROLLER" (after the next section).


Keyboard accelerators

You may have noticed that many programs make use of keys like CTRL+S to mean save. How can you do this? I've added support for things like this too...

To be able to use these, firstly you must add some entries to your resource file. An example one looks like this:

32767 ACCELERATORS
{
  "r", MI_ShowNormal
  VK_ESCAPE, MI_Close, VIRTKEY
  "a", MI_About
  "+", MI_SizeUp
  "-", MI_SizeDown
}

And the general format of one of these tables is...
32767 ACCELERATORS
{
   event, ID [, type] [options]
   ...
}
Where:
Event is one of the following things:
     a character in quotes (eg "S" or "s") - is case sensitive.
     a caret then a letter (eg "^S" or "^s") - which means CTRL+...
     an ASCII character code number
     or a VK_ value, eg VK_F1 for the F1 button. Type must be VIRTKEY.
ID is the ID of the event when it occurs.
Type is VIRTKEY if Event is VK_ value.
Options can be one of these:
     ALT     - ALT key needs to be pressed
     SHIFT   - SHIFT key needs to be pressed
     CONTROL - CONTROL key needs to be pressed

Notice that the ID of this resource is 32767 - this must always been 32767. If you #define F_USEKEYACCEL before you include application.h, the accelerators will be picked up, loaded, and implemented.

Now, how do we intercept events generated by the keypresses? Manually, the same way that you would with a menu. With the GUI controller? The same way again. Details of how to intercept these events follow...


Advanced use of the GUI controller

Things like menus and toolbars, and even accelerators send events a little differently to normal buttons, and as such, they have to be handled differently. Don't worry - you don't have to handle them manually - there is a way.

There is a second GUI controller, known as the Menu controller. If you have created a menu or keyboard accelerators, then you know that you only have ID numbers - no handles, nothing else to intercept events by. Ordinarily you can intercept these events under WM_COMMAND, like this: (the message is sent to the parent window - the window on which the menu shows, or, in the case of the popupmenu, just on your main message queue).


case WM_COMMAND:
     switch (LOWORD(wParam))
            {
            case ID_MENUITEM_1:
                 //Menu item one selected...
                 break;
            case ID_MENUITEM_2:
                 //Menu item two selected...
                 break;
              ...
            }
     break;

With main menus (at the top of a window, ie. File, Edit, etc.), there is an extra event that is sent. This event is known as WM_MENUSELECT, and it occurs when the mouse cursor is over a menu item. Often applications will intercept it and display some helpful text on a statusbar. You can manually intercept this via this code:


case WM_MENUSELECT:
     switch (LOWORD(wParam))
            {
            case ID_MENUITEM_1:
                 //Menu item one has mouse cursor...
                 break;
            case ID_MENUITEM_2:
                 //Menu item two has mouse cursor...
                 break;
              ...
            }
     break;

Now surely there has to be an easier way? Of course! The GUI Controller has this aforementioned Menu controller thing which handles all of this for you. To work with this, check out this code.


//Firstly add your menu ID to the controller...
MENUCONTROLLER.AddMenu(ID_OF_MENU_ITEM, OnMenuItemClick, OnMenuItemSelect);

//OnMenuItemSelect can be NULL if you don't want to know when the
//mouse cursor is over a menu item.

//OnMenuItemClick and OnMenuItemSelect have specific prototypes:
void OnMenuItemClick(HWND ParentWindow, int ID);
void OnMenuItemSelect(HWND ParentWindow, int ID);
//ParentWindow is the parent window, ID is the ID of the menu
//item. This means that you could assign the same function for
//all of your menu items, and then just check what item was
//clicked inside that function with a switch statement. Saves
//heaps of time!

//So, an example catch would look like this:
void FileNewClicked(HWND ParentWindow, int ID)
     {
     MessageBox(ParentWindow, "You've Just clicked File->New!", "F3C", 0);
     };

MENUCONTROLLER.AddMenu(ID_OF_MENU_ITEM, &FileNewClicked, NULL);

//If you wanted to add an OnMenuItemSelect function, you would
//do that the same way... here is an example of that...
void ShowDescription(HWND ParentWindow, int ID)
     {
     switch (ID)
            {
            case ID_OF_MENU_ITEM_1:
                 Statusbar.text = "This option opens something-or-other";
                 break;
            case ID_OF_MENU_ITEM_2:
                 Statusbar.text = "This option closes something-or-other";
                 break;
              ...
            }
     }

MENUCONTROLLER.AddMenu(ID_OF_MENU_ITEM_1, NULL, &ShowDescription);
MENUCONTROLLER.AddMenu(ID_OF_MENU_ITEM_2, NULL, &ShowDescription);
                      ...
MENUCONTROLLER.AddMenu(ID_OF_MENU_ITEM_n, NULL, &ShowDescription);

As you can see, this is a little easier than manually handling things - although you should still know how to handle them manually, in case you ever need to.

All of the same details here apply for keyboard accelerators too - all you need to do is put in the ID of the accelerators - just keep in mind that they do not send the OnMenuItemSelected event.


Congratulations!

As far as I can remember, that is about all you'll have to know to start using the F3C. Here is a template file, that you might like to use to get started.

If you don't like using WinMain, or don't like fiddling with a message queue, try this template, which uses some other things to make the programming even simpler.


Back to indexThe FreeFoote Foundation Classes Documentation