Search
VC++ Document/View Application

This section briefly describes how to add FlowChartX ActiveX control in CView class in the MFC Document/View application. For detailed information how to use FlowChartX under Microsoft VC++ 7 please, refer to VC++ 7 Quick Start (MFC, Dialog application).

Creating new project.

In MSVC environment choose "File -> New -> Project". In the dialog box that appears, select "Visual C++ Projects" from Project Types list and "MFC Application" from Templates list.  Give your project a suitable name and click OK. MFC Application Wizard starts building a new project. Click "Application type" in the dialog that appears and set it to "Single Document". Leave the default values for the rest of the options. Next click "Advanced features" and make sure "ActiveX controls" check box is selected. Choose any other options you might need and press Finish. The wizard generates the new project.

 Note

Instead deriving your own View class from CView MFC Class, you can derive it from CFormView Class. That saves you writing explicit code to notify the control to draw itself in OnDraw. Most of code written below as an example is also applicable for CFormView-derived classes.

Adding the FlowChartX control to the project.

Choose "Project -> Add Class...". In dialog that appears select "Visual C++ -> MFC" from Categories tree and "MFC Class From ActiveX Control" from Templates list and hit Open to open next dialog. From "Available ActiveX Controls" combo box find FlowChartX and add IFlowChart interface to "Generated classes" list. Fill in appropriate names for the class, the .h, .cpp files and click Finish.

Add following code in View header file just before the class declaration:

C++  Copy Code

#include "FlowChart.h"

#define IDC_FLOWCHART (10000)
#define IDC_FCHSCROLL (10001)
#define IDC_FCVSCROLL (10002)
#define SCROLL_WIDTH (16)

Let's assume the control header file is called FlowChart.h. Declare a new variable for FlowChartX control in the private section of the View class. Since the control is windowless and cannot create its own scrollers add manually two scrollbars. Here is how source code should look:

C++  Copy Code

private:
    CFlowChart m_FlowChart;
    CScrollBar m_hScroll;
    CScrollBar m_vScroll;

Go back to View class implementation file and make the necessary adjustments to make the control visible in the View area. First, handle WM_CREATE message in the View class. Most easily you can do that by using Messages pane in Properties bar. The OnCreate method should also create the FlowChartX control and both scrollbars as shown below:

C++  Copy Code

int CMFCViewView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;
 
    // Acquire the client area rectangle of the view. The
    // view rectangle is probably zero-set, but
    // it doesn't matter because we will adjust
    // child's size in the WM_SIZE message handler.
    CRect rcView, rcFlowChart, rcScroll;
    GetClientRect(rcView);

    // Create the FlowChart control here
    m_FlowChart.Create(NULL, WS_VISIBLE, rcView, this, IDC_FLOWCHART);

    // Create both scrollers here
    m_hScroll.Create(SBS_HORZ | WS_CHILD | WS_VISIBLE, rcView, this, IDC_FCHSCROLL);
    m_vScroll.Create(SBS_VERT | WS_CHILD | WS_VISIBLE, rcView, this, IDC_FCVSCROLL);

    // Register the scrollers within the FlowChart control.
    m_FlowChart.RegExtrScrollers((ULONG)m_hScroll.m_hWnd, (ULONG)m_vScroll.m_hWnd);
    m_FlowChart.ShowScrollers();

    return 0;
}

Now a handler for WM_SIZE message should be added. It's triggered when the user resizes application window and we should put code to resize control visible area and both scrollbars. Following code snippet implements that:

C++  Copy Code

void CMFCViewView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);

    m_FlowChart.SetWindowPos(NULL, 0, 0, cx - SCROLL_WIDTH, cy - SCROLL_WIDTH, SWP_NOZORDER);
    m_hScroll.SetWindowPos(NULL, 0, cy - SCROLL_WIDTH, cx - SCROLL_WIDTH, SCROLL_WIDTH, SWP_NOZORDER);
    m_vScroll.SetWindowPos(NULL, cx - SCROLL_WIDTH, 0, SCROLL_WIDTH, cy - SCROLL_WIDTH, SWP_NOZORDER);
}

Using external scrollbars.

To notify the control when the user interacts with external scrollbars, handle  WM_HSCROLL and WM_VSCROLL messages sent to CMFCViewView window. Underneath you can find one possible implementation how to do that:

C++  Copy Code

void CMFCViewView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    if (m_hScroll.m_hWnd != pScrollBar->m_hWnd && m_vScroll.m_hWnd != pScrollBar->m_hWnd)
        return;

    // Pass this info to the FlowChart
    LRESULT nResult;
    LPDISPATCH pDisp = (LPDISPATCH)m_FlowChart.GetControlUnknown();
    IOleInPlaceObjectWindowless* poipow = NULL;

    pDisp->QueryInterface(IID_IOleInPlaceObjectWindowless, (void**)&poipow);
    poipow->OnWindowMessage(WM_HSCROLL, MAKEWPARAM(nSBCode, nPos), (LPARAM)(pScrollBar->m_hWnd), &nResult);
    poipow->Release();

    CView::OnHScroll(nSBCode, nPos, pScrollBar);
}

