2025-04-27 07:49:33 -04:00

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();
}
}