Click here to Skip to main content
15,888,610 members
Articles / Desktop Programming / MFC

How To Place Two Controls in the Same View

Rate me:
Please Sign up or sign in to vote.
4.00/5 (4 votes)
18 Feb 2010CPOL3 min read 29.9K   706   14   4
Composite view in MFC: Two CListCtrl controls in the same view

Introduction

This code shows how to place multiple controls, in particular, CListCtrl controls, into the same view. Or, if you prefer, place two views into one composite view. The story behind this article is that I have been asked to add a feature to an MFC program: show a sum of numbers in columns. The first thing to remember is splitters, but there are already four there, so I do not want to touch that mess. Instead, I want to substitute a view. Since the easiest way to store your code so that you can later find it is to publish it on the Internet...

Step 1: Two List Controls in the Same View

Use the wizard to create a new project; use CView as the view base class. Add two members to the view:

C++
CListCtrl m_lista;
CListCtrl m_listb;  

Modify the view's OnCreate() function:

C++
// TODO: Add your specialized creation code here
// Create the style
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP |
    LVS_REPORT;
    
// Create the list controls.  Don't worry about specifying
// correct coordinates.  That will be handled in OnSize()
BOOL bResultA = m_lista.Create(dwStyle, CRect(0,0,0,0), this, IDC_LIST1);
BOOL bResultB = m_listb.Create(dwStyle/*|LVS_NOCOLUMNHEADER*/, 
		CRect(0,0,0,0), this, IDC_LIST2);

// Make the second control a different color.
COLORREF cr = 0xe0f0f0;
m_listb.SetTextBkColor(cr);
m_listb.SetBkColor(cr);

Add the layout logic to the OnSize() function:

C++
void CMysplit2View::OnSize(UINT nType, int cx, int cy) 
{
	CView::OnSize(nType, cx, cy);
	
	// TODO: Add your message handler code here
    if (::IsWindow(m_lista.m_hWnd))
        m_lista.MoveWindow(0, 0, cx, cy/2, TRUE);
    if (::IsWindow(m_listb.m_hWnd))
        m_listb.MoveWindow(0, cy/2, cx, cy, TRUE);	
}

The identifiers IDC_LIST1 and IDC_LIST2 are added manually to Resource.h, do not forget to update _APS_NEXT_CONTROL_VALUE.

C++
#define IDC_LIST1                       1001
#define IDC_LIST2                       1002
....
#define _APS_NEXT_CONTROL_VALUE		1003

Try it: it works! Probably, this already is what you need and you don't need to read further.

An important question: can we put there a view instead of a control? Yes, we can. (But we will have to allocate the view in the heap separately.)

Step 2: Synchronous Row Resizing

Once more, manually add two constants, the header control IDs IDC_HDR1 and IDC_HDR2, to Resource.h and update _APS_NEXT_CONTROL_VALUE.

C++
#define IDC_HDR1                       1003
#define IDC_HDR2                       1004
....
#define _APS_NEXT_CONTROL_VALUE		1005

Modify CMysplit2View::OnCreate() to make header controls use these header IDs instead of the default 0:

C++
// changing the header control IDs
m_lista.GetHeaderCtrl()->SetDlgCtrlID(IDC_HDR1);
m_listb.GetHeaderCtrl()->SetDlgCtrlID(IDC_HDR2);

