575 lines
14 KiB
C++
575 lines
14 KiB
C++
// Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved
|
|
#include "precomp.h"
|
|
#include "resource.h"
|
|
#include "globals.h"
|
|
|
|
#include "gridhdr.h"
|
|
#include "grid.h"
|
|
#include "utils.h"
|
|
|
|
|
|
|
|
#define MAX_TITLE 1024 // The maximum property name is limited to 1024 bytes.
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CHdrWnd
|
|
//
|
|
// The only purpose of this class is to contain a CGridHeader window so that
|
|
// the CGridHeader can easily be scrolled left in right in a manner such
|
|
// that the portions of the CGridHeader that would otherwise extend to the
|
|
// left of the CHdrWnd's client rectangle are clipped.
|
|
//
|
|
// !!!CR: Is there a better way to do this? It seems like a waste of a
|
|
// !!!CR: window. If there were some way to set the window origin withou
|
|
// !!!CR: having to reference the paint time DC, then this class would not
|
|
// !!!CR: be necessary.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
CHdrWnd::CHdrWnd(CGrid* pGrid)
|
|
{
|
|
m_pGrid = pGrid;
|
|
m_ixScrollOffset = 0;
|
|
m_iColScrollOffset = 0;
|
|
m_bUIActive = FALSE;
|
|
}
|
|
|
|
CHdrWnd::~CHdrWnd()
|
|
{
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CHdrWnd, CWnd)
|
|
//{{AFX_MSG_MAP(CHdrWnd)
|
|
ON_WM_SIZE()
|
|
ON_WM_SETFOCUS()
|
|
ON_WM_KILLFOCUS()
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CHdrWnd message handlers
|
|
|
|
//*************************************************************************
|
|
// CHdrWnd::OnSize
|
|
//
|
|
// Catch the WM_ONSIZE message. This is necessary for resizing the CGridHeader
|
|
// control that is contained within this window. The CGridHeader is sized
|
|
// so that its right edge is flush against the right edge of the client rectangle
|
|
// of this window. The left edge of the CGridHeader may extend to the left
|
|
// of the client edge of this window and is thus clipped. This gives the
|
|
// appearance of scrolling the grid header without the necessity of changing the
|
|
// implementation of the header control.
|
|
//
|
|
// Parameters:
|
|
// See the MFC documentation.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
//**************************************************************************
|
|
void CHdrWnd::OnSize(UINT nType, int cx, int cy)
|
|
{
|
|
CWnd::OnSize(nType, cx, cy);
|
|
|
|
if (m_hdr.m_hWnd) {
|
|
CRect rc;
|
|
GetClientRect(rc);
|
|
rc.left = -m_ixScrollOffset;
|
|
m_hdr.MoveWindow(rc);
|
|
m_hdr.FixColWidths();
|
|
}
|
|
}
|
|
|
|
|
|
void CHdrWnd::OnGridSizeChanged()
|
|
{
|
|
m_hdr.FixColWidths();
|
|
|
|
}
|
|
|
|
|
|
|
|
//***********************************************************************
|
|
// CHdrWnd::Create
|
|
//
|
|
// Create the header window. Essentially, this just creates a minimal
|
|
// window with the CGridHeader window inside of it.
|
|
//
|
|
// Parameters:
|
|
// See the MFC documentation.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
//***********************************************************************
|
|
BOOL CHdrWnd::Create(DWORD dwChildStyle, CRect& rc, CWnd* pwndParent, UINT nId)
|
|
{
|
|
|
|
DWORD dwStyle = WS_CHILD;
|
|
if (dwChildStyle & WS_VISIBLE) {
|
|
dwStyle |= WS_VISIBLE;
|
|
}
|
|
|
|
BOOL bDidCreateWnd = CWnd::Create(NULL, _T("CHdrWnd"), dwStyle, rc, pwndParent, nId);
|
|
if (!bDidCreateWnd) {
|
|
return FALSE;
|
|
}
|
|
|
|
bDidCreateWnd = m_hdr.Create(dwChildStyle | WS_VISIBLE, rc, this, GenerateWindowID());
|
|
return bDidCreateWnd;
|
|
}
|
|
|
|
//***********************************************************************
|
|
// CHdrWnd::SetScrollOffset
|
|
//
|
|
// Move the CGridHeader within this window so that its right edge is flush
|
|
// against the right edge of this window's client rectangle and the left
|
|
// edge of the CGridHeader is offset by the specified amount from the left
|
|
// edge of the client rectangle of this window.
|
|
//
|
|
// Parameters:
|
|
// [in] int iCol
|
|
//
|
|
// [in] int ixScrollOffset
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
//***********************************************************************
|
|
void CHdrWnd::SetScrollOffset(int iCol, int ixScrollOffset)
|
|
{
|
|
m_iColScrollOffset = iCol;
|
|
m_hdr.m_iColScrollOffset = iCol;
|
|
m_ixScrollOffset = ixScrollOffset;
|
|
|
|
|
|
CRect rc;
|
|
GetClientRect(rc);
|
|
rc.left = -ixScrollOffset;
|
|
m_hdr.MoveWindow(rc);
|
|
|
|
if (iCol != NULL_INDEX) {
|
|
int cxColHdr = m_hdr.ColWidthFromHeader(iCol);
|
|
int cxColGrid = m_pGrid->ColWidth(iCol);
|
|
m_hdr.FixColWidths();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CGrid message handlers
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CGridHdr
|
|
|
|
CGridHdr::CGridHdr()
|
|
{
|
|
m_pGrid = NULL;
|
|
m_bIsSettingColumnWidth = FALSE;
|
|
m_iSelectedColumn = 0;
|
|
m_iColScrollOffset = 0;
|
|
}
|
|
|
|
CGridHdr::~CGridHdr()
|
|
{
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CGridHdr, CHeaderCtrl)
|
|
//{{AFX_MSG_MAP(CGridHdr)
|
|
ON_WM_SETFOCUS()
|
|
ON_WM_KILLFOCUS()
|
|
//}}AFX_MSG_MAP
|
|
|
|
ON_NOTIFY_REFLECT(HDN_ITEMCHANGED, OnItemChanged)
|
|
ON_NOTIFY_REFLECT(HDN_ITEMCHANGING, OnItemChanging)
|
|
ON_NOTIFY_REFLECT(HDN_ITEMCLICK, OnItemClick)
|
|
ON_NOTIFY_REFLECT(HDN_DIVIDERDBLCLICK, OnDividerDblClick)
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CGridHdr message handlers
|
|
//***********************************************************
|
|
// CGridHdr::OnItemChanged
|
|
//
|
|
// Catch a change to a header control item so that we can resize
|
|
// the grid when the header divider lines are moved.
|
|
//
|
|
// Parameters:
|
|
// See the MFC documentation for header control notification
|
|
// messages.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
//************************************************************
|
|
|
|
void CGridHdr::OnItemChanged(NMHDR *pNMHDR, LRESULT* pResult)
|
|
{
|
|
if (m_bIsSettingColumnWidth) {
|
|
return;
|
|
}
|
|
|
|
if (m_pGrid) {
|
|
HD_NOTIFY FAR* pHdNotify = (HD_NOTIFY FAR *) (void*) pNMHDR;
|
|
|
|
|
|
HD_ITEM hdItem;
|
|
int iItem = pHdNotify->iItem;
|
|
|
|
// Get the width of the header control item and set width
|
|
// of the corresponding column in the grid to match the
|
|
// width of the header control item.
|
|
hdItem.mask = HDI_WIDTH;
|
|
BOOL bGotItem = GetItem(iItem, &hdItem );
|
|
if (bGotItem) {
|
|
// The grid does horizontal scrolling by entire columns. This
|
|
// means that if a column is greater than the width of the header's
|
|
// client rectangle, then the user can never see the right side of
|
|
// the column. This also prevents the user from ever shrinking the
|
|
// column width if it is greater than the client width.
|
|
CRect rcGridClient;
|
|
m_pGrid->GetClientRect(rcGridClient);
|
|
int cxMax = rcGridClient.Width() - m_pGrid->GetRowHandleWidth();
|
|
|
|
if (hdItem.cxy > cxMax) {
|
|
hdItem.mask = HDI_WIDTH;
|
|
hdItem.cxy = cxMax;
|
|
|
|
m_bIsSettingColumnWidth = TRUE;
|
|
BOOL bDidSetItem = SetItem(iItem, &hdItem);
|
|
m_bIsSettingColumnWidth = FALSE;
|
|
}
|
|
|
|
m_pGrid->SetColumnWidth(iItem, hdItem.cxy, TRUE);
|
|
FixColWidths();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGridHdr::OnItemChanging(NMHDR *pNMHDR, LRESULT* pResult)
|
|
{
|
|
*pResult = FALSE;
|
|
|
|
if (m_hWnd == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (m_pGrid) {
|
|
HD_NOTIFY FAR* pHdNotify = (HD_NOTIFY FAR *) (void*) pNMHDR;
|
|
int iItem = pHdNotify->iItem;
|
|
HD_ITEM item;
|
|
item.mask = HDI_LPARAM ;
|
|
GetItem(iItem, &item);
|
|
if (LOWORD(item.lParam) == FALSE){
|
|
*pResult = TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void CGridHdr::SetColumnWidth(int iCol, int cx, BOOL bRedraw)
|
|
{
|
|
if (m_hWnd == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get visibility
|
|
HD_ITEM item;
|
|
item.mask = HDI_LPARAM ;
|
|
GetItem(iCol, &item);
|
|
int nVisible = LOWORD(item.lParam);
|
|
|
|
HD_ITEM hdItem;
|
|
|
|
// Get the width of the header control item and set width
|
|
// of the corresponding column in the grid to match the
|
|
// width of the header control item.
|
|
hdItem.mask = HDI_WIDTH | HDI_LPARAM;
|
|
hdItem.cxy = cx;
|
|
hdItem.lParam = MAKELPARAM(nVisible,cx);
|
|
|
|
m_bIsSettingColumnWidth = TRUE;
|
|
SetItem(iCol, &hdItem );
|
|
m_bIsSettingColumnWidth = FALSE;
|
|
|
|
if (bRedraw) {
|
|
RedrawWindow();
|
|
}
|
|
}
|
|
|
|
int CGridHdr::ColWidthFromHeader(int iCol)
|
|
{
|
|
HD_ITEM item;
|
|
item.mask = HDI_LPARAM ;
|
|
GetItem(iCol, &item);
|
|
return HIWORD(item.lParam);
|
|
}
|
|
|
|
//***********************************************************
|
|
// CGridHdr::OnItemClick
|
|
//
|
|
// Catch a "item clicked" notification message from the header
|
|
// control.
|
|
//
|
|
// Parameters:
|
|
// See the MFC documentation for header control notification
|
|
// messages.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
//************************************************************
|
|
void CGridHdr::OnItemClick(NMHDR *pNMHDR, LRESULT* pResult)
|
|
{
|
|
if (m_pGrid) {
|
|
HD_NOTIFY FAR* pHdNotify = (HD_NOTIFY FAR *) (void*) pNMHDR;
|
|
if (pHdNotify->iItem == m_iSelectedColumn) {
|
|
// Flip the sort order if currently selected column header was clicked.
|
|
BOOL bAscending = m_pGrid->ColumnIsAscending(m_iSelectedColumn);
|
|
m_pGrid->SetSortDirection(m_iSelectedColumn, !bAscending);
|
|
}
|
|
m_pGrid->OnHeaderItemClick(pHdNotify->iItem);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//***********************************************************
|
|
// CGridHdr::OnDividerDblClick
|
|
//
|
|
// Catch a divider double-clicked notification message from the header
|
|
// control.
|
|
//
|
|
// Parameters:
|
|
// See the MFC documentation for header control notification
|
|
// messages.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
//************************************************************
|
|
void CGridHdr::OnDividerDblClick(NMHDR *pNMHDR, LRESULT* pResult)
|
|
{
|
|
if (m_pGrid) {
|
|
HD_NOTIFY FAR* pHdNotify = (HD_NOTIFY FAR *) (void*) pNMHDR;
|
|
|
|
int iCol = pHdNotify->iItem;
|
|
int cxMax = m_pGrid->GetMaxValueWidth(iCol);
|
|
|
|
// Limit the width of the header to one pixel less than the width of the grid
|
|
// so that the header divider line will always be visible.
|
|
//
|
|
// This may result in the width of the column in the grid being wider than
|
|
// the width of the header item for the corresponding column. This
|
|
// discrepency is corrected if the window is resized so that it grows.
|
|
CRect rcClientGrid;
|
|
m_pGrid->GetClientRect(rcClientGrid);
|
|
int cxGrid = rcClientGrid.Width();
|
|
int cxColHdr = cxMax;
|
|
if (cxColHdr> cxGrid - 1) {
|
|
cxColHdr = cxGrid - 1;
|
|
}
|
|
|
|
|
|
#if 0
|
|
|
|
HD_ITEM hdItem;
|
|
|
|
hdItem.mask = HDI_WIDTH;
|
|
hdItem.cxy = cxColHdr;
|
|
m_bIsSettingColumnWidth = TRUE;
|
|
SetItem(iCol, &hdItem );
|
|
#endif //0
|
|
|
|
|
|
|
|
m_bIsSettingColumnWidth = FALSE;
|
|
m_pGrid->SetColumnWidth(iCol, cxMax, TRUE);
|
|
FixColWidths();
|
|
|
|
// RedrawWindow();
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void CGridHdr::OnSetFocus(CWnd* pOldWnd)
|
|
{
|
|
CHeaderCtrl::OnSetFocus(pOldWnd);
|
|
|
|
// TODO: Add your message handler code here
|
|
if (!m_bUIActive)
|
|
{
|
|
m_bUIActive = TRUE;
|
|
m_pGrid->OnRequestUIActive();
|
|
}
|
|
|
|
}
|
|
|
|
void CGridHdr::OnKillFocus(CWnd* pNewWnd)
|
|
{
|
|
CHeaderCtrl::OnKillFocus(pNewWnd);
|
|
|
|
// TODO: Add your message handler code here
|
|
m_bUIActive = FALSE;
|
|
}
|
|
|
|
void CHdrWnd::OnSetFocus(CWnd* pOldWnd)
|
|
{
|
|
CWnd::OnSetFocus(pOldWnd);
|
|
|
|
// TODO: Add your message handler code here
|
|
if (!m_bUIActive)
|
|
{
|
|
m_bUIActive = TRUE;
|
|
m_pGrid->OnRequestUIActive();
|
|
}
|
|
|
|
}
|
|
|
|
void CHdrWnd::OnKillFocus(CWnd* pNewWnd)
|
|
{
|
|
CWnd::OnKillFocus(pNewWnd);
|
|
|
|
// TODO: Add your message handler code here
|
|
m_bUIActive = FALSE;
|
|
}
|
|
|
|
void CGridHdr::SetColVisibility(int iCol, BOOL bVisible)
|
|
{
|
|
if (m_hWnd == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (GetItemCount() <= iCol){
|
|
return;
|
|
}
|
|
|
|
TCHAR szTitle[MAX_TITLE];
|
|
HD_ITEM item;
|
|
item.mask = HDI_LPARAM | HDI_BITMAP | HDI_FORMAT | HDI_TEXT | HDI_WIDTH;
|
|
item.pszText = szTitle;
|
|
item.cchTextMax = sizeof(szTitle) - 1;
|
|
GetItem(iCol, &item);
|
|
|
|
|
|
item.hbm = NULL;
|
|
LPARAM lParam = MAKELPARAM(bVisible,HIWORD(item.lParam));
|
|
item.lParam = lParam;
|
|
|
|
if (!bVisible){
|
|
m_pGrid->SetColumnWidth(iCol, 0, FALSE);
|
|
item.cxy = 0;
|
|
DeleteItem(iCol);
|
|
InsertItem(iCol, &item);
|
|
|
|
}
|
|
else {
|
|
int nWidth = HIWORD(item.lParam);
|
|
m_pGrid->SetColumnWidth(iCol, nWidth , FALSE);
|
|
item.cxy = nWidth;
|
|
DeleteItem(iCol);
|
|
InsertItem(iCol, &item);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//********************************************************
|
|
// CGridHdr::FindLastVisibleCol
|
|
//
|
|
// Find the column index of the last column that is at
|
|
// least partially visible.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// The column index of the last visible column.
|
|
//
|
|
//********************************************************
|
|
int CGridHdr::FindLastVisibleCol()
|
|
{
|
|
int nCols = m_pGrid->GetCols();
|
|
CRect rcGrid;
|
|
m_pGrid->GetClientRect(rcGrid);
|
|
int cxGrid = rcGrid.Width();
|
|
int cxMaxHdr = cxGrid - m_pGrid->GetRowHandleWidth();
|
|
|
|
for (int iCol = m_iColScrollOffset; iCol < nCols; ++iCol) {
|
|
int cxColGrid = m_pGrid->ColWidth(iCol);
|
|
cxMaxHdr -= cxColGrid;
|
|
if (cxMaxHdr <= 0) {
|
|
return iCol;
|
|
}
|
|
}
|
|
if (nCols > 0) {
|
|
return nCols - 1;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//********************************************************
|
|
// CGridHdr::FixColWidths
|
|
//
|
|
// Fix the column widths of the header items to keep them
|
|
// in sync with the column widths in the grid. This is
|
|
// necessary because the column width of a header item
|
|
// may be less than the column width of the corresponding
|
|
// item so that the user can grab the divider line on
|
|
// the last visible column and resize the column.
|
|
//
|
|
// Parameters:
|
|
// None.
|
|
//
|
|
// Returns:
|
|
// Nothing.
|
|
//
|
|
//********************************************************
|
|
void CGridHdr::FixColWidths()
|
|
{
|
|
CRect rcGrid;
|
|
m_pGrid->GetClientRect(rcGrid);
|
|
int cxGrid = rcGrid.Width();
|
|
int cxMaxHdr = cxGrid - m_pGrid->GetRowHandleWidth();
|
|
int iLastVisibleCol = FindLastVisibleCol();
|
|
|
|
|
|
BOOL bChangedColWidth = FALSE;
|
|
int nCols = m_pGrid->GetCols();
|
|
for (int iCol = 0; iCol < nCols; ++iCol) {
|
|
int cxColGrid = m_pGrid->ColWidth(iCol);
|
|
int cx = cxColGrid;
|
|
if (iCol == iLastVisibleCol) {
|
|
if (cx > cxMaxHdr) {
|
|
cx = cxMaxHdr;
|
|
}
|
|
}
|
|
|
|
int cxColHdr = ColWidthFromHeader(iCol);
|
|
if (cx != cxColHdr) {
|
|
SetColumnWidth(iCol, cx, FALSE);
|
|
bChangedColWidth = TRUE;
|
|
}
|
|
}
|
|
if (bChangedColWidth) {
|
|
RedrawWindow();
|
|
}
|
|
}
|