Magazine > Issues > 2003 > December >  C++ Q&A: Docking the Menu Bar, Abstract Cla...
C++ Q&A
Docking the Menu Bar, Abstract Classes vs. Interfaces, and More
Paul DiLascia

Code download available at: CQA0312.exe (334 KB)
Browse the Code Online

Q I'm writing a Windows®-based app using C++ and MFC. My app has a standard toolbar implemented using CToolBar. I want to allow the toolbar to be docked on any side of the frame window (top, bottom, left, right), but not floating. I tried various combinations of the flags CBRS_ALIGN_XXX, CBRS_FLOATING, and CBRS_SIZE_DYNAMIC, but I couldn't seem to make it work. Is there some way to make the toolbar dockable but disallow floating?
Q I'm writing a Windows®-based app using C++ and MFC. My app has a standard toolbar implemented using CToolBar. I want to allow the toolbar to be docked on any side of the frame window (top, bottom, left, right), but not floating. I tried various combinations of the flags CBRS_ALIGN_XXX, CBRS_FLOATING, and CBRS_SIZE_DYNAMIC, but I couldn't seem to make it work. Is there some way to make the toolbar dockable but disallow floating?
Meg Donovan

A Indeed there is a way, but it's not as simple as using the right combination of style flags. You have to write some code. I know, life is tough.
A Indeed there is a way, but it's not as simple as using the right combination of style flags. You have to write some code. I know, life is tough.
Figure 1 Docking the Toolbar 
Just so everyone knows what I'm talking about, MFC control bars (of which CToolBar is an example) support a feature called docking. This means the user can drag the tool/control bar to one of the edges of the frame, as in Figure 1. To enable docking, you have to set things up, usually in your frame's OnCreate handler:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  // ... create toolbar
  VERIFY(m_wndToolBar.CreateEx(..., CBRS_GRIPPER));
  m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
  // enable docking feature
  EnableDocking(CBRS_ALIGN_ANY);
  DockControlBar(&m_wndToolBar);
  return 0;
}
When you want docking, it's important to use the CBRS_GRIPPER style, which causes the toolbar to display the grip handle. When you enable your toolbar for docking, it becomes automatically enabled for floating, too. This means the user can drag the toolbar somewhere off the border and leave it "floating" in mid-air, as shown in Figure 2. But what if you want to let your toolbar dock, but not float? I was unable to find any combination of style flags to do it, but I was able to write a bit of code to make it happen. The details are a bit grody so I encapsulated (hid) them in a separate class, CNoFloatBar, which you can simply drop into your app to achieve the desired effect. Just instantiate and call CNoFloatBar::Install, as you can see in the following code:
   int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
   {
     •••
     m_noFloat.Install(&m_wndToolBar);
     return 0;
   }