Manually add message map entries (outside of the Class Wizard's AFX_MSG_MAP block):

C++
BEGIN_MESSAGE_MAP(CMysplit2View, CView)
	//{{AFX_MSG_MAP(CMysplit2View)
	ON_WM_CREATE()
	ON_WM_SIZE()
	//}}AFX_MSG_MAP
	// Standard printing commands
    .....
    ON_NOTIFY_EX( HDN_ENDTRACK, IDC_HDR1, myOnNotifyHdnEndtrackA)
    ON_NOTIFY_EX( HDN_ENDTRACK, IDC_HDR2, myOnNotifyHdnEndtrackB)
END_MESSAGE_MAP()

Add the notification handling functions:

C++
// mysplit2View.h
// Generated message map functions
protected:
    afx_msg BOOL myOnNotifyHdnEndtrackA
	( UINT id, NMHDR * pNotifyStruct, LRESULT * result );
    afx_msg BOOL myOnNotifyHdnEndtrackB
	( UINT id, NMHDR * pNotifyStruct, LRESULT * result );
    //{{AFX_MSG(CMysplit2View)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
C++
// mysplit2View.cpp

afx_msg BOOL CMysplit2View::myOnNotifyHdnEndtrackA
	( UINT id, NMHDR * pNotifyStruct, LRESULT * result )
{
    NMHEADER* pnmh = (NMHEADER*)pNotifyStruct;
    int n = pnmh->iItem;
    m_listb.SetColumnWidth(n,m_lista.GetColumnWidth(n));
    return 0;
}
afx_msg BOOL CMysplit2View::myOnNotifyHdnEndtrackB
	( UINT id, NMHDR * pNotifyStruct, LRESULT * result )
{
    return 0;
}

Try the code: It resizes the second list control after you resize the first one. I have not done the same functionality for the second header control because I am going to hide it. The first header will be enough. Taking that into account, we could live with 0 as the header control ID...

If It Does Not Work

There are two kinds of HDN_ENDTRACK messages: HDN_ENDTRACKA and HDN_ENDTRACKW. They correspond to different numbers. It is very likely that you are trying to intercept the wrong message. So you may have to replace

C++
ON_NOTIFY_REFLECT(HDN_ENDTRACK, OnEndtrack) 

with:

C++
ON_NOTIFY(HDN_ENDTRACKA, 0, OnEndtrack)
ON_NOTIFY(HDN_ENDTRACKW, 0, OnEndtrack)

in the message map (of course, if you are sure that the same code will do for both versions).

Step 3: Set the 2nd Control Height to be Just Enough for a Single Row

First of all, we remove the header from the second control:

C++
int CMysplit2View::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;
	
    // TODO: Add your specialized creation code here
    // Create the style
    DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP |
        LVS_REPORT;
    
    // Create the list controls.  Don't worry about specifying
    // correct coordinates.  That will be handled in OnSize()
    BOOL bResultA = m_lista.Create(dwStyle, CRect(0,0,0,0), this, IDC_LIST1);
    BOOL bResultB = m_listb.Create
	(dwStyle|LVS_NOCOLUMNHEADER, CRect(0,0,0,0), this, IDC_LIST2);

    // changing the header control IDs
    m_lista.GetHeaderCtrl()->SetDlgCtrlID(IDC_HDR1);
    m_listb.GetHeaderCtrl()->SetDlgCtrlID(IDC_HDR2);

    // Make the second control a different color.
    COLORREF cr = RGB(255,170,85);
    m_listb.SetTextBkColor(cr);
    m_listb.SetBkColor(cr);
    ...
}

			// 
			//

Secondly, we modify the OnSize() function so that the height is equal to that of one row:

C++
void CMysplit2View::OnSize(UINT nType, int cx, int cy) 
{
    CView::OnSize(nType, cx, cy);
	
    // TODO: Add your message handler code here
    // this does not work: allocates space for 1 extra line
    // int height = m_listb.ApproximateViewRect(CSize(-1,10),1).cy;
    RECT r;
    bool b=m_lista.GetItemRect(m_lista.GetTopIndex(),&r,LVIR_BOUNDS);
    ASSERT(b);
    int height=r.bottom-r.top;
    if (::IsWindow(m_lista.m_hWnd))
        m_lista.MoveWindow(0, 0, cx, cy-height, TRUE);
    if (::IsWindow(m_listb.m_hWnd))
        m_listb.MoveWindow(0, cy-height, cx, cy, TRUE);	
}

Step 4: Showing and Hiding the Second Control

Use the Class Wizard to create a button and add a function for it:

C++
//mysplit2View.h
    //{{AFX_MSG(CMysplit2View)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnSwitch2ndview();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
C++
//mysplit2View.cpp
BEGIN_MESSAGE_MAP(CMysplit2View, CView)
    //{{AFX_MSG_MAP(CMysplit2View)
    ...
    ON_COMMAND(ID_SWITCH2NDVIEW, OnSwitch2ndview)
    //}}AFX_MSG_MAP
    ...
