Displaying Database Rows in a Scrolling Window

You've seen all the general DAO theory you're going to get here. Now you're ready for a practical example. Before you dig into the code for EX32A, however, you need to study the general problem of displaying database rows in a scrolling window. If this were an easy problem to solve, there would probably be an MFC CScrollDatabaseView class. But there isn't, so we'll write our own class. Actually, it's not that difficult if we make some simplifying assumptions about the database. First, our scrolling row-view class will be based on a dynaset, and that means that it can accommodate any table, including those in ODBC data sources and ISAM-type files. Second, we'll specify read-only access, which means that the number of rows in the dynaset can't change.

Scrolling Alternatives

There are lots of ways to implement scrolling with Visual C++. If you look at the DAOVIEW MFC sample database program on the Visual C++ CD-ROM, you'll see the use of the MFC CListView class, which encapsulates the Windows list view common control. The trouble with this approach is that you must copy all the selected rows into the control, which can be slow, and more significantly, you can't see updates that other programs are making in the same table. The list view is a de facto snapshot of a database table.

We'll base our scrolling view on the MFC CScrollView class, and our code will be smart enough to retrieve only those records that are needed for the client area of the window. The only limitation here is the logical size of the scrolling window. In Microsoft Windows 95, the limits are ±32,767, and that restricts the number of rows we can display. If the distance between rows is 14 units, we can display only up to 2340 rows.

A Row-View Class

If you've read other books about programming for Windows, you know that authors spend lots of time on the problem of scrolling lists. This is a tricky programming exercise that must be repeated over and over. Why not encapsulate a scrolling list in a base class? All the ugly details would be hidden, and you could get on with the business of writing your application.

The CRowView class, adapted from the class of the identical name in the CHKBOOK MFC advanced sample program on the Visual C++ CD-ROM, does the job. Through its use of virtual callback functions, it serves as a model for other derivable base classes. CRowView has some limitations, and it's not built to industrial-strength specifications, but it works well in the DAO example. Figure 32-1 shows the header file listing.

ROWVIEW.H

// rowview.h : interface of the CRowView class
//
// This class implements the behavior of a scrolling view that presents
//  multiple rows of fixed-height data. A row view is similar to an
//  owner-draw list box in its visual behavior; but unlike list boxes,
//  a row view has all of the benefits of a view (as well as scroll view),
//  including perhaps most importantly printing and print preview.
/////////////////////////////////////////////////////////////////////////

class CRowView : public CScrollView 
{
DECLARE_DYNAMIC(CRowView)
protected:
// Construction/destruction
    CRowView();
    virtual ~CRowView();

// Attributes
protected:
    int m_nRowWidth;            // width of row in logical units
    int m_nRowHeight;           // height of row in logical units
    int m_nCharWidth;           // avg char width in logical units
    int m_nPrevSelectedRow;     // index of the most recently selected row
    int m_nPrevRowCount;        // most recent row count, before update
    int m_nRowsPerPrintedPage;  // how many rows fit on a printed page

// Operations-Attributes
protected:
    virtual void UpdateRow(int nInvalidRow); // called by derived class 
                                             //  OnUpdate
    virtual void CalculateRowMetrics(CDC* pDC)
        { GetRowWidthHeight(pDC, m_nRowWidth, m_nRowHeight,
            m_nCharWidth); }
    virtual void UpdateScrollSizes();
    virtual CRect RowToWndRect(CDC* pDC, int nRow);

virtual int RowToYPos(int nRow);
    virtual void RectLPtoRowRange(const CRect& rectLP, int& nFirstRow,
        int& nLastRow, BOOL bIncludePartiallyShownRows);
    virtual int LastViewableRow();

// Overridables
protected:
    virtual void GetRowWidthHeight(CDC* pDC, int& nRowWidth, 
        int& nRowHeight, int& nCharWidth) = 0;
    virtual int GetActiveRow() = 0;
    virtual int GetRowCount() = 0;
    virtual void OnDrawRow(CDC* pDC, int nRow, int y, BOOL bSelected) = 0;
    virtual void ChangeSelectionNextRow(BOOL bNext) = 0;
    virtual void ChangeSelectionToRow(int nRow) = 0;

// Implementation
protected:
    // standard overrides of MFC classes
    virtual void OnInitialUpdate();
    virtual void OnDraw(CDC* pDC);  // overridden to draw this view
    virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL);
    virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
    virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
    virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo);

// Generated message map functions
protected:
    //{{AFX_MSG(CRowView)
    afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

Figure 32-1. The CRowView header file listing.

Dividing the Work Between Base and Derived Classes

Because the CRowView class (itself derived from CScrollView) is designed to be a base class, it is as general as possible. CRowView relies on its derived class to access and paint the row's data. The EX32A example's document class obtains its row data from a scrollable DAO database, but the CHKBOOK example uses a random access disk file. The CRowView class serves both examples effectively. It supports the concept of a selected row that is highlighted in the view. Through the CRowView virtual member functions, the derived class is alerted when the user changes the selected row.

The CRowView Pure Virtual Member Functions

Classes derived from CRowView must implement the following pure virtual member functions:

Other CRowView Functions

Three other CRowView functions are available to be called by derived classes and the application framework:

The MFC Dialog Bar

You haven't seen the CDialogBar class yet because it hasn't made sense to use it. (A dialog bar is a child of the frame window that is arranged according to a dialog template resource and that routes commands in a manner similar to that of a toolbar.) It fits well in the DAO example, however. (See Figure 32-2.) The dialog bar contains an edit control for the SQL query string, and it has a pushbutton to re-execute the query. The button sends a command message that can be handled in the view, and it can be disabled by an update command UI handler. Most dialog bars reside at the top of the frame window, immediately under the toolbar. It's surprisingly easy to add a dialog bar to an application. You don't even need a new derived class. Here are the steps:

  1. Use the resource editor to lay out the dialog bar. Apply the following styles:

    Style = Child

    Border = None

    Visible = Unchecked

    You can choose a horizontally oriented bar for the top or bottom of the frame, or you can choose a vertically oriented bar for the left or right side of the frame. Add any controls you need, including buttons and edit controls.

  2. Declare an embedded CDialogBar object in your derived main frame class declaration, as shown here:

    CDialogBar m_wndMyBar;
    

  3. Add dialog bar object creation code to your main frame class OnCreate member function, as shown here:

    if (!m_wndMyBar.Create(this, IDD_MY_BAR, CBRS_TOP, 
        ID_MY_BAR)) {
        TRACE("Failed to create dialog bar\n");
        return -1;
    }
    

IDD_MY_BAR is the dialog resource ID assigned in the resource editor. The CBRS_TOP style tells the application framework to place the dialog bar at the top of the frame window. ID_MY_BAR is the dialog bar's control window ID, which should be within the range 0xE800 through 0xE820 to ensure that the Print Preview window preempts the dialog bar.