Pretty easy. But how does it work? MFC uses several private classes—CDockBar, CMiniDockFrameWnd, and CDockContext, all declared in afxpriv.h—to implement docking. When I first set out to implement CNoFloatBar, I figured I'd have to dig into this code, derive new classes, and override key functions. These efforts quickly sank in a morass of details. The MFC docking code is complex, difficult to understand, and full of non-virtual functions and hardwired class names. Clearly, this code was never meant to be extended. When you find yourself contemplating whether to copy a half dozen MFC functions verbatim into your own class just so you can change one line in each, it's a sure sign there's got to be a better way. Always remember: good programmers are lazy. Who wants to write all that code?
Figure 2 Floating Toolbar 
So how can you prevent floating? Floating happens when the user drags the toolbar somewhere away from the edge of the frame. If you can prevent dragging, you can prevent floating. One way might be to override CToolBar::OnMouseMove. But it doesn't work because when the user clicks the mouse on the toolbar to begin dragging, CToolBar::OnLButtonDown passes control to an internal function that enters a new message loop. (This is neither necessary nor ideal—but hey, what can you do?) You can override OnMouseMove all day, but your code will never be called because your program has gone off on a second message loop. So how can you trap mouse messages?
With a Windows hook, of course! When the toolbar gets WM_LBUTTONDOWN, CNoFloatBar installs a WH_GETMESSAGE hook. The hook procedure intercepts WM_MOUSEMOVE and calls a helper function CNoFloatBar::ConstrainPoint to constrain the cursor to lie along the edges of the frame. If the user moves the cursor away from the edges of the frame, CNoFloatBar::ConstrainPoint uses advanced pixel arithmetic to move it back, actually modifying MSG::lParam (the mouse coordinates) on the fly before passing the message on to the toolbar. This doesn't affect the actual cursor position on the screen (you'd have to call SetCursorPos for that); it only changes what the toolbar sees. In other words, CNoFloatBar lies. The user has moved the cursor away from the frame edge, but CNoFloatBar doesn't tell the toolbar. CNoFloatBar passes false mouse coordinates, ones that always lie along the frame edge. So CNoFloatBar doesn't actually prevent the toolbar from floating; it merely prevents the toolbar from ever seeing a cursor position that would cause it to float. Pretty clever, eh? You really have to run the code to see how it works.
There are, of course, other details to fret. You may not have noticed, but while you're dragging the toolbar you can press the control key to make it float. CNoFloatBar traps WM_KEYUP/DOWN to prevent this. MFC also lets users double-click to toggle the toolbar from floating to docked state. CNoFloatBar prevents this, too. Finally, in order to encapsulate all this no-float behavior in a separate class which you can plug into your app without deriving from CToolBar, CNoFloatBar derives itself from my world-famous CSubclassWnd class. CSubclassWnd provides a way to trap messages sent to any CWnd-derived object.
To show how everything works in practice, I wrote a little program called TestNoFloat that uses CNoFloatBar to implement a command, View | Allow Floating Toolbar, which turns the no-float feature on or off. Figure 3 shows the code for CNoFloatBar. As always, you can download the full app from the link at the top of this article.

NoFloat.h
///////////////////////////////////////////////////////////////
// MSDN Magazine — December 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#pragma once

#include "subclass.h"

//////////////////
// Class used to implement non-floating control bars.
// To use it, instantiate in your CMainFrame, then install like so:
//
//    m_noFloat.Install(&m_wndMyControlBar);
//
// The control bar can now dock but not float. Install one for each 
// control bar.
// 
class CNoFloatBar : public CSubclassWnd {
protected:
   static CNoFloatBar* g_pNoFloat;   // global: this
   static HHOOK g_hook;              // global: hook
   static LRESULT CALLBACK GetMessageHook(int code, WPARAM wp, 
      LPARAM lp); 

public:
   CNoFloatBar();
   virtual ~CNoFloatBar();

   BOOL Install(CWnd* pWnd) {
      return HookWindow(pWnd);
   }
   void Remove() {
      HookWindow((HWND)NULL);
   }

protected:
   // override for different mouse constraint
   virtual void ConstrainPoint(POINT& pt);

   // Rarely override:
   virtual BOOL OnGetMessageHook(int code, WPARAM wp, LPARAM 
                                 lp);
   virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
   DECLARE_DYNAMIC(CNoFloatBar);
};
NoFloat.cpp
///////////////////////////////////////////////////////////////
// MSDN Magazine — December 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual Studio .NET on Windows XP. Tab size=3.
//
#include "stdafx.h"
#include "NoFloat.h"
#include <afxpriv.h> // CDockBar

// Globals. Note: this implementation assumes only one non-
// floating toolbar in the application. If you have several, 
// you must make some provision for obtaining the CNoFloatBar 
// object somehow—for example from the window handle (HWND) by 
// looking it up in a map/hashtable.
//
HHOOK CNoFloatBar::g_hook = NULL;             // windows hook handle
CNoFloatBar* CNoFloatBar::g_pNoFloat = NULL;  // ptr to global 
                                              // object

IMPLEMENT_DYNAMIC(CNoFloatBar, CSubclassWnd);

CNoFloatBar::CNoFloatBar()
{
}

CNoFloatBar::~CNoFloatBar()
{
}

//////////////////
// Handle message sent to control bar.
// I'm interested in mouse down and double-click.
//
LRESULT CNoFloatBar::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
   if (msg==WM_LBUTTONDOWN) {
      // mouse down: install get message hook
      ASSERT(g_hook==NULL); // can't be already hooked!
      g_pNoFloat = this;
      g_hook = SetWindowsHookEx(WH_GETMESSAGE,
         GetMessageHook, NULL, ::GetCurrentThreadId());

   } else if (msg==WM_LBUTTONDBLCLK) {
      // don't allow double-click switch to floating state
      return 0;
   }
   return CSubclassWnd::WindowProc(msg, wp, lp);
}