END_MESSAGE_MAP()


void CMysplit2View::OnSwitch2ndview() 
{
    ShowSeconList(!IsSecondListVisible());
}

(The logic inside this function will be explained below.)

Now, we need to add a few member variables and methods:

C++
//mysplit2View.h
public:
    bool b_listb_enabled;
    bool IsSecondListVisible() {return b_listb_enabled;}
    void ShowSeconList(bool Enable=true);
protected:
    int m_height;
    int m_cx;
    int m_cy;
    void RepositionControls();
C++
//mysplit2View.cpp
CMysplit2View::CMysplit2View() : b_listb_enabled(false)
{
}
void CMysplit2View::OnSize(UINT nType, int cx, int cy) 
{
	CView::OnSize(nType, cx, cy);
	
	// TODO: Add your message handler code here
    // this does not work: allocates space for 1 extra line
    //int height = m_listb.ApproximateViewRect(CSize(-1,10),1).cy;
    RECT r;
    bool b=m_lista.GetItemRect(m_lista.GetTopIndex(),&r,LVIR_BOUNDS);
    ASSERT(b);
    m_height = r.bottom-r.top;
    m_cx = cx;
    m_cy = cy;
    RepositionControls();
}

void CMysplit2View::RepositionControls() 
{
    int height= IsSecondListVisible() ?  m_height : 0;
    if (::IsWindow(m_lista.m_hWnd))
        m_lista.MoveWindow(0, 0, m_cx, m_cy-height, TRUE);
    if (::IsWindow(m_listb.m_hWnd))
        m_listb.MoveWindow(0, m_cy-height, m_cx, m_cy, TRUE);
}

void CMysplit2View::ShowSeconList(bool Enable)
{
    b_listb_enabled = Enable;
    RepositionControls();
} 

The changes are transparent; I can only note that I refactored the positioning code into a new function, RepositionControls()<code>.

Step 4: Synchronized Scrolling

Use the wizard in the workspace view to add a new class derived from CListCtrl. (In principle, you could add the class manually.) Use Class Wizard to add a handler for WM_HSCROLL:

C++
// CListCtrlEx

CListCtrlEx::CListCtrlEx() {}
CListCtrlEx::~CListCtrlEx() {}

BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
    //{{AFX_MSG_MAP(CListCtrlEx)
    ON_WM_HSCROLL()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CListCtrlEx message handlers

void CListCtrlEx::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
    CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);

    // m_view->m_listb has no scroll bar and therefore cannot call this;
    // so if it code executes this is m_view->m_lista.
    m_view->ListA_OnHScroll(nSBCode,nPos,pScrollBar);
}

Change the class of list controls, and make CListCtrlEx a friend of CMySplit2View (a friend can access private and protected members):

C++
// mysplit2View.h
...
#include "ListCtrlEx.h"
...
class CMysplit2View : public CView
{
    friend class CListCtrlEx;
...
    CListCtrlEx m_lista;
    CListCtrlEx m_listb;

...
    virtual void ListA_OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); 
C++
// mysplit2View.cpp

CMysplit2View::CMysplit2View() :b_listb_enabled(false)
{
    m_lista.m_view=this;
    m_listb.m_view=this;

}

void CMysplit2View::ListA_OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )
{
    int dx = m_lista.GetScrollPos(SB_HORZ) - m_listb.GetScrollPos(SB_HORZ);
    if(dx)
    {
        m_listb.Scroll(dx);
    }
}

Step 5: Pointing to a Row

To be continued...

History

  • v1 Initial

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionnice Pin
DFJY16-Sep-13 17:46
DFJY16-Sep-13 17:46 
GeneralMaking new control responsive to mouse Pin
Ben Aldhouse11-Aug-10 21:33
Ben Aldhouse11-Aug-10 21:33 
GeneralCListCtrl not displaying to screen Pin
BidSki22-Feb-10 20:51
BidSki22-Feb-10 20:51 
Generalgood idea ,pls continue Pin
xiayingang15-Feb-10 8:21
xiayingang15-Feb-10 8:21 
wait for new

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.