A. microsoft.public.vc.mfc, microsoft.public.mfc.database, and to a lesser degree, comp.lang.c++. There are actually several news groups under the microsoft.public.vc.* listing.
A. There are several versions of Visual C++, which can be broken down into basic groups. Below, are the important features of those groups. If any feature is not specifically addressed here, then please verify with your salesperson or Microsoft before making a purchase.
Version 5.0 (Over version 4.x)
Version 4.2 (and 4.1, 4.0) (over version 2.x)
Version 2.x
Version 1.0, and 1.52
Enterprise Edition (Over Professional Edition)
Professional Edition (over Standard Edition)
Standard Edition
Learning Edition
Academically Priced
A. Basically, everything. Some things are harder to correct after AppWizard is done then others. Before you create a program with AppWizard, you should make sure you know the following things (you will be prompted by AppWizard):
As you are learning about AppWizard, it's OK to through away the results of AppWizard, because it only takes a minute to re-create it. But, as your programs become more complex, you will want to avoid this. Like the plague. Run through AppWizard several times, to test the results if possible, before committing to a particular program style.
Q. What's the difference between a CView and CFormView?
A. Every MFC application has a view class responsible for the program's appearance. Of the many possible view types, the two most common are:
CView - Used mostly for paint and word-processor style programs. This view class gives you control over the entire client area, but will require you to do more work in order to interact with the form, such as creating a button, and responding to it's click.
CFormView - This is used primarily for forms-based programs, like a database. This view makes it easy to add controls to a program (like a button) and respond to their events, but harder to control the client area of the form.
You get the chance to pick the view class type for your view, when you go through AppWizard. See the related topic on creating a CFormView-derived view.
A. AppWizard will have made the CFormView-derived class for you. It should already be there.
AppWizard will create the CView-derived view class for you. If you want it to be derived from CFormView, then on the last dialog box that AppWizard asks you (before clicking 'Finish'), make sure that the 'Base Class' specified for the View class is CFormView, and not CView (which is it's default). Note that you will need to highlight the view class from the list of classes that AppWizard will create for you (you will find that you can't change the base class for anything else).
A. The appearance of controls in a CFormView-derived class is handled by the built-in dialog editor:
A. Controls are normally added to views during design time with the resource editor. If you want a dynamic appearance to your program (ie, controls appear, disappear, rearrange, etc) it's usually still easier to create them in the resource editor, but control their appearance with the positioning and appearance functions supplied by the CWnd functions (like ShowWindow). The approach described here, will allow you to create and interact with dynamic controls, and should work in any view type (such as CView, where you can't add controls with resource editor). (See IsDialogMessage for more information on TAB navigation).
Step 1: Creating the control
There are 2 basic steps to creating the on-screen control. First you must construct an MFC object for the control, such as a CButton. Second, you must call the Create function for the object. You should not let the destructor for the object be called until after you are finished with the control, because the destructor will remove the on-screen control. Calling the constructor for the MFC object is very easily, simply declare an instance of the object.
The Create routine will vary, depending on the control. Basically, the Create routine controls the appearance, positioning, and properties of the onscreen control. Create will create the on-screen control, and associate it with the MFC object. You can now interact with the on-screen control via the MFC object.
Step 2: Interacting with the control
As mentioned earlier, you can interact with the on-screen control via the MFC object you used to Create it. But, if the contol sends messages to the application, you will need to manually add an entry into the message map for your view class. Make sure that you enter this manuall entry inside the message map, but but not in the message map section maintained by class wizard.
Basic messages, such as BN_CLICKED, from a button, are sent as WM_COMMAND messages to the application. More complex messages are sent as WM_NOTIFY messages. In the message map table, you will add either an ON_COMMAND or ON_NOTIFY entry. For either entry, the basic format is the same: it specifies the ID if a control, and the function to be called if that ID sends that message.
For a function to be used in an ON_COMMAND entry, it should be a member function, and have the following prototype:
afx_msg void MemberFunction();
For an ON_NOTIFY entry, the call-back function should be a member funciton, and have the following prototype:
afx_msg void MemberFunction( NMHDR * pNotifyStruct, LRESULT
* result );
Note: If you will be creating a range of dynamic controls, you can specify that they all invoke a single function with the ON_COMMAND_RANGE or ON_NOTIFY_RANGE macros, for entries in the message map.
Example:
Added to the View class definition:
public:
afx_msg void OnBtnPress();
Added as last line to the message map, just before END_MESSAGE_MAP():
ON_COMMAND(3000, OnBtnPress )
The following function is added to the view class, and demonstrates how to add the button at runtime, and might be invoked by a menu option:
void CDynBtnView::OnAddButton()
{
static int UniqueID=3000; // Controls need a Unique ID
// i is just used to make buttons appear in sequential order
int i = UniqueID-3000;
CRect Rect( 0, i * 25, 100, i*25+25 );
// The CButton is created here, and never released. This is a memory
// leak, which should be resolved in real use. The main point is
// this: You do not want the destructor for the CButton called in this
// function, since it will remove the button from the screen also.
CButton *TmpBtn = new CButton;
CString Caption;
Caption.Format( "Button%d", UniqueID );
// The Create function creates the on-screen button, and associates it with
// the CButton object.
if( TmpBtn->Create( Caption, WS_TABSTOP|WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, Rect, this, UniqueID++ )==0 )
MessageBox( "Failed" );
// That's it, the button should now be on screen
}
This function is added to the view class, and gets called when a dynamic button is pressed.
void CDynBtnView::OnBtnPress()
{
// Because of a message map entry, this
//function will be called when the dynamic
//button is pressed
MessageBox( "Clicked" );
}
Download the dynbtn.zip file for an example.
Normally, it's assumed that the use of TAB and SHIFT-TAB to move from control-to-control in a Windows program is part of Windows itself. This is only partially true. While windows provides the function to perform this navigation, the program is responsible for calling the function.
The IsDialogMessage function is the function which an application must call for 'normal' dialog-box processing. This function must be part of the message-processing loop. Since this function will perform all the necassary translating and dispatching of messages, the TranslateMessage and DispatchMessage functions must not be called if this function returns true.
If you're using MFC, then the best place to insert the call to this function would be in the PreTranslateMessage function, which should be added via ClassWizard to your view class. For example:
BOOL CWhateverView::PreTranslateMessage(MSG* pMsg)
{
if( IsDialogMessage( pMsg ) )
return TRUE;
else
return CView::PreTranslateMessage(pMsg);
}
Note that this problem doesn't occur with a CFormView-derived view, because the CFormView class has a PreTranslateMessage handler which (indirectly) calls IsDialogMessage. Information about creating controls dynamically.
Download the dynbtn.zip demo for an example.
A. In order to display a bitmap using MFC, several basic steps must be performed:
The following steps are a demonstration:
CDC memDC;
CBitmap Bitmap;
memDC.CreateCompatibleDC( pDC );
Bitmap.LoadBitmap(IDB_BITMAP1);
memDC.SelectObject( Bitmap );
pDC->BitBlt( 0, 0, 48, 48, &memDC, 0, 0 ,SRCCOPY );
Notes
A. Normally, you can tell where a user clicks a bitmap (a hot-spot) by comparing the user's click position with certain pre-defined rectangles of a bitmap. The OnLButtonDown or OnLButtonUp functions can be added (via classwizard) to your view, to help you handle these click events.
But, for some bitmaps, such as photo-style bitmaps, the regions that a user can click on are not rectangular, and are therefore harder to determine. The solution to the problem detailed here, originally appeared in a PC magazine article, but I can't recall the author. Full credit goes to that author.
The trick to handling non-rectangular 'hot-spots' for a bitmap, is to have 2 bitmaps: 1 bitmap serves as what the user will be looking at. The other bitmap serves as a 'look-up' bitmap. The 'lookup-bitmap' is the only one that should require further explanation, and is the basis for this approach.
The lookup bitmap is a copy of the original, displayed bitmap. However, in a paint program, you have gone into it and 'blocked-out' the hot-spots with specific colors. For example, imagine there is an image with a map of the United states. In the lookup bitmap (with a paint program), you completely color in each state with a unique color (use solid, not dithered, colors). While it's helpful to know the color values for the colors you have picked for each state, it's not that hard to determine them with a few changes to your program.
Once the lookup bitmap has been created and saved, it's loaded, and selected into a memory device context, just as if you were going to display it (but you won't display it). See ShowBitmap. The display bitmap is displayed, as usual.
You'll need to add (via Class Wizard) a function that gets called when the user clicks inside the image, something like OnLButtonUp, which is the function to handle the WM_LBUTTONUP message. In this function, you will receive as a parameter, the location of where the mouse was clicked.
You now take the location of that mouse click, and call the GetPixel function for the memory device context with it. This will look into the look-up bitmap and retrieve whatever color is at that spot. Since each state in the lookup bitmap has a unique solid color, you can now identify which state was clicked, by the color that is returned.
An example function might look like:
void CMFCBmpView::OnLButtonUp(UINT nFlags, CPoint point)
{
CDC memDC;
CBitmap Bitmap;
COLORREF Clr;
CString Color;
memDC.CreateCompatibleDC( NULL );
Bitmap.LoadBitmap(IDB_BITMAP1);
// IDB_BITMAP1 is replaced with whatever the lookup bitmap ID is
memDC.SelectObject( Bitmap );
Clr = memDC.GetPixel( point );
// At this point, Clr is the unique color for the 'hot-spot'
Color.Format("%lX", Clr );
MessageBox( Color );// This will simply show you the color value
CView::OnLButtonUp(nFlags, point);
}
There is a demo
available for download.
A. There are a couple steps here. You will need to create a dialog using resource editor, to prompt the user for the login/password. Then, create a new class (using class wizard) for the dialog, making sure that it is derived from a CDialog class. ClassWizard should let you pick the dialog once you've specified that it's derived from a CDialog.
Now, with that new password class, add the following to the InitInstance of your Application object (it's derived from CWinApp, and will have the same name as your program in the form CNameApp):
DlgLogin X; // Assuming DlgLogin was the name of the class you created.
if( X.DoModal()==IDCANCEL)
return( FALSE );
That should do it. Now if the user clicks cancel in the login dialog, the program will terminate and proceed no further.
We still have to decide how the login and password are checked.
You can get information from a CRecordSet, or a data file, or
from the system itself if you're on a network (depending on the
network). I would suggest that whatever validation code you create
be placed in the OnOK function for your new dialog class. This
way it's centralized, and you only need to add this class (ad
the above 2 lines of code) to perform future logon validation.
A. There are a couple methods to resize the main window (CMainFrame in your project) to a specific size. If you want to specifically minimze or maximize the program when it first starts up, you want to over-ride the ActivateFrame member function of the CMainFrame class in your project.
ActivateFrame is called by the framework just prior to the main window being made visible. It takes a single parameter that indicates the type of 'show' that should be performed, and it's default value is SW_SHOW, which will use Windows' default size and position. To change this:
CFrameWnd::ActivateFrame(nCmdShow);
CFrameWnd::ActivateFrame(SW_MAXIMIZE);
That's it. The program will now start up in maximized mode. You can also specify SW_MINIMIZE, if you want the program to start up minimzed.
Also, if you haven't already generated the project yet, then when you go through class wizard, you will notice an 'Advanced' button in the '4th (of 6)' dialog. Click the Advanced button and go to the Windows Styles tab. Here, you can selected whether the program should start up maximized or minimzed.
Finally, you can also manually adjust the exact size and position of any window by over-riding it's PreCreateWindow function. The parameter passed to this function is a LPCREATESTRUCT variable, which is a pointer to a CREATESTRUCT structure. Inside this structure, you will find the position, and size specifications of the window about to be created. You can change these values, and pass them on to the base class's PreCreateWindow function.
A. There are two common things you might want to do with a title bar: Disable the document name, or change it's title altogether. MFC normally creates a CMainFrame class with new projects that is the main window, that contains one or more views. The default title for the window is 'Document - Program Title'.
To control the document name:
To remove the document name from the caption bar so that just the application title exists, go to the PreCreateWindow function in the CMainFrame class of your application. Above the return statement, add the following line:
cs.style &=~(FWS_PREFIXTITLE|FWS_ADDTOTITLE);
This line will turn off the style bits on the CMainFrame that control frame window appearance, which are normally on. PreCreateWindow generally permits you to alter a window's definition just prior to creation, for standard Windows styles, but this particular using an MFC window style.
To control the appearance of the title bar more dynamically, during runtime, add code to the OnDraw (for CView), or WM_PAINT (for CFormView) message handlers. The following example will also remove the document name, if it's untitled:
if( GetDocument()->GetTitle() == "Untitled" )
{
// Retrieve the program name from resource string
CString m_strProgram, m_strFullString;
if (m_strFullString.LoadString(IDR_MAINFRAME))
AfxExtractSubString(m_strProgram, m_strFullString, 0);
AfxGetMainWnd()->SetWindowText( m_strProgram ); // Sets the caption
}
To Control the program name:
The program name that appears in the title bar is initially controlled by the 'Advanced' button of the 'Step 4 of 6' dialog box of AppWizard. You can directly edit this value after the project has been created by editing the string table in resource editor, and changing the string in front of the first '\n', in the string with an ID of IDR_MAINFRAME. You can also control the appearance more dynamically by using the code above.
A. The Tab Control is a part of the new Common Control collection from Microsoft. A Tab Control is placed on a form, or dialog, and then you must do the following:
There is a demo program available for download called tabs.zip.
A. In the resource editor, select the combo-box you want to adjust, and click the down-array of the combo-box. This should change the cursor and the selected region. Adjust this to adjust the height of the drop-down section.
A. When you have a CFormView, it's contents, but not it's size, are controlled by a dialog resource in your project. To have the entire program sized so that it neatly matches that of it's dialog resource, add the following two lines of code to your OnInitialUpdate function in your view class, after the call to CFormView::OnInitialUpdate:
GetParentFrame()->RecalcLayout();
ResizeParentToFit(FALSE);
A. The CBitmap class provides just about all of the functionality needed for working with bitmaps, but it does not support loading data from a .BMP file. For this, you'll need to write your own code to load the file. There is a demonstration class on this site called CMBitmap that has this feature already, which you can download with a demo program. The CMBitmap also correctly handles the bitmap's palette when it computer is on a 256 color or less video display.
A. The trick here is to know when the new default document is created. You should notice the following code in your InitInstance function, for your application class (it has the same .cpp filename as your project):
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
This is the code that is responsible for handling the processing of command line parameters to your program. ParseCommandInfo will parse the command line options, and put the values into the cmdInfo object. ProcessShellCommand will use this data to do things like open a file, if it's name was specified on the command line.
Inside the CComandInfo class, is a data member called m_nShellCommand, which identifies the type of command to be executed, such as file new, open, print, etc. The constructor for CCommandInfo sets the m_nShellCommand member to a value of CCommandLineInfo::FileNew, and this is where the 'new file' option comes from.
So, to stop the program from creating a new, blank, default document at program startup, change the m_nCommandShell member to CCommandLineInfo::FileNothing, just before the call to ParseCommandLine. For example:
CCommandLineInfo cmdInfo;
// Stop the blank default document from being created:
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;
ParseCommandLine(cmdInfo);
A. MFC pays attention to the document types registered with the CDocTemplate and MultiDocTemplate classes (for SDI and MDI, repsectively). Normally, MFC assumes that for each document type (and therefore, each file extension type) a CDocument class has been defined to handle that type. It's not actually important to create different document classes (one CDocument can handle several document types).
The code you want to look at is in the InitInstance of your
application object. Here, you will find code that looks similar
to:
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate( // or CDocTemplate
IDR_SOMETYPE,
RUNTIME_CLASS(CSomeDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CSomeView));
AddDocTemplate(pDocTemplate);
This call registers a CDocument class and the view and file
types associated with it. Your key here is the IDR_SOMETYPE, the
second parameter. If you look for this ID in the string table,
you will find something like:
\nSome\nSome\nSome Files (*.som)\n.som\nSome.Document\nSome Document
This is where MFC stores information about the CDocument's file type. Of particular interest, is the 4th and 5th portions (each portion seperated by a \n') which are "Some Files (*.som)\n.som". These are the file type and mask that appear in the file-open dialog.
The 'Standard' way to add a second (or any number) file type is to do the following:
1) Add a new resource string (noting it's ID), simiar to the above,
but with the new file type and extension:
\nSome\nSome\nSome Other Files (*.sof)\n.sof\nSome.Document\nSome
Document
2) Repeate the code found above, but this time using the ID
of the new string resource:
pDocTemplate = new CMultiDocTemplate( //or CDocTemplate
IDR_MYSECONDTYPE, // the ID of the string from #1
RUNTIME_CLASS(CSomeDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CSomeView));
AddDocTemplate(pDocTemplate);
When you compile the program, you should see the second file
extension appear on the File Open dialog. If you actually wanted
a second CDocument class to handle that file type, you would have
changed the second parameter to CMultiDocTemplate to that document
class name, instead of CSomeDoc.
A. If you want to parse the command line arguments to an MFC
program, the
recommended way is to write a class to do it. Part of the reasoning
here is that MFC
will automatically parse command line options like loading or
printing a file. The code
you want to look for is in the InitInstance of your application
class (it has the same .cpp
filename as your project). Here you will find:
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
This is the code that handles the file open or print for you.
The way it works, is that
MFC will call the ParseParam function of the CCommandInfo object
for each word on
the command line, inside the ParseCommandLine function.
What you want to do is:
1. Create a new class, derived from CCommandLineInfo
2. In your new class, add any data members needed to reflect the
command line
arguments.
3. Write your own ParseParam function inside this class. You'll
want to make sure that
you still call the original CCommandLineInfo::ParseParam function,
to handle the normal
parameters that you won't be handling.
So, your new class might look something like:
class CMyCmd : public CCommandLineInfo { public: BOOL JustTerminate; void ParseParam( LPCSTR lpszParam, BOOL bFlag, BOOL bLast ) { if( bFlag && strcmp( lpszParam, "exit" )==0 ) // handle "/exit" JustTerminate=1; else { JustTerminate=0; CCommandLineInfo::ParseParam( lpszParam, bFlag, bLast ); } } CMyCmd(); virtual ~CMyCmd(); }; 4. In your InitInstance, make sure you use your new class instead of the CComandLineInfo:// Parse command line for standard shell commands, DDE, file open
// CCommandLineInfo cmdInfo; - Removed, so we use our new class
CMyCmd cmdInfo;
ParseCommandLine(cmdInfo);
if( cmdInfo.JustTerminate ) // Use our custom data members
return( FALSE );
5. After ParseCommandLine, add whatever code you need to work
with the data
members that were set by ParseParam, as demonstrated by JustTerminate
above.
A. You will want to handle the OnSetCursor function for the view or dialog class that you want to control. You can add this function by going to class wizard, and adding a message map for WM_SETCURSOR. You will be given a function called OnSetCursor. The following code demonstrates how to test for the mouse over a child window (The OK button in our example), or just a specific client region (the upper left corner, in our example). This example was done in the CAboutDlg class, which is generated for all SDI and MDI projects:
BOOL CAboutDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
// MG: Change cursor when over a window
if( pWnd== GetDlgItem( IDOK ) )
{
::SetCursor( ::LoadCursor(NULL,IDC_CROSS) );
return(1);
}
// MG: Change cursor when over a non-window area
if( nHitTest==HTCLIENT )
{
POINT point;
GetCursorPos( &point );
ScreenToClient( &point );
if( point.x < 20 && point.y < 20 )
{
::SetCursor( ::LoadCursor(NULL,IDC_CROSS) );
return(1);
}
}
return CDialog::OnSetCursor(pWnd, nHitTest, message);
}
Note: You can use LoadCursor to load a cursor from your resource file, so you can implement custom cursors using the resource editor.
A. The GetProfileXXXXX functions are member functions of your CWinApp class. Therefore, you can call them in IniInstance, but not from your view or document class. To do this, you need to call the AfxGetApp() function, which returns a pointer to the CWinApp used by your project (it's a global, called 'TheApp', in all projects). For example:
CString X = AfxGetApp()->GetProfileString( Family, Setting, Default );
A. Using ClassWizard, you want to add a handler for the WM_CTLCOLOR message. When you do this, you will be adding a function called OnCtlColor. This function will be called by MFC before each control is drawn, as well as before the dialog background is drawn. The function created will look like the following:
HBRUSH CSomeDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
You should return the desired brush to draw pWnd, and select it into the pDC object.
This method won't work for buttons. To do this, you will need to make the button an Owner-Draw button, and then handle the drawing your self. Take a look at the MS Knowledge base article #Q117778 for details on how to approach this.
But, be warned: Changing colors isn't recommended. People are used to seeing a specific color, and there's no gaurantee that the color scheme you find appealing will also be appealing to others. Also, anyone who has changed there system colors, might not like your dialog 'breaking the rules', and looking different than the colors they have selected.
Q. I changed my Icon, in IDR_MAINFRAME, but I don't see my custom Icon, why?
The Icon in IDR_MAINFRAME is the one that MFC uses for the
program Icon. But, if
you change the icon, you also need to change the 16x16 version
of it. To do this, bring up
the icon in the resource editor, and click in the 'Device' combo-box.
You will then have
the ability to change the resolution to 16x16, and edit the icon
that MFC uses for small
icons for your program.
Q. How can I determine the IP address of the computer my app is running on?
A. You want to look into the WSAStartup and gethostname functions. Using these functions, you can aquire a pointer to a HOSTENT structure, which contains an array of IP addresses (there may be 1 for local network, and 1 for dialup networking, etc.). The IP addresses are stored within the h_addr_list member of the HOSTENT structure, and are stored as 4 seperate bytes.
HOSTENT * Host; WSADATA wsaData; char Str[128]; WSAStartup( MAKEWORD(2,0), &wsaData ); gethostname ( Str, sizeof(Str) ); Host = gethostbyname ( Str ); Str[0]='\0'; for( int i=0; Host->h_addr_list[i]; i++ ) sprintf( Str+strlen(Str), "%u.%u.%u.%u ", Host->h_addr_list[i][0]&0xFF, Host->h_addr_list[i][1]&0xFF, Host->h_addr_list[i][2]&0xFF, Host->h_addr_list[i][3]&0xFF ); MessageBox( Str );
Q. When I create a control class derived from a normal window control class, it's Create function isn't invoked after I attach a member variable to it in a dialog.
For purposes of this question: Let's assume you created a class called CPersonLst, derived from CListCtrl. You want this class to automatically create the columns "Name" and "BDay" for this control.. So, you add the code to do so in the Create or OnCreate mehtods of the class, then placed a List Control on a dialog, and created a 'Control' category member variable for the List and made it a CPersonList type.
A. The problem here is that MFC is creating the List control, then attaching it to your class (since you created the member variable for it). This 'attaching' is also called 'sub-classing'. What you want to do is add the code that adds the columns into the PreSubclassWindow message of your derived class, which you can do via ClassWizard.
Had you created the control dynamically, at run-time, then the Create and WM_CREATE messages would have worked.
Q, I want the user to pick a directory/folder, how do I show that dialog?
A. This is not an MFC question, but an SDK one, as MFC does not provide access to the directory-picking dialog as it does the other common dialogs. You want to use the ShBrowseForFolder function. The following function demonstrates it's usage:
bool PickDir( HWND m_hWnd, const char *Prompt, char * Dest ) { // Dest is assumed to be _MAX_PATH characters in length BROWSEINFO bi; ITEMIDLIST * pItemIDList; char Folder[_MAX_PATH]; memset( &bi, 0, sizeof(bi) ); bi.hwndOwner=m_hWnd; bi.pszDisplayName=Folder; bi.lpszTitle=Prompt; if( (pItemIDList=SHBrowseForFolder( &bi )) != NULL ) { SHGetPathFromIDList( pItemIDList, Dest ); return(true); } else return( false ); }
Q, My Edit- Cut/Copy/Paste menus don't work. How do I use the Clipboard?
A. By default, the Cut, Copy, and Paste options of the clipboard are disabled in your application. Part of this has to do with the complexity of working with the Clipboard and it's ability to store a variety of data types such as bitmaps and text. You will have to enable and disable the menu items yourself (via code), as well as implement the Cut/Copy/Paste operations you want. See end of this topic for more details.
Menu items are enabled or disabled not with the EnableMenuItem function (as they were in the SDK), but by handling the UPDATE_COMMAND_UI message for the menu ID. You will have to add this handler for the Edit (clipboard) menu items you want to control.
In addition to this, you will also have to handle the COMMAND message for these menu items. The following sample code demonstrates how to implement Clipboard functionality for all EDIT controls on your view.
In order to simplify some of the following sample code, a helper function has been written called GetFocusClass. This function is similar to GetFocus, which returns a CWnd* to the child with the current focus. Unlike GetFocus() however, this function permits you to specify a particular window class, like "EDIT" or "LISTBOX", and if the current window is not that type, it returns NULL. Here is the helper function (added to your View class):
//////////////////////////////////////////////////////////////////////////// // Name: GetFocusClass // Descr: Returns pointer to current Focus control, if it's correct class. // Param: WinClass = Class of window desired. (Example: "EDIT") // returns: Pointer to current child window, if it's correct class, else NULL // Comment: Added via ClassView, as normal function, not a message map in ClassWizard CWnd* CWhateverView::GetFocusClass(const char * WinClass) { CWnd* pWin; BOOL Result = FALSE; // Does a valid child have the Focus? if( (pWin=GetFocus())!=NULL && pWin->m_hWnd!=NULL ) { char Text[64]=""; // If so, what is it's class (i.e., "EDIT", "LISTBOX", etc.) if(GetClassName( pWin->m_hWnd, Text, sizeof(Text) ) ) if( stricmp( Text, WinClass) == 0 ) return( pWin ); // If a match, return the CWnd* } return( NULL ); //No good, return NULL }
Next, comes a function that will enable or disable the menu items:
//////////////////////////////////////////////////////////////////////////// // Name: SetClipBoardMenu // Descr: Enables/disables the Edit Cut/Copy/Paste menu choices // Param: pCmdUI - Menu item to enable/disabled. Passed by OnUpdate???? function // returns: Nothing // Comment: Added via ClassView, as normal function, not a message map in ClassWizard void CWhateverView::SetClipboardMenu(CCmdUI* pCmdUI) { CEdit* pEdit = (CEdit*) GetFocusClass( "EDIT" ); BOOL Result = FALSE; if( pEdit ) // Make sure current control is an edit box { // If it is an edit, what menu item is being enabled/disabled? if( pCmdUI->m_nID== ID_EDIT_PASTE ) { // Paste is enabled only if there is text in the clipboard... if( OpenClipboard() ) { if( GetClipboardData( CF_TEXT ) ) Result = TRUE; CloseClipboard(); } } else if( pCmdUI->m_nID==ID_EDIT_CUT || pCmdUI->m_nID==ID_EDIT_COPY ) { // Cut and Copy are enabled only if something is highlighter int Start, Stop; pEdit->GetSel(Start, Stop); if( Start != Stop) Result = TRUE; } } pCmdUI->Enable( Result ); // Enable or Disable the menu }
The final custom function, which performs the clipboard operation (cut, copy, or paste) against any EDIT box (if it has the focus):
//////////////////////////////////////////////////////////////////////////// // Name: DoClipboardOp // Descr: Performs a Cut/Copy/or Paste clipboard operation // Param: ID = Type of operation, should be an ID_EDIT_???? id. // returns: Nothing // Comment: Added via ClassView, as normal function, not a message map in ClassWizard void CWhateverView::DoClipboardOp(int ID) { CEdit* pEdit = (CEdit*) GetFocusClass( "EDIT" ); BOOL Result = FALSE; if( pEdit ) // Is the current window an Edit box? { // If so, invoke the CEdit routine for the clipboard operation if( ID==ID_EDIT_CUT ) pEdit->Cut(); else if( ID==ID_EDIT_COPY ) pEdit->Copy(); else if( ID==ID_EDIT_PASTE ) pEdit->Paste(); } }
The functions above were all added like normal functions, using ClassView (not message mapped, with ClassWizard). Next, you will want to add the UPDATE_COMMAND_UI message map (this time using ClassWizard) for each of your ID_EDIT_???? menu choices, and call SetClipboardMenu from those new functions:
// The OnUpdate options for the Edit menu items (UPDATE_COMMAND_UI in ClassWizard) void CWhateverView::OnUpdateEditCut(CCmdUI* pCmdUI) { SetClipboardMenu( pCmdUI ); } void CWhateverView::OnUpdateEditPaste(CCmdUI* pCmdUI) { SetClipboardMenu( pCmdUI ); } void CWhateverView::OnUpdateEditCopy(CCmdUI* pCmdUI) { SetClipboardMenu( pCmdUI ); }
Finally, using ClassWizard again, you will want to add a message map for the ID_EDIT_???? menu items to handle the specific EDIT menu selection. In these functions, you will call DoClipboardOp() with the ID of the menu ID (the clipboard operation):
// The Edit menu operations (COMMAND in ClassWizard) void CWhateverView::OnEditCopy() { DoClipboardOp( ID_EDIT_COPY ); } void CWhateverView::OnEditCut() { DoClipboardOp( ID_EDIT_CUT ); } void CWhateverView::OnEditPaste() { DoClipboardOp( ID_EDIT_PASTE ); }
Unfortunately, there's no two ways about it. VC++'s support
for the clipboard sucks in comparison to other GUI enviroments.
VC++ leaves you closer to the Windows API (SDK) then other environments
like Delphi and VB. The way the clipboard works, is an application
must open the clipboard using OpenClipboard. It then calls
EmptyClipboard to clear any previous contents, then SetClipboarddata
to set new clipboard data. The basic storage format for the clipboard
is one memory handle, which can be a handle to any type of data.
When the data is set, the application calls CloseClipboard.
The application no longer owns the handle it gave to SetClipboardData.
Note: There is a demo program available for download called clip.zip which implements a slightly different method of obtaining the current window.
Q. How do I add ToolTips to my program?
A. The implementation of custom tool tips can be a little complicated, but the MFC framework provides a simple interface for basic tooltip support. The primary drawback to the MFC approach is that the tooltips are limited to about 80 characters. What you need to do:
1. In the .h file for your view or dialog, add the following message map entry (manully, not with ClassWizard), right after the DECLARE_MESSAGE_MAP macro:
afx_msg BOOL OnToolTipNotify( UINT id, NMHDR * pNMHDR, LRESULT * pResult 2. In your .cpp file for your view or dialog, add the following, just above END_MESSAGE_MAP:
ON_NOTIFY_EX( TTN_NEEDTEXT, 0, OnToolTipNotify )
3. In your OnInitDialog, or OnInitialUpdate function, add the
following line:
EnableToolTips();
4. Write the following function for your view or dialog class:
BOOL CWhatever::OnToolTipNotify( UINT id, NMHDR * pNMHDR, LRESULT * pResult ) { TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pNMHDR; UINT nID =pNMHDR->idFrom; if (pTTT->uFlags & TTF_IDISHWND) { // idFrom is actually the HWND of the tool nID = ::GetDlgCtrlID((HWND)nID); if(nID) { pTTT->lpszText = MAKEINTRESOURCE(nID); pTTT->hinst = AfxGetResourceHandle(); return(TRUE); } } return(FALSE); }
5. For each control you want a tooltip for, add a string in the String Table (resources). The string should have the same ID as the control.
If you don't want to add resource strings, you can simply assign
a pointer to the desired tool tip string to pTTT->lpszText.
Q. Why are my pop-up menus disabled in dialogs?
Basically, when a menu is about to pop up, a WM_INITMENUPOP message is sent to the parent window. In the base class of the CMainFrame, there is already a handler for the WM_INITMENUPOPUP, and it sends the UPDATE_COMMAND_UI sequence to the mainframe, the view, and the App classes, but not to the dialog.
So, you have two options:
1. Have your view class for all the menu enabling for the dialog.
I'm not crazy about this approach.
2. Have your view class pass of it's processing to the current
dialog, if one exists. I like this.
So, in order to accomplish #2, let's first take a look at how your doing your dialog now:
void CWhateverView::OnButton1()
{
CSomeDlg X;
X.DoModal();
}
In order to accomplish #2 above, we need to have the OnCmdMsg() function (added via classwizard) for your view call the OnCmdMsg for your dialog. However, the OnCmdMsg function can't see the X object in OnButton1, because it's a local variable.
So, you would do the following:
Create a member variable in your view, called pDialog, thats a
CDialog *:
CDialog* pDialog;
In your view constructor, set the pointer to 0.
pDialog = 0;
Change your OnButton1, so that it initializes the dialog pointer:
void CWhateverView::OnButton1()
{
pDialog = new CSomeDlg;
pDialog->DoModal();
delete pDialog;
pDialog = 0;
}
Finally, change your view so that it's OnCmdMsg look like the
following (you may need to add OnCmdMsg):
BOOL CWhateverView::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
if( pDialog && pDialog->OnCmdMsg( nID, nCode, pExtra,
pHandlerInfo ) )
return( TRUE );
return CFormView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
In addition to the above, in your dialog class you need to *manually* add the ON_UPDATE_COMMAND_UI and ON_COMMAND macros for the routines that will be responsible for enabling/disabling a menu item, and specifying the function to call when the menu item is invoked.