void CMFCViewView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    if(m_hScroll.m_hWnd != pScrollBar->m_hWnd && m_vScroll.m_hWnd != pScrollBar->m_hWnd)
        return;

    // Pass this info to the FlowChart
    LRESULT nResult;
    LPDISPATCH pDisp = (LPDISPATCH)m_FlowChart.GetControlUnknown();
    IOleInPlaceObjectWindowless* poipow = NULL;

    pDisp->QueryInterface(IID_IOleInPlaceObjectWindowless, (void**)&poipow);
    poipow->OnWindowMessage(WM_VSCROLL, MAKEWPARAM(nSBCode, nPos), (LPARAM)(pScrollBar->m_hWnd), &nResult);
    poipow->Release();

    CView::OnVScroll(nSBCode, nPos, pScrollBar);
}

Drawing notification for the windowless control.

After external scrollbars are connected to FlowChartX control, it is time to add some functionality to OnDraw method. By default, CView doesn't notify  windowless controls to repaint themselves, which results in not properly updated appearance. Following code changes handle that problem:

C++  Copy Code

void CMFCViewView::OnDraw(CDC* pDC)
{
    CMFCViewDoc* pDoc = GetDocument();

    ASSERT_VALID(pDoc);

    // Because the view doesn't causes the windowless control
    // to redraw itself, we need to notify the control on our own.
    // Note, that the following code can be optimized to redraw
    // just the invalid region instead of entire client area.
    RECT rcView;
    IViewObject* pvo;
    LPDISPATCH pDisp = (LPDISPATCH)m_FlowChart.GetControlUnknown();

    GetClientRect(&rcView);

    pDisp->QueryInterface(IID_IViewObject, (void**)&pvo);
    pvo->Draw(DVASPECT_CONTENT, -1, NULL, NULL, pDC->m_hAttribDC,
        pDC->m_hDC, (LPRECTL)&rcView, (LPRECTL)&rcView, NULL, 0);
    pvo->Release();

    // Draw the Zoom area in the lower right corner of the view.
    rcView.left = rcView.right - SCROLL_WIDTH;
    rcView.top = rcView.bottom - SCROLL_WIDTH;
    pDC->DrawFrameControl(&rcView, DFC_MENU, DFCS_MENUARROW);
}

The application performance can be improved further more by disabling background repainting of the View window. It is redundant, because the control is painted on the whole client area. That can be done by handling WM_ERASEBKGND message and returning TRUE instead of calling the base class method as shown below:

C++  Copy Code

BOOL CMFCViewView::OnEraseBkgnd(CDC* pDC)
{
    return TRUE; //CView::OnEraseBkgnd(pDC);
}

As a result FlowChartX paints flicker-free when the user moves or resizes the main window.

Adding more functionality.

Additional features might, for example, include zoom-in and zoom-out. Let's draw an arrow-like button in lower-right corner besides the scrollbars. When clicked 'Zoom' menu pops up. As a first step to do that create the menu in the Resource Editor - something like that:

ID of the menu is IDR_CONTEXTMENU and IDs of the items are respectively - IDM_ZOOM50, IDM_ZOOM100, IDM_ZOOM200. Add a message handler for WM_LBUTTONDOWN message and implement it as follows:

C++  Copy Code

void CMFCViewView::OnLButtonDown(UINT nFlags, CPoint point)
{
    // Check whether the mouse is clicked over our
    // little pop-up arrow in the lower right corner
    // of the screen.
    CRect rcArrow;

    GetClientRect(rcArrow);
    rcArrow.left = rcArrow.right - SCROLL_WIDTH;
    rcArrow.top = rcArrow.bottom - SCROLL_WIDTH;

    if (rcArrow.PtInRect(point))
    {
        // The mouse is clicked over the arrow, so
        // we will show IDR_CONTEXTMENU menu there.
        CMenu menu, *pSubMenu;
        menu.LoadMenu(IDR_CONTEXTMENU);

        // Calculate the screen coordinates for the specified
        // in point parameter client coordinates
        POINT ptScreen = point;
        ClientToScreen(&ptScreen);

        // Get the sub-menu at position 0.
        pSubMenu = menu.GetSubMenu(0);
        pSubMenu->TrackPopupMenu(TPM_LEFTALIGN, ptScreen.x, ptScreen.y, this);
    }

    CView::OnLButtonDown(nFlags, point);
}

Finally add command handles for the three menu items:

C++  Copy Code

// Menu handles
void CMFCViewView::OnZoom50()
{
    m_FlowChart.put_ZoomFactor(50);
}

void CMFCViewView::OnZoom100()
{
    m_FlowChart.put_ZoomFactor(100);
}

void CMFCViewView::OnZoom200()
{
    m_FlowChart.put_ZoomFactor(200);
}

Remember to add following three lines in the message map implementation:

C++  Copy Code

BEGIN_MESSAGE_MAP(CMFCViewView, CView)

    // ...another macros here...
    ON_COMMAND(IDM_ZOOM50, OnZoom50)
    ON_COMMAND(IDM_ZOOM100, OnZoom100)
    ON_COMMAND(IDM_ZOOM200, OnZoom200)

END_MESSAGE_MAP()

Finish by adding following three lines to View header file:

C++  Copy Code

public:
    afx_msg void OnZoom50();
    afx_msg void OnZoom100();
    afx_msg void OnZoom200();