//////////////////
// Hook proc: pass to virtual fn.
//
LRESULT CALLBACK CNoFloatBar::GetMessageHook(int code, WPARAM 
    wp, LPARAM lp)
{
   if (code==HC_ACTION) {
      ASSERT(g_pNoFloat);
      if (!g_pNoFloat->OnGetMessageHook(code, wp, lp)) {
         // object returned FALSE: unhook
         LRESULT lRet = ::CallNextHookEx(g_hook, code, wp, lp);
         UnhookWindowsHookEx(g_hook);
         g_hook = NULL;
         return lRet;
      }
   }
   return ::CallNextHookEx(g_hook, code, wp, lp);
}

//////////////////
// Virtual hook proc: constrain mouse movement, prevent 
// Shift/Control to float.
//
BOOL CNoFloatBar::OnGetMessageHook(int code, WPARAM wp, LPARAM lp)
{
   MSG& msg = *((MSG*)lp);
   if (msg.message==WM_LBUTTONUP || ::GetCapture()!=m_hWnd) {
      // unhook if mouse up or if the dock context released 
      // capture
      return FALSE; 

   } else if (msg.message==WM_MOUSEMOVE) {
      // mouse move: alter coords to possible docking area
      ConstrainPoint(msg.pt);
      CWnd* pWnd = CWnd::FromHandle(msg.hwnd);
      CPoint pt = msg.pt;
      pWnd->ScreenToClient(&pt);
      msg.lParam = MAKELPARAM(pt.x, pt.y);

   } else if (msg.message==WM_KEYUP || msg.message==WM_KEYDOWN) {
      // special keys that can end up floating: disallow
      if (msg.wParam == VK_SHIFT || msg.wParam == VK_CONTROL)
         msg.wParam = 0; // harmless
   }
   return TRUE; // keep hooking
}

//////////////////
// Constrain point to possible dockbars 
// (top/bottom/left/right).
// Algorithm: loop through all dockbars, compute point on 
// dockbar that mouse would snap to if forced to dock to that 
// dockbar, then take closest of these. Distance squared is as 
// good as distance for our purposes. 
//
void CNoFloatBar::ConstrainPoint(POINT& pt)
{
   CFrameWnd* pFrame = CWnd::FromHandle(m_hWnd)->GetParentFrame();
   ASSERT(pFrame);
   CRect rc;
   pFrame->GetWindowRect(&rc);

   CPoint ptSnap;
   UINT distance2 = (UINT)-1;
   POSITION pos = pFrame->m_listControlBars.GetHeadPosition();
   while (pos != NULL) {
      CDockBar* pBar = (CDockBar*)pFrame->m_listControlBars.GetNext(pos);
      if (pBar->IsDockBar()) { // always true for real CDockBar
         CRect rc;
         pBar->GetWindowRect(&rc);
         CPoint ptBarSnap(min(max(pt.x, rc.left), rc.right),
                          min(max(pt.y, rc.top),  rc.bottom));
         int dx = ptBarSnap.x - pt.x;
         int dy = ptBarSnap.y - pt.y;
         UINT d2 = dx*dx + dy*dy;
         if (d2 < distance2) {
            // this snap point is closer than previous ones: 
            // use it
            distance2 = d2;
            ptSnap = ptBarSnap;
         }
      }
   }
   pt = ptSnap;
}

Q In the Microsoft® .NET Framework, what's the difference between an abstract class and an interface? When should I use one as opposed to the other?
Q In the Microsoft® .NET Framework, what's the difference between an abstract class and an interface? When should I use one as opposed to the other?
Sam Keiko

