MultilineList.cpp
上传用户:tony_wxd
上传日期:2022-06-16
资源大小:40k
文件大小:35k
源码类别:

ListView/ListBox

开发平台:

Visual C++

  1. /*
  2.  * CMultilineList custom control
  3.  * Copyright (C) 2006 Dave Calkins (coder1024@gmail.com)
  4.  *
  5.  * This library is free software; you can redistribute it and/or modify it under
  6.  * the terms of the GNU Lesser General Public License as published by the Free
  7.  * Software Foundation; either version 2.1 of the License, or (at your option)
  8.  * any later version.
  9.  *
  10.  * This library is distributed in the hope that it will be useful, but WITHOUT
  11.  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12.  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  13.  * details.
  14.  *
  15.  * You should have received a copy of the GNU Lesser General Public License
  16.  * along with this library; if not, write to the
  17.  *
  18.  *   Free Software Foundation, Inc.
  19.  *   59 Temple Place, Suite 330
  20.  *   Boston, MA 02111-1307 USA 
  21.  *
  22.  */
  23. #include "stdafx.h"
  24. #include "MultilineList.h"
  25. using namespace std;
  26. // Win32 class name used for the window; if you're using the VS dialog editor you need
  27. // to put this string in the Class property of the custom control
  28. #define MULTILINELIST_CLASSNAME _T("CMultilineList")
  29. // width of the internal borders used between cells/cols/rows
  30. #define GRID_WIDTH              1
  31. // spacing inside cells between the text and the cell borders
  32. #define INNER_PADDING           2
  33. // control IDs for child controls
  34. #define CHILD_ID_HEADERCTRL     101
  35. #define CHILD_ID_SCROLLBAR      102
  36. #define CHILD_ID_SCROLLBARHORZ  103
  37. // header control height is set to be the font height plus the below
  38. #define HEADERCTRL_HEIGHT_EXTRA 6
  39. // # pixels to scroll horizontally when using arrows
  40. #define HORZSCROLL_PIXELS       25
  41. IMPLEMENT_DYNAMIC(CMultilineList, CWnd)
  42. BEGIN_MESSAGE_MAP(CMultilineList, CWnd)
  43.    ON_WM_ERASEBKGND()
  44.    ON_WM_PAINT()
  45.    ON_WM_SIZE()
  46.    ON_WM_VSCROLL()
  47.    ON_WM_MOUSEWHEEL()
  48.    ON_WM_HSCROLL()
  49.    ON_WM_LBUTTONDOWN()
  50. END_MESSAGE_MAP()
  51. ////////////////////////////////////////////////////////////////////////////////
  52. //                                  PUBLIC
  53. ////////////////////////////////////////////////////////////////////////////////
  54. CMultilineList::CMultilineList()
  55. : m_nCols(0),
  56.   m_nRows(0),
  57.   m_viewYPos(0),
  58.   m_viewXPos(0),
  59.   m_curSelRow(-1)
  60. {
  61.    RegisterWindowClass();
  62. }
  63. CMultilineList::~CMultilineList()
  64. {
  65. }
  66. BOOL CMultilineList::Create(CWnd* pParentWnd, const RECT& rect, UINT nID,
  67.                             DWORD dwStyle /*=WS_VISIBLE*/)
  68. {
  69. return CWnd::Create(MULTILINELIST_CLASSNAME, _T(""), dwStyle, rect,
  70.       pParentWnd, nID);
  71. }
  72. void CMultilineList::SetSize(int nCols, int nRows)
  73. {
  74.    ASSERT(nCols >= 0);
  75.    ASSERT(nRows >= 0);
  76.    // if no change, do nothing
  77.    if ((nCols == m_nCols) &&
  78.        (nRows == m_nRows))
  79.    {
  80.       return;
  81.    }
  82.    // if the # cols or # rows is being reduced
  83.    if ((nCols < m_nCols) ||
  84.        (nRows < m_nRows))
  85.    {
  86.       // walk through all cells
  87.       map<pair<int,int>,Cell>::iterator i;
  88.       for (i = m_cells.begin(); i != m_cells.end(); )
  89.       {
  90.          int col = i->first.first;
  91.          int row = i->first.second;
  92.          // remove any cells outside the new dimensions
  93.          if ((col >= nCols) ||
  94.              (row >= nRows))
  95.          {
  96.             i = m_cells.erase(i);
  97.          }
  98.          else
  99.          {
  100.             ++i;
  101.          }
  102.       }
  103.    }
  104.    // if the # cols is being reduced
  105.    if (nCols < m_nCols)
  106.    {
  107.       // walk through all columns
  108.       map<int,Column>::iterator j;
  109.       for (j = m_columns.begin(); j != m_columns.end(); )
  110.       {
  111.          int col = j->first;
  112.          // remove any columns outside the new dimensions
  113.          if (col >= nCols)
  114.          {
  115.             j = m_columns.erase(j);
  116.          }
  117.          else
  118.          {
  119.             ++j;
  120.          }
  121.       }
  122.       // invalidate all row heights
  123.       m_rowHeights.clear();
  124.    }
  125.    // if the # rows is being reduced but # cols is not being reduced
  126.    // (since if the # cols was reduced all row heights would have been
  127.    // invalidated anyway)
  128.    if ((nRows < m_nRows) &&
  129.        (nCols >= m_nCols))
  130.    {
  131.       // walk through the calculated row heights
  132.       map<int,int>::iterator k;
  133.       for (k = m_rowHeights.begin(); k != m_rowHeights.end(); )
  134.       {
  135.          int row = k->first;
  136.          // remove (invalidate) calculated row heights corresponding to rows which are outside the new dimensions
  137.          if (row >= nRows)
  138.          {
  139.             k = m_rowHeights.erase(k);
  140.          }
  141.          else
  142.          {
  143.             ++k;
  144.          }
  145.       }
  146.    }
  147.    // store new size
  148.    m_nCols = nCols;
  149.    m_nRows = nRows;
  150.    // update
  151.    Invalidate(FALSE);
  152.    CalculateRowHeights();
  153.    UpdateChildControls();
  154. }
  155. void CMultilineList::GetSize(int & nCols, int & nRows)
  156. {
  157.    // return current size of the list
  158.    nCols = m_nCols;
  159.    nRows = m_nRows;
  160. }
  161. void CMultilineList::SetColHeading(int col, LPCTSTR heading)
  162. {
  163.    ASSERT(col >= 0);
  164.    ASSERT(col < m_nCols);
  165.    ASSERT(heading != NULL);
  166.    // use existing column object (if there is one) or a new one
  167.    Column column;
  168.    map<int,Column>::iterator i = m_columns.find(col);
  169.    if (i != m_columns.end())
  170.    {
  171.       column = i->second;
  172.       // abort if no change
  173.       if (column.m_heading == CString(heading))
  174.          return;
  175.    }
  176.    // set column heading
  177.    column.m_heading = heading;
  178.    // store the column object back in the map
  179.    m_columns[col] = column;
  180.    // update
  181.    Invalidate(FALSE);
  182.    UpdateChildControls();
  183. }
  184. void CMultilineList::SetColWidth(int col, int width)
  185. {
  186.    ASSERT(col >= 0);
  187.    ASSERT(col < m_nCols);
  188.    ASSERT(width >= 0);
  189.    // use existing column object (if there is one) or use default
  190.    Column column;
  191.    map<int,Column>::iterator i = m_columns.find(col);
  192.    if (i != m_columns.end())
  193.    {
  194.       column = i->second;
  195.       // abort if no change
  196.       if (column.m_width == width)
  197.          return;
  198.    }
  199.    // set column width
  200.    column.m_width = width;
  201.    // store the column object back in the map
  202.    m_columns[col] = column;
  203.    // invalidate all row heights
  204.    m_rowHeights.clear();
  205.    // update
  206.    Invalidate(FALSE);
  207.    CalculateRowHeights();
  208.    UpdateChildControls();
  209. }
  210. void CMultilineList::SetCellText(int col, int row, LPCTSTR text)
  211. {
  212.    ASSERT(col >= 0);
  213.    ASSERT(row >= 0);
  214.    ASSERT(col < m_nCols);
  215.    ASSERT(row < m_nRows);
  216.    ASSERT(text != NULL);
  217.    // use existing cell object (if there is one) or a new one
  218.    Cell cell;
  219.    pair<int,int> coord = make_pair(col,row);
  220.    map<pair<int,int>,Cell>::iterator i = m_cells.find(coord);
  221.    if (i != m_cells.end())
  222.    {
  223.       cell = i->second;
  224.       // abort if no change
  225.       if (cell.m_text == CString(text))
  226.          return;
  227.    }
  228.    // set cell text
  229.    cell.m_text = text;
  230.    // store the cell object back in the map
  231.    m_cells[coord] = cell;
  232.    // invalidate this row's height
  233.    m_rowHeights.erase(row);
  234.    // update
  235.    Invalidate(FALSE);
  236.    CalculateRowHeights();
  237.    UpdateChildControls();
  238. }
  239. CString CMultilineList::GetCellText(int col, int row)
  240. {
  241.    ASSERT(col >= 0);
  242.    ASSERT(row >= 0);
  243.    ASSERT(col < m_nCols);
  244.    ASSERT(row < m_nRows);
  245.    CString result;
  246.    // use existing cell object (if there is one) or use default
  247.    Cell cell;
  248.    map<pair<int,int>,Cell>::iterator i = m_cells.find(make_pair(col,row));
  249.    if (i != m_cells.end())
  250.    {
  251.       cell = i->second;
  252.    }
  253.    // return the cell text
  254.    result = cell.m_text;
  255.    return result;
  256. }
  257. void CMultilineList::SetSelRow(int row)
  258. {
  259.    ASSERT(row >= 0);
  260.    ASSERT(row < m_nRows);
  261.    // force selected row to the specified value
  262.    m_curSelRow = row;
  263.    // update
  264.    Invalidate(FALSE);
  265.    UpdateChildControls();
  266. }
  267. int CMultilineList::GetSelRow()
  268. {
  269.    // return currently selected row
  270.    return m_curSelRow;
  271. }
  272. int CMultilineList::GetRowFromPoint(CPoint pt)
  273. {
  274.    int result = -1;
  275.    CalculateRowHeights();
  276.    // ensure point is inside content area
  277.    CRect cr;
  278.    GetContentRect(cr);
  279.    if (cr.PtInRect(pt))
  280.    {
  281.       // convert to list coords
  282.       int listYPos = m_viewYPos + (pt.y - cr.top);
  283.       // walk through the rows until we find one at the specified vertical location
  284.       int row = 0;
  285.       for (int yPos = 0; row < m_nRows; row++)
  286.       {
  287.          int thisRowHeight = m_rowHeights[row];
  288.          if ((yPos + thisRowHeight) > listYPos)
  289.          {
  290.             break;
  291.          }
  292.          else
  293.          {
  294.             yPos += thisRowHeight;
  295.          }
  296.       }
  297.       // did we find a row?
  298.       if (row < m_nRows)
  299.       {
  300.          result = row;
  301.       }
  302.    }
  303.    return result;
  304. }
  305. void CMultilineList::EnsureRowIsVisible(int row)
  306. {
  307.    ASSERT(row >= 0);
  308.    ASSERT(row < m_nRows);
  309.    // ensure row heights are up to date
  310.    CalculateRowHeights();
  311.    // find top coord of specified row
  312.    int rowYPos = 0;
  313.    for (int _row = 0; _row < m_nRows; _row++)
  314.    {
  315.       if (_row == row)
  316.       {
  317.          break;
  318.       }
  319.       else
  320.       {
  321.          rowYPos += m_rowHeights[_row];
  322.       }
  323.    }
  324.    // height of specified row
  325.    int rowHeight = m_rowHeights[row];
  326.    // content rect
  327.    CRect cr;
  328.    GetContentRect(cr);
  329.    // top of row is above visible area
  330.    if (rowYPos < m_viewYPos)
  331.    {
  332.       // scroll up so that top of view is the top of the row
  333.       m_viewYPos = rowYPos;
  334.       // update
  335.       Invalidate(FALSE);
  336.       UpdateChildControls();
  337.    }
  338.    // bottom of row is below visible area
  339.    else if ((rowYPos + rowHeight) > (m_viewYPos + cr.Height()))
  340.    {
  341.       // assuming row is smaller than the viewable area,
  342.       // scroll down so that bottom of view includes the bottom of the row
  343.       if (rowHeight < cr.Height())
  344.       {
  345.          m_viewYPos += ((rowYPos + rowHeight) - (m_viewYPos + cr.Height()));
  346.       }
  347.       else
  348.       {
  349.          // if row is taller than the viewable area, then just scroll so
  350.          // the top of the view is at the top of the row
  351.          m_viewYPos = rowYPos;
  352.       }
  353.       // update
  354.       Invalidate(FALSE);
  355.       UpdateChildControls();
  356.    }
  357. }
  358. ////////////////////////////////////////////////////////////////////////////////
  359. //                                  PRIVATE
  360. ////////////////////////////////////////////////////////////////////////////////
  361. BOOL CMultilineList::RegisterWindowClass()
  362. {
  363.    HINSTANCE hInst = AfxGetInstanceHandle();
  364.    WNDCLASS wndcls;
  365.    ZeroMemory(&wndcls,sizeof(WNDCLASS));
  366.    if (!(::GetClassInfo(hInst, MULTILINELIST_CLASSNAME, &wndcls)))
  367.    {
  368.       wndcls.style            = CS_DBLCLKS;
  369.       wndcls.lpfnWndProc      = ::DefWindowProc;
  370.       wndcls.cbClsExtra       = 0;
  371.       wndcls.cbWndExtra       = 0;
  372.       wndcls.hInstance        = hInst;
  373.       wndcls.hIcon            = NULL;
  374.       wndcls.hCursor          = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
  375.       wndcls.hbrBackground    = (HBRUSH) (COLOR_3DFACE + 1);
  376.       wndcls.lpszMenuName     = NULL;
  377.       wndcls.lpszClassName    = MULTILINELIST_CLASSNAME;
  378.       if (!AfxRegisterClass(&wndcls))
  379.       {
  380.          AfxThrowResourceException();
  381.          return FALSE;
  382.       }
  383.    }
  384.    return TRUE;
  385. }
  386. void CMultilineList::PreSubclassWindow()
  387. {
  388. // add a border
  389. ModifyStyle(GetSafeHwnd(),0,WS_BORDER,0);
  390. // KLUDGE - after adding the border above
  391. // this seems to be necessary to get the border
  392. // to initially be drawn
  393. CRect wr;
  394. GetWindowRect(wr);
  395. GetParent()->ScreenToClient(wr);
  396. wr.right -= 1;
  397. MoveWindow(wr);
  398. wr.right += 1;
  399. MoveWindow(wr);
  400. /////////////////////
  401.    PrepareOffscreenSurface();
  402.    // create GDI objects used in rendering
  403.    m_gridPen.CreatePen(PS_SOLID,GRID_WIDTH,GetSysColor(COLOR_WINDOWTEXT));
  404.    // create default font matching parent's font
  405.    LOGFONT lf;
  406.    GetParent()->GetFont()->GetLogFont(&lf);
  407.    m_defaultFont.CreateFontIndirect(&lf);
  408.    lf.lfWeight = FW_BOLD;
  409.    m_headerFont.CreateFontIndirect(&lf);
  410.    // initialize child controls
  411.    CreateChildControls();
  412.    PositionChildControls();
  413.    CWnd::PreSubclassWindow();
  414. }
  415. void CMultilineList::OnSize(UINT nType, int cx, int cy)
  416. {
  417.    CWnd::OnSize(nType, cx, cy);
  418.    PositionChildControls();
  419.    UpdateChildControls();
  420. }
  421. BOOL CMultilineList::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  422. {
  423.    NMHDR * pNMHDR = (NMHDR*)lParam;
  424.    // header control notifications
  425.    if (wParam == CHILD_ID_HEADERCTRL)
  426.    {
  427.       NMHEADER * pNMHEADER = (NMHEADER*)pNMHDR;
  428.       // user is resizing a column
  429.       if (pNMHDR->code == HDN_TRACK)
  430.       {
  431.          // use existing column object (if there is one) or use default
  432.          Column column;
  433.          std::map<int,Column>::iterator i = m_columns.find(pNMHEADER->iItem);
  434.          if (i != m_columns.end())
  435.          {
  436.             column = i->second;
  437.          }
  438.          // update the width of the column object and store it back in the map
  439.          column.m_width = pNMHEADER->pitem->cxy;
  440.          m_columns[pNMHEADER->iItem] = column;
  441.          // update
  442.          Invalidate(FALSE);
  443.          UpdateChildControls();
  444.       }
  445.       // user has finished resizing a column
  446.       if (pNMHDR->code == HDN_ENDTRACK)
  447.       {
  448.          // use existing column object (if there is one) or use default
  449.          Column column;
  450.          std::map<int,Column>::iterator i = m_columns.find(pNMHEADER->iItem);
  451.          if (i != m_columns.end())
  452.          {
  453.             column = i->second;
  454.          }
  455.          // update the width of the column object and store it back in the map
  456.          column.m_width = pNMHEADER->pitem->cxy;
  457.          m_columns[pNMHEADER->iItem] = column;
  458.          // invalidate all row heights
  459.          m_rowHeights.clear();
  460.          // update
  461.          Invalidate(FALSE);
  462.          CalculateRowHeights();
  463.          UpdateChildControls();
  464.       }
  465.    }
  466.    return CWnd::OnNotify(wParam, lParam, pResult);
  467. }
  468. void CMultilineList::CreateChildControls()
  469. {
  470.    m_headerCtrl.Create(
  471.       CCS_TOP|WS_CHILD|WS_VISIBLE,CRect(0,0,0,0),
  472.       this,CHILD_ID_HEADERCTRL);
  473.    m_headerCtrl.SetFont(&m_headerFont);
  474.    m_scrollBar.Create(
  475.       SBS_RIGHTALIGN|SBS_VERT|WS_CHILD|WS_VISIBLE,
  476.       CRect(0,0,0,0),this,CHILD_ID_SCROLLBAR);
  477.    m_scrollBarHorz.Create(
  478.       SBS_BOTTOMALIGN|SBS_HORZ|WS_CHILD|WS_VISIBLE,
  479.       CRect(0,0,0,0),this,CHILD_ID_SCROLLBARHORZ);
  480. }
  481. void CMultilineList::PositionChildControls()
  482. {
  483.    // ensure the child controls exist
  484.    if ((m_headerCtrl.GetSafeHwnd() == NULL) ||
  485.        (m_scrollBar.GetSafeHwnd() == NULL) ||
  486.        (m_scrollBarHorz.GetSafeHwnd() == NULL))
  487.        return;
  488.    CRect cr;
  489.    GetClientRect(cr);
  490.    // get text height
  491.    CDC * pDC = GetDC();
  492.    CFont * origFont = pDC->SelectObject(&m_headerFont);
  493.    TEXTMETRIC tm;
  494.    pDC->GetOutputTextMetrics(&tm);
  495.    pDC->SelectObject(origFont);
  496.    ReleaseDC(pDC);
  497.    // header control
  498.    CRect hdrRect = cr;
  499.    hdrRect.bottom = hdrRect.top + tm.tmHeight + HEADERCTRL_HEIGHT_EXTRA;
  500.    m_headerCtrl.MoveWindow(hdrRect);
  501.    // scrollbar
  502.    CRect sbRect = cr;
  503.    sbRect.left = sbRect.right - GetSystemMetrics(SM_CXVSCROLL);
  504.    sbRect.top = sbRect.top + GetSystemMetrics(SM_CYHSCROLL);
  505.    m_scrollBar.MoveWindow(sbRect);
  506.    // horiz scrollbar
  507.    CRect sbRectHorz = cr;
  508.    sbRectHorz.top = sbRectHorz.bottom - GetSystemMetrics(SM_CYHSCROLL);
  509.    if (m_scrollBar.IsWindowVisible())
  510.    {
  511.       sbRectHorz.right = sbRectHorz.right - GetSystemMetrics(SM_CXVSCROLL);
  512.    }
  513.    m_scrollBarHorz.MoveWindow(sbRectHorz);
  514.    // adjust left edge of the header control to be at the left edge of the list
  515.    // this causes the header control to scroll horizontally with the content
  516.    if (m_viewXPos > 0)
  517.    {
  518.       CRect contentRect;
  519.       GetContentRect(contentRect);
  520.       CRect hr;
  521.       m_headerCtrl.GetWindowRect(hr);
  522.       ScreenToClient(hr);
  523.       hr.left = contentRect.left - m_viewXPos;
  524.       m_headerCtrl.MoveWindow(hr);
  525.    }
  526. }
  527. void CMultilineList::UpdateChildControls()
  528. {
  529.    // get content rect
  530.    CRect contentRect;
  531.    GetContentRect(contentRect);
  532.    // remove excess header control items
  533.    while (m_headerCtrl.GetItemCount() > m_nCols)
  534.    {
  535.       if (!m_headerCtrl.DeleteItem(m_headerCtrl.GetItemCount()-1))
  536.          break;
  537.    }
  538.    // add header control items if needed
  539.    while (m_headerCtrl.GetItemCount() < m_nCols)
  540.    {
  541.       HDITEM hdi;
  542.       ZeroMemory(&hdi,sizeof(HDITEM));
  543.       hdi.mask = HDI_TEXT | HDI_WIDTH | HDI_FORMAT;
  544.       hdi.cxy = DEFAULT_COLUMN_WIDTH;
  545.       hdi.fmt = HDF_STRING | HDF_CENTER;      
  546.       hdi.pszText = _T("");
  547.       hdi.cchTextMax = 0;
  548.       if (m_headerCtrl.InsertItem(m_headerCtrl.GetItemCount(),&hdi) < 0)
  549.          break;
  550.    }
  551.    // adjust left edge of the header control to be at the left edge of the list
  552.    // this makes the header control scroll horizontally with the list content
  553.    CRect hr;
  554.    m_headerCtrl.GetWindowRect(hr);
  555.    ScreenToClient(hr);
  556.    hr.left = contentRect.left - m_viewXPos;
  557.    m_headerCtrl.MoveWindow(hr);
  558.    // for each header control item
  559.    for (int col = 0; col < m_nCols; col++)
  560.    {
  561.       // use existing object (if there is one) or default
  562.       Column column;
  563.       map<int,Column>::iterator i = m_columns.find(col);
  564.       if (i != m_columns.end())
  565.       {
  566.          column = i->second;
  567.       }
  568.       HDITEM hdi;
  569.       ZeroMemory(&hdi,sizeof(HDITEM));
  570.       hdi.mask = HDI_TEXT | HDI_WIDTH;
  571.       // get current item attributes
  572.       if (m_headerCtrl.GetItem(col,&hdi))
  573.       {
  574.          // if different, update header control item
  575.          CString curHeading(hdi.pszText);
  576.          if ((curHeading != column.m_heading) ||
  577.                (hdi.cxy != column.m_width))
  578.          {
  579.             hdi.pszText = (LPTSTR)((LPCTSTR)(column.m_heading));
  580.             hdi.cchTextMax = column.m_heading.GetLength();
  581.             hdi.cxy = column.m_width;
  582.             m_headerCtrl.SetItem(col, &hdi);
  583.          }
  584.       }
  585.    }
  586.    // compute height of the entire list
  587.    int totalHeight = 0;
  588.    map<int,int>::iterator iRowHeights;
  589.    for (iRowHeights = m_rowHeights.begin(); iRowHeights != m_rowHeights.end(); ++iRowHeights)
  590.    {
  591.       totalHeight += iRowHeights->second;
  592.    }
  593.    // should the vert scrollbar be enabled?
  594.    if (totalHeight <= contentRect.Height())
  595.    {
  596.       // no, ensure its disabled
  597.       if (m_scrollBar.IsWindowEnabled())
  598.       {
  599.          m_scrollBar.EnableWindow(FALSE);
  600.          m_scrollBar.ShowWindow(SW_HIDE);
  601.       }
  602.    }
  603.    else
  604.    {
  605.       // ensure its enabled
  606.       if (!m_scrollBar.IsWindowEnabled())
  607.       {
  608.          m_scrollBar.EnableWindow(TRUE);
  609.          m_scrollBar.ShowWindow(SW_SHOW);
  610.       }
  611.       // correct values for scrollbar
  612.       int nMin = 0;
  613.       int nMax = totalHeight;
  614.       int nPage = contentRect.Height();
  615.       int nPos = m_viewYPos;
  616.       // get current scrollbar values
  617.       SCROLLINFO si;
  618.       ZeroMemory(&si,sizeof(SCROLLINFO));
  619.       si.cbSize = sizeof(SCROLLINFO);
  620.       si.fMask = SIF_ALL;
  621.       m_scrollBar.GetScrollInfo(&si);
  622.       // if any of the values are different, update scrollbar
  623.       if ((nMin != si.nMin) ||
  624.           (nMax != si.nMax) ||
  625.           (nPage != si.nPage) ||
  626.           (nPos != si.nPos))
  627.       {
  628.          si.nMin = nMin;
  629.          si.nMax = nMax;
  630.          si.nPage = nPage;
  631.          si.nPos = nPos;
  632.          m_scrollBar.SetScrollInfo(&si);
  633.       }
  634.    }
  635.    // compute total width of the list
  636.    int totalWidth = 0;
  637.    for (int col = 0; col < m_nCols; col++)
  638.    {
  639.       // use existing object (if there is one) or default
  640.       Column column;
  641.       map<int,Column>::iterator iColumn = m_columns.find(col);
  642.       if (iColumn != m_columns.end())
  643.       {
  644.          column = iColumn->second;
  645.       }
  646.       
  647.       totalWidth += column.m_width;
  648.    }
  649.    // should horz scrollbar be enabled?
  650.    if (totalWidth <= contentRect.Width())
  651.    {
  652.       // ensure its disabled and hidden
  653.       if (m_scrollBarHorz.IsWindowEnabled())
  654.       {
  655.          m_scrollBarHorz.EnableWindow(FALSE);
  656.          m_scrollBarHorz.ShowWindow(SW_HIDE);
  657.       }
  658.    }
  659.    else
  660.    {
  661.       // ensure its enabled and visible
  662.       if (!m_scrollBarHorz.IsWindowEnabled())
  663.       {
  664.          m_scrollBarHorz.EnableWindow(TRUE);
  665.          m_scrollBarHorz.ShowWindow(SW_SHOW);
  666.       }
  667.       // correct values for scrollbar
  668.       int nMin = 0;
  669.       int nMax = totalWidth;
  670.       int nPage = contentRect.Width();
  671.       int nPos = m_viewXPos;
  672.       // get current scrollbar values
  673.       SCROLLINFO si;
  674.       ZeroMemory(&si,sizeof(SCROLLINFO));
  675.       si.cbSize = sizeof(SCROLLINFO);
  676.       si.fMask = SIF_ALL;
  677.       m_scrollBarHorz.GetScrollInfo(&si);
  678.       // if any of the values are different, update scrollbar
  679.       if ((nMin != si.nMin) ||
  680.           (nMax != si.nMax) ||
  681.           (nPage != si.nPage) ||
  682.           (nPos != si.nPos))
  683.       {
  684.          si.nMin = nMin;
  685.          si.nMax = nMax;
  686.          si.nPage = nPage;
  687.          si.nPos = nPos;
  688.          m_scrollBarHorz.SetScrollInfo(&si);
  689.       }
  690.    }
  691. }
  692. void CMultilineList::CalculateRowHeights()
  693. {
  694.    CFont * origFont = m_offscreenDC.SelectObject(&m_defaultFont);
  695.    CBitmap * origBitmap = m_offscreenDC.SelectObject(&m_offscreenBitmap);
  696.    // for all rows
  697.    for (int row = 0; row < m_nRows; row++)
  698.    {
  699.       // if a row height is not already available for this row
  700.       map<int,int>::iterator iRow = m_rowHeights.find(row);
  701.       if (iRow == m_rowHeights.end())
  702.       {
  703.          int rowHeight = 0;
  704.          // compute heights of each cell, finding the tallest one
  705.          for (int col = 0; col < m_nCols; col++)
  706.          {
  707.             // get column object (if one exists) or use the default
  708.             Column column;
  709.             std::map<int,Column>::iterator iCol = m_columns.find(col);
  710.             if (iCol != m_columns.end())
  711.             {
  712.                column = iCol->second;
  713.             }
  714.             // get cell object (if one exists) or use the default
  715.             Cell cell;
  716.             map<pair<int,int>,Cell>::iterator iCell = m_cells.find(make_pair(col,row));
  717.             if (iCell != m_cells.end())
  718.             {
  719.                cell = iCell->second;
  720.             }
  721.             CRect textRect(0,0,column.m_width,0);
  722.             textRect.DeflateRect(INNER_PADDING,INNER_PADDING);
  723.             int origRight = textRect.right;
  724.             // compute cell height
  725.             m_offscreenDC.DrawTextEx(cell.m_text,textRect,
  726.                DT_CALCRECT|DT_LEFT|DT_NOPREFIX|DT_TOP|DT_WORDBREAK,
  727.                NULL);
  728.             textRect.right = origRight;
  729.             textRect.InflateRect(INNER_PADDING,INNER_PADDING);
  730.             textRect.bottom += GRID_WIDTH;
  731.             if (textRect.bottom > rowHeight)
  732.             {
  733.                rowHeight = textRect.bottom;
  734.             }
  735.          }
  736.          // store row height
  737.          m_rowHeights[row] = rowHeight;
  738.       }
  739.    }
  740.    m_offscreenDC.SelectObject(origBitmap);
  741.    m_offscreenDC.SelectObject(origFont);
  742. }
  743. void CMultilineList::GetContentRect(CRect & r)
  744. {
  745.    // get geom of client area and child controls
  746.    CRect cr;
  747.    GetClientRect(cr);
  748.    CRect hdrRect;
  749.    m_headerCtrl.GetClientRect(hdrRect);
  750.    CRect sbRect;
  751.    m_scrollBar.GetClientRect(sbRect);
  752.    CRect sbRectHorz;
  753.    m_scrollBarHorz.GetClientRect(sbRectHorz);
  754.    // shrink for child controls
  755.    r = cr;
  756.    r.top += hdrRect.Height();
  757.    r.right -= sbRect.Width();
  758.    r.bottom -= sbRectHorz.Height();
  759.    // vert scroll bar not visible; extend area to cover where it would be
  760.    if (!m_scrollBar.IsWindowVisible())
  761.    {
  762.       r.right += sbRect.Width();
  763.    }
  764.    // horz scroll bar not visible; extend area to cover where it would be
  765.    if (!m_scrollBarHorz.IsWindowVisible())
  766.    {
  767.       r.bottom += sbRectHorz.Height();
  768.    }
  769. }
  770. BOOL CMultilineList::OnEraseBkgnd(CDC* pDC)
  771. {
  772.    // do nothing; we don't want to erase the background because we
  773.    // use double-buffering and so overwrite the entire client area
  774.    // with every repaint
  775.    return TRUE;
  776. }
  777. void CMultilineList::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
  778. {
  779.    CWnd::OnVScroll(nSBCode, nPos, pScrollBar);
  780.    CalculateRowHeights();
  781.    // get content rect
  782.    CRect cr;
  783.    GetContentRect(cr);
  784.    if ((nSBCode == SB_THUMBPOSITION) ||
  785.        (nSBCode == SB_THUMBTRACK))
  786.    {
  787.       // set pos to specified absolute pos
  788.       m_viewYPos = nPos;
  789.    }
  790.    else if (nSBCode == SB_PAGEUP)
  791.    {
  792.       // set pos up by 1 page
  793.       m_viewYPos -= cr.Height();
  794.    }
  795.    else if (nSBCode == SB_PAGEDOWN)
  796.    {  
  797.       // compute height of the entire list
  798.       int totalHeight = 0;
  799.       map<int,int>::iterator iRowHeights;
  800.       for (iRowHeights = m_rowHeights.begin(); iRowHeights != m_rowHeights.end(); ++iRowHeights)
  801.       {
  802.          totalHeight += iRowHeights->second;
  803.       }
  804.       // move down by 1 page or to end if there's less
  805.       // than one page remaining outside the view
  806.       int remainingSpace = totalHeight - (m_viewYPos + cr.Height());
  807.       if (remainingSpace < cr.Height())
  808.       {
  809.          m_viewYPos += remainingSpace;
  810.       }
  811.       else
  812.       {
  813.          m_viewYPos += cr.Height();
  814.       }
  815.    }
  816.    else if ((nSBCode == SB_LINEUP) || (nSBCode == SB_LINEDOWN))
  817.    {
  818.       // determine top visible row
  819.       int y1 = 0;
  820.       int row = 0;
  821.       for (int yPos = 0; row < m_nRows; row++)
  822.       {
  823.          int thisRowHeight = m_rowHeights[row];
  824.          if ((yPos + thisRowHeight) > m_viewYPos)
  825.          {
  826.             break;
  827.          }
  828.          else
  829.          {
  830.             yPos += thisRowHeight;
  831.          }
  832.       }
  833.       // determine remaining height in list (including and after the top visible row)
  834.       int remainingHeight = 0;
  835.       for (int row2 = row; row2 < m_nRows; row2++)
  836.       {
  837.          remainingHeight += m_rowHeights[row2];
  838.       }
  839.       // for scrolling up, use one row above the top visible row
  840.       if (nSBCode == SB_LINEUP)
  841.       {
  842.          row--;
  843.          if (row < 0)
  844.             row = 0;
  845.       }
  846.       // move by the height of the top row
  847.       int deltaY = m_rowHeights[row];
  848.       if (nSBCode == SB_LINEUP)
  849.       {
  850.          deltaY *= -1;
  851.       }
  852.       // assuming there's more to scroll (or if we're scrolling up)
  853.       if (remainingHeight > cr.Height() || (nSBCode == SB_LINEUP))
  854.       {
  855.          m_viewYPos += deltaY;
  856.          if (m_viewYPos < 0)
  857.          {
  858.             m_viewYPos = 0;
  859.          }
  860.       }
  861.    }
  862.    // limit upward scrolling
  863.    if (m_viewYPos < 0)
  864.    {
  865.       m_viewYPos = 0;
  866.    }
  867.    // update
  868.    Invalidate(TRUE);
  869.    UpdateChildControls();
  870. }
  871. void CMultilineList::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
  872. {
  873.    CWnd::OnHScroll(nSBCode, nPos, pScrollBar);
  874.    // compute total width of the list
  875.    int totalWidth = 0;
  876.    for (int col = 0; col < m_nCols; col++)
  877.    {
  878.       // use existing object (if there is one) or default
  879.       Column column;
  880.       map<int,Column>::iterator iColumn = m_columns.find(col);
  881.       if (iColumn != m_columns.end())
  882.       {
  883.          column = iColumn->second;
  884.       }
  885.       
  886.       totalWidth += column.m_width;
  887.    }
  888.    if ((nSBCode == SB_THUMBPOSITION) ||
  889.        (nSBCode == SB_THUMBTRACK))
  890.    {
  891.       // set pos to specified absolute pos
  892.       m_viewXPos = nPos;
  893.    }
  894.    else if (nSBCode == SB_PAGELEFT)
  895.    {
  896.       CRect cr;
  897.       GetContentRect(cr);
  898.       // set pos up by left 1 page
  899.       m_viewXPos -= cr.Width();
  900.    }
  901.    else if (nSBCode == SB_PAGERIGHT)
  902.    {
  903.       CRect cr;
  904.       GetContentRect(cr);
  905.       
  906.       // move right by 1 page or to end if there's less
  907.       // than one page remaining outside the view
  908.       int remainingSpace = totalWidth - (m_viewXPos + cr.Width());
  909.       if (remainingSpace < cr.Width())
  910.       {
  911.          m_viewXPos += remainingSpace;
  912.       }
  913.       else
  914.       {
  915.          m_viewXPos += cr.Width();
  916.       }
  917.    }
  918.    else if (nSBCode == SB_LINELEFT)
  919.    {      
  920.       // set pos left by constant amount
  921.       m_viewXPos -= HORZSCROLL_PIXELS;
  922.    }
  923.    else if (nSBCode == SB_LINERIGHT)
  924.    {
  925.       CRect cr;
  926.       GetContentRect(cr);
  927.       // don't scroll right unless there's more to scroll to
  928.       if ((totalWidth - m_viewXPos) > cr.Width())
  929.       {
  930.          // set pos right by constant amount
  931.          m_viewXPos += HORZSCROLL_PIXELS;
  932.       }
  933.    }
  934.    // limit scrolling
  935.    if (m_viewXPos < 0)
  936.    {
  937.       m_viewXPos = 0;
  938.    }
  939.    Invalidate(TRUE);
  940.    UpdateChildControls();
  941. }
  942. BOOL CMultilineList::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
  943. {
  944.    // scroll up/down 1 line when mouse-wheel is used
  945.    if (zDelta < 0)
  946.    {
  947.       OnVScroll(SB_LINEDOWN,0,NULL);
  948.    }
  949.    else
  950.    {
  951.       OnVScroll(SB_LINEUP,0,NULL);
  952.    }
  953.    return CWnd::OnMouseWheel(nFlags, zDelta, pt);
  954. }
  955. void CMultilineList::OnLButtonDown(UINT nFlags, CPoint point)
  956. {
  957.    // on click, select the row which was clicked on
  958.    m_curSelRow = GetRowFromPoint(point);
  959.    Invalidate(FALSE);
  960.    UpdateChildControls();
  961.    // ensure the selected row is visible
  962.    if (m_curSelRow >= 0)
  963.    {
  964.       EnsureRowIsVisible(m_curSelRow);
  965.    }
  966.    // sent notification message to parent window
  967.    int thisCtrlID = GetDlgCtrlID();
  968.    GetParent()->PostMessage(WM_COMMAND,MAKEWPARAM(thisCtrlID,LBN_SELCHANGE),(LPARAM)GetSafeHwnd());
  969. }
  970. void CMultilineList::OnPaint()
  971. {
  972.    CPaintDC dc(this);
  973.    // ensure offscreen rendering surface is ready
  974.    if (!PrepareOffscreenSurface())
  975.       return;
  976.    // ensure GDI objects are ready and match current system preferences
  977.    LOGPEN lp;
  978.    m_gridPen.GetLogPen(&lp);
  979.    if (lp.lopnColor != GetSysColor(COLOR_WINDOWTEXT))
  980.    {
  981.       m_gridPen.DeleteObject();
  982.       m_gridPen.CreatePen(PS_SOLID,GRID_WIDTH,GetSysColor(COLOR_WINDOWTEXT));
  983.    }
  984.    // ensure row height calculations are up to date
  985.    CalculateRowHeights();
  986.    // get content area (client area minus child controls)
  987.    CRect cr;
  988.    GetClientRect(cr);
  989.    CFont * origFont = m_offscreenDC.SelectObject(&m_defaultFont);
  990.    CBitmap * origBitmap = m_offscreenDC.SelectObject(&m_offscreenBitmap);
  991.    // render the content area
  992.    CRect renderRect;
  993.    GetContentRect(renderRect);
  994.    RenderContent(m_offscreenDC,renderRect);
  995.    // blt the rendered content from the offscreen area to the window
  996.    dc.BitBlt(renderRect.left,renderRect.top,
  997.       renderRect.Width(),renderRect.Height(),
  998.       &m_offscreenDC,
  999.       renderRect.left,renderRect.top,
  1000.       SRCCOPY);
  1001.    m_offscreenDC.SelectObject(origBitmap);
  1002.    m_offscreenDC.SelectObject(origFont);
  1003. }
  1004. BOOL CMultilineList::PrepareOffscreenSurface()
  1005. {
  1006.    CRect wr;
  1007.    GetWindowRect(wr);
  1008.    CSize ws = wr.Size();
  1009.    // not yet created or the size has changed
  1010.    if ((m_offscreenBitmap.GetSafeHandle() == NULL) ||
  1011.        (m_offscreenBitmapSize != ws))
  1012.    {
  1013.       CDC * pDC = GetDC();
  1014.       // create offscreen DC if necessary
  1015.       if (m_offscreenDC.GetSafeHdc() == NULL)
  1016.       {
  1017.          if (!m_offscreenDC.CreateCompatibleDC(pDC))
  1018.             return FALSE;
  1019.       }
  1020.       // delete existing bitmap (if there is one)
  1021.       if (m_offscreenBitmap.GetSafeHandle() != NULL)
  1022.       {
  1023.          m_offscreenBitmap.DeleteObject();
  1024.       }
  1025.       // create new bitmap sized to the window
  1026.       if (!m_offscreenBitmap.CreateCompatibleBitmap(pDC,ws.cx,ws.cy))
  1027.          return FALSE;
  1028.       // store the new size
  1029.       m_offscreenBitmapSize = ws;
  1030.       ReleaseDC(pDC);
  1031.    }
  1032.    return TRUE;
  1033. }
  1034. void CMultilineList::RenderContent(CDC & dc, CRect & r)
  1035. {
  1036.    dc.FillRect(r,CBrush::FromHandle(GetSysColorBrush(COLOR_WINDOW)));
  1037.    dc.SetBkMode(TRANSPARENT);
  1038.    dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
  1039.    CPen * origPen = dc.SelectObject(&m_gridPen);
  1040.    int y1 = 0;
  1041.    int row = 0;
  1042.    // determine top visible row and setup y1 to be the top of its rendering area
  1043.    for (int yPos = 0; row < m_nRows; row++)
  1044.    {
  1045.       int thisRowHeight = m_rowHeights[row];
  1046.       if ((yPos + thisRowHeight) > m_viewYPos)
  1047.       {
  1048.          y1 = r.top - (m_viewYPos - yPos);
  1049.          break;
  1050.       }
  1051.       else
  1052.       {
  1053.          yPos += thisRowHeight;
  1054.       }
  1055.    }
  1056.    int firstRenderedRow = row;
  1057. // for each row
  1058.    for (; (row < m_nRows) && (y1 <= r.bottom); row++)
  1059.    {
  1060. // start with zero height
  1061.       int y2 = y1;
  1062. // for each column
  1063.       for (int col = 0, x1 = r.left-m_viewXPos; (col < m_nCols); col++)
  1064.       {
  1065. // get the column object (if it exists) or use defaults
  1066.          Column column;
  1067.          std::map<int,Column>::iterator i = m_columns.find(col);
  1068.          if (i != m_columns.end())
  1069.          {
  1070.             column = i->second;
  1071.          }
  1072. // get the cell object (if it exists) or use defaults
  1073.          Cell cell;
  1074.          map<pair<int,int>,Cell>::iterator j = m_cells.find(make_pair(col,row));
  1075.          if (j != m_cells.end())
  1076.          {
  1077.             cell = j->second;
  1078.          }
  1079. // determine the required size of the text, given the column width
  1080.          int x2 = x1 + column.m_width;
  1081.          CRect textRect(x1,y1,x2,y1);
  1082.          textRect.DeflateRect(INNER_PADDING,INNER_PADDING);
  1083.          int origRight = textRect.right;
  1084.          
  1085.          dc.DrawTextEx(cell.m_text,textRect,
  1086.             DT_CALCRECT|DT_LEFT|DT_NOPREFIX|DT_TOP|DT_WORDBREAK,
  1087.             NULL);
  1088.          
  1089.          textRect.right = origRight;
  1090. // if this row is selected, fill in the background with the selection color and
  1091. // set the text color
  1092.          if (row == m_curSelRow)
  1093.          {
  1094.             CRect bgRect(textRect);
  1095.             bgRect.InflateRect(INNER_PADDING,INNER_PADDING);
  1096.             bgRect.bottom = bgRect.top + m_rowHeights[row] + GRID_WIDTH;
  1097.             bgRect.top += GRID_WIDTH;
  1098.             dc.FillRect(bgRect,CBrush::FromHandle(GetSysColorBrush(COLOR_HIGHLIGHT)));
  1099.             dc.SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
  1100.          }
  1101.          else
  1102.          {
  1103. // else, ensure text color is set to the non-selected color
  1104.             dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
  1105.          }
  1106.          textRect.bottom += INNER_PADDING;
  1107.          textRect.bottom += GRID_WIDTH;
  1108. // ensure tallest cell is stored
  1109.          if (textRect.bottom > y2)
  1110.          {
  1111.             y2 = textRect.bottom;
  1112.          }
  1113. // render the cell text
  1114.          dc.DrawTextEx(cell.m_text,textRect,
  1115.             DT_LEFT|DT_NOPREFIX|DT_TOP|DT_WORDBREAK,
  1116.             NULL);
  1117.          x1 = x2;
  1118.       }
  1119. // render horizontal grid line below the current row
  1120.       dc.MoveTo(r.left,y2+GRID_WIDTH);
  1121.       dc.LineTo(x1,y2+GRID_WIDTH);
  1122. // if not the first row, also render the top horizontal line above the current row
  1123.       if (row > firstRenderedRow)
  1124.       {
  1125.          dc.MoveTo(r.left,y1+GRID_WIDTH);
  1126.          dc.LineTo(x1,y1+GRID_WIDTH);
  1127.       }
  1128. // render the vertical lines between the columns
  1129.       for (int col = 0, x1 = r.left-m_viewXPos; (col < m_nCols) && (x1 <= r.right); col++)
  1130.       {
  1131.          Column column;
  1132.          std::map<int,Column>::iterator i = m_columns.find(col);
  1133.          if (i != m_columns.end())
  1134.          {
  1135.             column = i->second;
  1136.          }
  1137.          int x2 = x1 + column.m_width;
  1138.          dc.MoveTo(x2,y1+GRID_WIDTH);
  1139.          dc.LineTo(x2,y2+GRID_WIDTH);
  1140.          x1 = x2;
  1141.       }
  1142.       y1 = y2;
  1143.    }
  1144.    dc.SelectObject(origPen);
  1145. }