A Good question! In the .NET Framework, an abstract class is any class that's declared with the abstract keyword. Usually—but this is not required—such a class has at least one abstract method. For example, take a look at the following code:
abstract class Shape
{
  // abstract method/property
  public abstract double Area { get; }
  // concrete method/property/data
  private Rectangle bounds;
  public Rectangle Bounds {
    get { return bounds; }
  }
}
A Good question! In the .NET Framework, an abstract class is any class that's declared with the abstract keyword. Usually—but this is not required—such a class has at least one abstract method. For example, take a look at the following code:
abstract class Shape
{
  // abstract method/property
  public abstract double Area { get; }
  // concrete method/property/data
  private Rectangle bounds;
  public Rectangle Bounds {
    get { return bounds; }
  }
}
Shape is a generic class that has a read-only property called Area. The Shape class doesn't implement this property itself. Area must be supplied by a derived class, as shown here:
// concrete class implements Area
public class Circle : Shape
{
  private double r;
  public double Area 
  { 
    get { return pi*r*r; } 
  }
}
In C++, you make a class abstract by giving it at least one abstract (pure) virtual function. In C#, you do so by declaring the class abstract; in C++ use the "=0" syntax:
class CShape
{
  // pure (abstract) virtual fn
  double GetArea() = 0;
}
Unlike C#, C++ has no way to declare a class abstract; you must declare at least one pure virtual function using the initialize-to-zero syntax. But in both C++ and C#, an abstract class cannot be instantiated. It can be used only to derive new classes:
// C# — error!
Shape s = new Shape();
// C++ — error!
CShape* pShape = new CShape();
A good example of an abstract class in MFC is CView. This class implements basic functionality for a window that displays the contents of a document. It provides data and functions that implement basic view behavior such as command routing, window sizing, display update, and so on—but CView leaves out one function, CView::OnDraw:
// from afxwin.h
class CView : public CWnd
{
  •••
  virtual void OnDraw(CDC* pDC) = 0;
};
CView doesn't implement OnDraw because it doesn't know how. Despite aspirations of clairvoyance, the programmers in Redmond can't know what you intend to draw or how. It's up to you to supply the implementation. So OnDraw is abstract. (Here's an MFC trivia question: which other CView function is abstract?)
So much for abstract classes. What about interfaces? An interface is an abstract class that has no data members and whose methods are all abstract:
public interface IShape
{
  public abstract double Area { get; }
  // no data, no implementation
  public abstract Rectangle Bounds { get; } 
}
While an abstract class may provide partial implementation in the form of concrete methods and data, an interface has neither. In C++/OOP terminology, such a class is sometimes called an abstract base class (ABC). You could say that an interface is purely abstract. Like abstract classes, interfaces can be used only to derive other classes; they cannot be instantiated directly. In C#, the concept of interface is built into the language (through the interface keyword). In C++, there's no explicit concept of interface. In C++, interface is a design pattern that you decide to adopt.
Formal definitions are fine, but what's really going on? What's the best way to think about abstract classes and interfaces? You should think of an interface as a list of methods (or properties) that a class must implement in order to "be" the class. A typical example from the .NET Framework is IDisposable. It has just one method—Dispose.
public interface IDisposable
{
  void Dispose();
}
What this means is that in order to "be" a disposable thing, a class must implement a method called Dispose. A single class may implement several interfaces. For example, in the .NET Framework, ArrayList implements IEnumerable as well as IList and ICollection. An array list "is" enumerable and "is" a list and "is" a collection. An array list can be all these things because it implements the required methods for each interface. Interfaces are the building blocks for systems of reusable code objects.
Whether in COM, .NET, Java, or SNOBOL (just kidding), interfaces provide a way to implement multiple inheritance, without the implementation problems associated with multiple inheritance in C++. One crucial difference between multiple inheritance using interfaces and multiple inheritance by inclusion (as in C++) is that with interfaces you can't implicitly treat any object as an instance of some interface it implements, the way you can with C++ multiple inheritance. You must explicitly obtain the interface before using it—either by casting, as in C# and Java, or by calling a special method like QueryInterface in C++/COM.
So to make a long story short, interfaces are purely descriptive, with no implementation; abstract classes are part description, part implementation. The chart shown in Figure 4 summarizes some of main similarities and differences between abstract classes and interfaces in the C# and C++ languages.

Abstract Class Interface
Cannot instantiate. Cannot instantiate.
Can only be used to derive. Can only be used to derive.
Can contain data and/or implementation in the form of concrete methods. No data, no implementation. (All methods/virtual functions must be abstract/pure.)
In C#: declare both class and abstract methods with abstract keyword. In C#: declare class with interface keyword. All methods become virtual.
In C++: declare at least one pure virtual function using "= 0" syntax. Can use destructor, too. In C++: no concept of interface built into C++; interface is a design pattern you must follow.
Use when you want to provide a partial implementation with some functions omitted. Use when you want to define the abstract qualities of a thing, the methods a class must implement to "be" that thing.

Q I'm trying to migrate a small test program from C++ and MFC to C# and .NET. It seems that some things are easier with the .NET Framework while other things are more difficult. I'm having trouble setting the default location and style of a window/control. In MFC I'm used to overriding PreCreateWindow to modify the CREATESTRUCT. Is there some equivalent in the .NET Framework?
Q I'm trying to migrate a small test program from C++ and MFC to C# and .NET. It seems that some things are easier with the .NET Framework while other things are more difficult. I'm having trouble setting the default location and style of a window/control. In MFC I'm used to overriding PreCreateWindow to modify the CREATESTRUCT. Is there some equivalent in the .NET Framework?
Justin Grover

A Indeed, there is! When the .NET Framework needs to register or create a control, it first calls Control.CreateParams to get the create parameters. Control.CreateParams is a read-only property that returns an instance of class, also called CreateParams, that holds the .NET equivalent of CREATESTRUCT. Since CreateParams (the property) is virtual, you can override it. Figure 5 shows a code snippet that demonstrates how. In the snippet, MyControl is created with the WS_BORDER and WS_POPUP styles, as well as WS_EX_TOOLWINDOW. These symbols aren't defined in Windows.Forms, so you must define them yourself using the values in winuser.h. Make sure you call base.CreateParams to get the default CreateParams before modifying them.
A Indeed, there is! When the .NET Framework needs to register or create a control, it first calls Control.CreateParams to get the create parameters. Control.CreateParams is a read-only property that returns an instance of class, also called CreateParams, that holds the .NET equivalent of CREATESTRUCT. Since CreateParams (the property) is virtual, you can override it. Figure 5 shows a code snippet that demonstrates how. In the snippet, MyControl is created with the WS_BORDER and WS_POPUP styles, as well as WS_EX_TOOLWINDOW. These symbols aren't defined in Windows.Forms, so you must define them yourself using the values in winuser.h. Make sure you call base.CreateParams to get the default CreateParams before modifying them.
//////////////////
// Code snippet showing how to override Control.CreateParams to 
// set Windows parameters used to create a window. CreateParams 
// is the .NET equivalent of CREATESTRUCT in the Windows API.
//

public class MyControl : Control
{
   // these come from winuser.h
   private const int
      WS_EX_TOOLWINDOW = 0x00000080,
      WS_BORDER  = 0x00800000,
      WS_POPUP   = -2147483648;

   protected override CreateParams CreateParams
   {
      get
      {
         CreateParams cp = base.CreateParams;
         cp.Style = WS_BORDER|WS_POPUP;
         cp.ExStyle = WS_EX_TOOLWINDOW;
         return cp;
      }
   }
   •••
}
Figure 6 shows the various create parameters. Usually you should use CreateParams only for more obscure features such as WS_EX_TOOLWINDOW, since the Control and Form classes provide direct properties for common features. For example, if all you want to do is change the starting size or location, you can use Control.Size and Control.Location without overriding CreateParams. For Forms, Form.DesktopLocation is better (see my April 2003 column). Unlike MFC, the .NET Framework lets you modify these properties before you actually create the control/form. Whatever values you set will be used. Happy programming!

Property Meaning
Caption Gets or sets the control's initial text
ClassName Gets or sets the name of the Windows class to derive the control from
ClassStyle Gets or sets a bitwise combination of class style values
ExStyle Gets or sets a bitwise combination of extended window style values
Height Gets or sets the initial height of the control
Param Gets or sets additional parameter information needed to create the control
Parent Gets or sets the control's parent
Style Gets or sets a bitwise combination of window style values
Width Gets or sets the initial width of the control
X Gets or sets the initial left position of the control
Y Gets or sets the top position of the initial location of the control

Send your questions and comments for Paul to  cppqa@microsoft.com.


Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at http://www.dilascia.com.

Page view tracker