65.9K
CodeProject 正在变化。 阅读更多。
Home

CQuickList

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (83投票s)

2004年8月28日

Ms-RL

14分钟阅读

viewsIcon

677596

downloadIcon

13118

一个自定义绘制的虚拟列表控件。支持子项编辑、图像、按钮(复选框、单选按钮)、自定义颜色和“列导航”。

Sample Image - quicklist.gif

目录

引言

CQuickList 是另一个派生于 CListCtrl 的拥有者绘制控件。该控件与 CodeProject 上其他拥有者绘制列表控件的主要区别在于,它是一个虚拟列表。这意味着您无需将项插入列表中。相反,列表控件会在需要信息时询问父窗口。因此,您可以快速创建大型复杂列表,而无需占用大量内存。如果您以前从未用过虚拟列表,最好看一下文章 “虚拟列表”,我将在其中尝试解释这个概念。

背景

一段时间以前,我需要一个列表中子项可以包含图像。如果还能编辑子项就更好了。但对我来说最重要的是列表是虚拟的,而且由于我找不到这样的控件,所以我开始自己创建。这就是我目前为止的成果。

然而,尽管我没有找到我想要的东西,但我找到了其他控件,它们对我来说是巨大的帮助。所以,感谢 CodeProject 上的所有其他作者 :-)。

功能

CQuickList 的主要功能包括:

  • 子项编辑。
  • 子项中的图像。
  • 列表中的按钮(如复选框、单选按钮)。
  • 进度条。
  • 自定义颜色。
  • 工具提示。
  • 列导航。
  • 粗体/斜体文本。
  • 在列表为空时显示消息。
  • 自动处理 LVN_ODFINDITEM 消息。
  • 代码量小。使用 #define 移除未使用的功能。
  • Unicode 支持。
  • 支持 Windows XP 主题。

创建 CQuickList

创建 CQuickList 非常简单。在资源编辑器中添加一个列表控件,并为此控件添加一个 CListCtrl 变量。在头文件中将 “CListCtrl” 替换为 “CQuickList”,就完成了。

确保您已勾选“Owner data”样式,并将视图设置为“Report”模式。同时请确保“Owner draw fixed” *未* 勾选。

向列表中添加项

假设 m_list 是列表的控件变量。通常,您会这样向列表中添加数据

m_list.InsertItem(0, _T("Hello world"));

但在虚拟列表中(如 CQuickList),这样做无效。取而代之的是,您需要自己处理数据。您需要更改列表显示的元素数量,而不是添加项

//"Add" 100 elements
m_list.SetItemCount(100);

无论您将项数设置为 100 还是 1,000,000,运行此命令所需的时间实际上都将为零。在非虚拟列表中,添加一百万个项可能需要数小时。

处理 WM_QUICKLIST_GETLISTITEMDATA 消息

正常的虚拟列表会在需要信息时向父窗口发送 LVN_GETDISPINFO 消息。在使用 CQuickList 时也会发送此消息,但该消息并不重要。您应该处理 WM_QUICKLIST_GETLISTITEMDATA 消息。在头文件中添加此项

afx_msg LRESULT OnGetListItem(WPARAM wParam, LPARAM lParam);

在消息映射中添加消息处理程序

BEGIN_MESSAGE_MAP(CMyListCtrlDlg, CDialog)
    //...other messages here...
    ON_MESSAGE(WM_QUICKLIST_GETLISTITEMDATA, OnGetListItem) 
END_MESSAGE_MAP()

最后,添加函数

LRESULT CMyListCtrlDlg::OnGetListItem(WPARAM wParam, LPARAM lParam)
{
    //wParam is a handler to the list
    //Make sure message comes from list box
    ASSERT( (HWND)wParam == m_list.GetSafeHwnd() );

    //lParam is a pointer to the data that 
    //is needed for the element
    CQuickList::CListItemData* data = 
        (CQuickList::CListItemData*) lParam;

    //Get which item and subitem that is asked for.
    int item = data->GetItem();
    int subItem = data->GetSubItem();

    //...insert information that is needed in "data"...

    return 0;
}

CQuickList::CListItemData

CQuickList::CListItemData 是一个非常简单但重要的类。该类包含几个成员变量。这些变量用于绘制项。该类中的公共部分是

class CQuickList::CListItemData
{
public:
    CListItemData();

    //Some obvius functions
    int GetItem() const;
    int GetSubItem() const;
    bool IsSelected() const;
    bool IsHot() const;

    //The item text
    CString m_text;

    //Tool tip text. Note: Don't forget to call EnableToolTips()
    //to enable tool tips.
    CString m_tooltip;

    //Set this to true if you don't want to draw a selection mark
    //even if this item is selected.
    //Default value: false
    bool m_noSelection;

    //Set this to true if the item is available for editing
    //Default value: false
    bool m_allowEdit;

    //Information about which text style that should be used.
    struct CListTextStyle
    {
        //Default value: false
        bool m_bold;

        //Default value: false
        bool m_italic;

        //Default value:
        // DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS 
        //See CDC:DrawText in MSDN
        UINT m_textPosition; 
    } m_textStyle;

    //Information about the image
    struct CListImage
    {
        //The image position in the image list.
        //-1 if no image.
        //Default value: -1
        int m_imageID;

        //The image list where the image is.
        //Default value: A pointer to the image list in the list
        //control that is used small images (LVSIL_SMALL)
        CImageList* m_imageList;

        //Set true if you don't want to draw selection mark if the
        //item is selection
        //Default value: true
        bool m_noSelection;

        //Center the image. Useful if no text.
        //Default value: false;
        bool m_center;

        //Blend if the image is selected. Use ILD_BLEND25 or
        //ILD_BLEND50, or 0 if you don't want to use this feature.
        //Default value: ILD_BLEND25
        int m_blend;

    } m_image;

    //Information about the button
    struct CListButton
    {
        //The style to use to draw the control.
        //Default value: DFCS_BUTTONCHECK
        //Use DFCS_CHECKED to draw the check mark.
        //Use DFCS_BUTTONRADIO for radio button, DFCS_BUTTONPUSH
        //for push button.
        //See CDC::DrawFrameControl for details.
        int m_style; 

        //If you want to draw a button, set this to true
        //Default value: false
        bool m_draw;

        //Center the check box is the column. Useful if no text
        //Default value: false
        bool m_center;

        //Set this to true if you don't want to draw selection
        //mark under the control.
        //Default value: true
        bool m_noSelection;

    } m_button;

    //Information about the progress bar
    struct CListProgressbar
    {
        //Note: The m_text member specifies the text in the
        //progress bar

        //The max value of progress bar. Use -1 to disable
        //progress bar. The min value is supposed to be 0.
        //Default value: -1
        int m_maxvalue;

        //The value the progress bar has. The width of the
        //progress bar is calculated with use m_value and
        //m_maxvalue.
        //Default value: 0
        int m_value;

        //The color the progress bar should be drawn with. 
        //Default value: DEFAULTCOLOR
        COLORREF m_fillColor;

        //The color of the text on the progress bar
        //Default value: DEFAULTCOLOR
        COLORREF m_fillTextColor;

        //How to draw the edge. Use 0 for no edge.
        //See CDC::DrawEdge for different styles.
        //Default value: EDGE_SUNKEN
        UINT m_edge;
    } m_progressBar;

    //Information about the colors to use
    struct CListColors
    {
        //Default value for all: DEFAULTCOLOR
        COLORREF m_textColor;
        COLORREF m_backColor;
        COLORREF m_hotTextColor;
        COLORREF m_selectedTextColor;
        COLORREF m_selectedBackColor;
        COLORREF m_selectedBackColorNoFocus;

        //These colors are used to draw selected items in
        //the "navigation column"
        COLORREF m_navigatedTextColor;
        COLORREF m_navigatedBackColor;
    } m_colors;

};

如您所见,有多种设置可供使用。但您可能只会用到其中的几个。我将在以下文本中尝试解释大多数设置。以下文本中的 data 是指向 CQuickList::CListItemData 对象的指针。

文本

最简单的设置是 m_text。这是要绘制的文本

data->m_text = _T("Hello world");

这里将为当前项绘制文本 Hello world

工具提示

Tooltip sample

工具提示有时会很有用。如果您将鼠标光标悬停在项上,此处设置的文本将显示为工具提示。示例:

data->m_tooltip = _T("Tip: Hello world");

此处将显示 “Tip: Hello world”。

注意 1:要激活工具提示,您必须调用 EnableToolTips(TRUE)

注意 2:如果您不使用此功能,可以定义 QUICKLIST_NOTOOLTIP 来减小应用程序的大小。

注意 3:不幸的是,在使用工具提示时存在一些问题。参见关注点

不绘制选中项

No selection sample. Column 1 is not selected.

默认情况下,如果一个项被选中,它将被绘制为选中状态。但是,如果您希望即使项被选中也将其绘制为未选中状态,请执行以下操作:

data->m_noSelection = false;

现在,即使该项被选中,也不会被绘制为选中状态。但这有什么用呢?这似乎是一个无用的功能,但有时它会非常有用。例如,如果您在一个列表中有多个列,某些列可能被绘制为未选中状态。在图片中,即使第一列的所有项都被选中,它们也被绘制为未选中状态。

允许编辑

如果您希望允许编辑一个项,请执行以下操作:

data->m_allowEdit = true;

注意 1:您还需要处理一些消息才能实现编辑。我将在后面讨论

注意 2:如果您不使用此功能,可以定义 QUICKLIST_NOEDIT 来减小应用程序的大小。

文本样式

Text style sample

有时,将文本绘制为粗体或斜体很有用。这很简单:

data->m_textStyle.bold = true;
data->m_textStyle.italic = true;

您还可以指定文本的绘制位置。示例:

//Left align text:
data->m_textStyle.m_textPosition = 
    DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS;

//Center text:
data->m_textStyle.m_textPosition = 
    DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS;

但是您可以进行比这更多的设置。请参见 MSDN 中的 CDC::DrawText

注意:如果您不使用此功能,可以定义 QUICKLIST_NOTEXTSTYLE 来减小应用程序的大小。

图片

Image sample

拥有图像很有用。如果您有一个与列表连接的图像列表(称为 SetImageList),您只需执行以下操作:

//Use image 2 in the list
data->m_image.m_imageID = 2;

然后将使用默认图像列表。但是,您可以指定要使用的图像列表,如下所示:

//Use another image list
data->m_image.m_imageList = &m_mySecondImagelist;

默认情况下,如果项被选中,图像*不会*被绘制为选中状态。但您可以更改此设置:

data->m_image.m_noSelection = false;

如图所示,选中项的蓝色比未选中项稍深一些。如果您不希望这样,可以更改 m_blend 设置:

//Don't "blend" the image:
data->m_image.m_blend = 0;
//Other possible values are ILD_BLEND25 and ILD_BLEND50 (default).

默认情况下,图像会绘制在左侧。但如果您除了图像没有其他内容,为什么不将其居中呢?

//Center image
data->m_image.m_center = true;

注意:如果您不使用此功能,可以定义 QUICKLIST_NOIMAGE 来减小应用程序的大小。

按钮

Button sample

如果您想绘制复选框或单选按钮,可以使用 m_button 变量:

//We want to draw a button
data->m_button.m_draw = true;

//Check box, not checked:
data->m_button.m_style = DFCS_BUTTONCHECK
//Check box, checked:
data->m_button.m_style = DFCS_BUTTONCHECK|DFCS_CHECKED;

//Radio button, not checked:
data->m_button.m_style = DFCS_BUTTONRADIO
//Radio button, checked:
data->m_button.m_style = DFCS_BUTTONRADIO|DFCS_CHECKED;

与图像一样,按钮默认不会被绘制为选中状态。它们也可以居中:

//Draw as selected
data->m_button.m_noSelection = false;

//Center
data->m_button.m_center = true;

注意 1:默认情况下,XP 中的按钮*不会*根据主题进行绘制。要解决此问题,您应该调用 SetThemeManager()在此处阅读

注意 2:如果您不使用此功能,可以定义 QUICKLIST_NOBUTTON 来减小应用程序的大小。

进度条

Progressbar sample

进度条在列表控件中很少使用,但它们可能很有用。要使用它,首先指定最大值:

//Max value is 100
data->m_progressBar.m_maxvalue = 100;

最小值是 0。然后指定进度条的值:

//Fill half the progress bar:
data->m_progressBar.m_value = 50;

您还可以更改边框。边框的默认值是 EDGE_SUNKEN。如果您不希望有边框,请将其值设置为 0。

//No edge:
data->m_progressBar.m_edge = 0;

有关更多设置,请参见 MSDN 中的 CDC::DrawEdge

您还可以指定使用的填充颜色和文本颜色。这些设置的默认值是 DEFAULTCOLOR,这意味着 Windows 应决定使用哪种颜色。

//Red fill color
data->m_progressBar.m_fillColor = RGB(255,0,0);
//White text color
data->m_progressBar.m_fillColor = RGB(255,255,255);

注意 1:如果您在 m_text 中指定了任何文本,该文本将绘制在进度条中。

注意 2:如果您不使用此功能,可以定义 QUICKLIST_NOPROGRESSBAR 来减小应用程序的大小。

颜色

Default colors Specified colors

默认情况下将使用 Windows 颜色,但您可以使用 m_colors 进行更改:

//Green text
data->m_colors.m_textColor = RGB(0,255,0);

//Black background
data->m_colors.m_backColor = RGB(0,0,0);

//If the item is "hot", use purple color
data->m_colors.m_hotTextColor = RGB(0,255,255);

//If the item is selected, the text should 
//be drawn in white color
data->m_colors.m_selectedTextColor = RGB(255,255,255);

//If the item is selected, the background should
//be drawn in green color
data->m_colors.m_selectedBackColor = RGB(0,128,0);

//If the item is selected but the list hasn't focus,
//the background should be drawn in gray color
data->m_colors.m_selectedBackColorNoFocus = RGB(64,64,64);

//If the item is "navigated", text will be drawn in red
data->m_colors.m_navigatedTextColor = RGB(255,0,0);

//If the item is "navigated", background will be drawn in blue
data->m_colors.m_navigatedBackColor = RGB(0,0,128);

设置背景颜色时,您可以使用 TRANSPARENTCOLOR 作为透明色。这在有背景图像时非常有用。

子项之间的导航

Navigation sample

在普通列表中,您可以使用鼠标或键盘选择项。但无法选择子项,这有时可能非常有用。不过,在 CQuickList 中,这是可能的 :-)。要指定当前选中的列,请调用:

//Enable navigation (it is enabled as default)
m_list.EnableColumnNavigation(true);

//Set column 2 as "navigated".
m_list.SetNavigationColumn(2);

好的,这很简单。但是,假设您有三列,并且不希望可以导航到第 2 列。要解决此问题,请添加一个消息处理程序来处理 WM_QUICKLIST_NAVIGATIONTEST 消息(在头文件中添加一个函数,并在消息处理程序中连接它)。然后,像这样编写函数:

LRESULT CMyListCtrlDlg::OnNavigationTest(WPARAM wParam, LPARAM lParam)
{
    //Make sure message comes from list box
    ASSERT( (HWND)wParam == m_list.GetSafeHwnd() );

    CQuickList::CNavigationTest* test = 
        (CQuickList::CNavigationTest*) lParam;

    //The previous column is in test->m_previousColumn.

    //Don't allow navigation to column 2
    if(test->m_newColumn == 2)
        test->m_allowChange = false;

    return 0;
}

现在,将无法导航到第 2 列。

注意:如果您不使用此功能,可以定义 QUICKLIST_NONAVIGATION 来减小应用程序的大小。

LVN_ODFINDITEM 消息

您可能知道,通过在列表中输入字符,可以查找列表中的项(此处有更多信息)。要在虚拟列表中实现此功能,您需要处理 LVN_GETDISPINFO 消息。但在使用 CQuickList 时,这并非必需,列表会自动为您处理。但是,您可以指定列表在尝试查找项时要搜索的列。示例:

//Search in column 1:
m_list.SetKeyfindColumn(1);

您可以使用 KEYFIND_CURRENTCOLUMN 在当前导航的列中搜索。如果您希望父窗口处理此消息,请使用 KEYFIND_DISABLED

注意:如果您不使用此功能,可以定义 QUICKLIST_NOKEYFIND 来减小应用程序的大小。

点击图像/按钮

当您单击复选框时,您期望它会切换状态。但在虚拟列表中,列表无法更改值。为了解决这个问题,列表会向父窗口发送一条消息,由父窗口来完成工作。添加一个消息处理程序来处理 WM_QUICKLIST_CLICK 消息(在头文件中添加一个函数,并在消息处理程序中连接它)。然后,像这样编写函数:

LRESULT CMyListCtrlDlg::OnListClick(WPARAM wParam, LPARAM lParam)
{
    //Make sure message comes from list box
    ASSERT( (HWND)wParam == m_list.GetSafeHwnd() );

    CQuickList::CListHitInfo *hit= 
        (CQuickList::CListHitInfo*) lParam;

    //Item:    hit->m_item
    //Subitem: hit->m_subitem

    //Hit button?
    if(hit->m_onButton)
    {
        //...toggle check box in the database...

        //Redraw check box
        m_list.RedrawCheckBoxs( hit->m_item, 
                    hit->m_item, 
                    hit->m_subitem);
    }
    else //Hit image?
    if(hit->m_onImage)
    {
        //... toggle image ...

        //Redraw image
        m_list.RedrawImages(hit->m_item, 
                    hit->m_item, 
                    hit->m_subitem);
    }

    return 0;
}

如您所见,可以得知是否点击了图像。

注意:解决此问题的另一种方法是处理 NM_LCLICK 消息。然后调用 CQuickList::HitTest 来查看是否点击了按钮或图像。

空列表

Empty list sample

如果列表为空,显示一条小消息可能更友好。这很容易做到:

//Show "Hello world" is the list is empty
m_list.SetEmptyMessage(_T("Hello world"));

注意:如果您不使用此功能,可以定义 QUICKLIST_NOEMPTYMESSAGE 来减小应用程序的大小。

右键单击列标题

Right click sample

据我所知,在 CListCtrl 中没有简单的方法可以捕获列标题上的右键单击。这很可惜,因为这是一种向用户显示上下文菜单的好方法,例如,用户可以在其中隐藏菜单。在使用 CQuickList 时,当在列标题上发生右键单击时,列表会向父窗口发送 WM_QUICKLIST_HEADERRIGHTCLICK 消息。WPARAM 是列表的句柄,LPARAM 是指向 CQuickList::CHeaderRightClick 对象的指针。该对象包含鼠标位置(m_mousePos)和被点击的列(m_column)。

一个弹出菜单的函数可能如下所示:

LRESULT CMyListCtrlDlg::OnHeaderRightClick(WPARAM wParam, LPARAM lParam)
{
    //Make sure message comes from list box
    ASSERT( (HWND)wParam == m_list.GetSafeHwnd() );

    CQuickList::CHeaderRightClick *hit= 
        (CQuickList::CHeaderRightClick*) lParam;

    //Load menu
    CMenu menu;
    VERIFY(menu.LoadMenu(IDR_HEADERMENU));

    //Pop up sub menu 0
    CMenu* popup = menu.GetSubMenu(0);

    popup->TrackPopupMenu( TPM_LEFTALIGN | TPM_RIGHTBUTTON,
                hit->m_mousePos.x, 
                hit->m_mousePos.y, 
                this);

    return 0;
}

编辑子项

CQuickList 中编辑子项与 CListCtrl 的区别不大。编辑开始前,会向父窗口发送 OnBeginlabeleditList 消息。除非您想指定比 m_text 中指定的文本不同的文本,否则您可以忽略此消息。编辑完成后,会发送 LVN_ENDLABELEDIT 消息。如果您想保存文本,必须为此消息添加处理程序。函数可能如下所示:

void CMyListCtrlDlg::OnEndlabeleditList(NMHDR* pNMHDR, LRESULT* pResult) 
{
    LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

    // If pszText is NULL, editing was canceled
    if(pDispInfo->item.pszText != NULL)
    {
        //Item:    pDispInfo->item.iItem
        //Subitem: pDispInfo->item.iSubItem
        //... save the text ...
    }

    *pResult = 0;
}

您可以调用 CQuickList::GetLastEndEditKey 来查看编辑结束时按下了哪个键。例如,如果用户按了回车键(VK_RETURN),则可能需要开始编辑列表中的下一项。

当编辑框失去焦点时,它会被关闭。如果您调用 CQuickList::SetEndEditOnLostFocus(false),它将不会在失去焦点时关闭。相反,父窗口将收到 WM_QUICKLIST_EDITINGLOSTFOCUS 消息。(我猜这个功能很奇怪,但我的一个程序需要它,所以我就添加了 :-))。

通过按 F2 或 ENTER 键,或通过用户双击项,将开始编辑。您可以通过调用 SetEditOnEnterSetEditOnF2SetEditOnDblclk 函数来更改此行为。您还可以调用 EditSubItem 来开始编辑项。

XP 中的主题

CQuickList 支持 Windows XP 中的主题。主题用于绘制按钮(复选框、单选按钮等)。如果您不使用此功能,可以忽略。

要启用主题,您应该用指向 CTheme 对象的指针调用 SetThemeManager()。如果您不这样做,按钮将以传统方式绘制。

演示项目中有一个 CTheme 类。我参考了文章 “XP Style CBitmapButton (CHoverBitmapButton)” 来创建它。它的好处是程序可以在 Windows XP 以外的系统上正常运行。不幸的是,我们必须在主窗口中添加一些代码。请参见演示项目,了解如何操作。请确保在 StdAfx.h 中定义 “USEXPTHEMES”。您需要一个较新版本的 Platform SDK 才能编译支持主题的代码。

注意:如果您不使用此功能,可以定义 QUICKLIST_NOXPTHEME 来减小应用程序的大小。

致谢

由于我以前没有做过类似的工作,我很高兴有其他项目对我帮助很大。我查看过甚至复制过其他项目的一些代码。对我来说最有用的项目是 “XListCtrl - A custom-draw list control with subitem formatting”。但我也想感谢

另一个不错的控件是 Virtual Grid Control。它与 CQuickList 非常相似,值得一看。

待办事项

CQuickList 工作良好,但还有一些我想修复/实现的事情:

  • 修复工具提示问题(参见关注点)。
  • 我想在创建 CQuickList 控件时设置 LVS_OWNERDATA 设置。但 CreateOnCreate 都未调用。有什么建议吗?
  • 指定项的高度。
  • 当您在两个列之间的列标题中双击时,列宽应设置为最宽项的宽度。这并不完美,特别是当使用按钮或图像时。
  • 支持拖放。我曾尝试创建一个拖动图像函数(CreateDragImageEx),但完全不起作用。

关注点

CQuickList 主要设计为支持整行选择,但即使不使用它也能正常工作。然而,可能会有一些轻微的绘图问题,所以我的建议是使用整行选择。

我在 Windows XP 中使用带有清单文件的列表时遇到了一些问题。一个问题是当鼠标指针悬停在项上时,“热点”项发生了变化。解决方案是处理 LVN_HOTTRACK 消息。另一个问题是当鼠标指针移过列表时,列表会绘制在编辑框之上,我通过处理 WM_MOUSEMOVE 消息解决了这个问题。

XP 中另一个奇怪的行为是,在使用工具提示时存在一些绘图问题。使用工具提示时,您可能会注意到列表会轻微闪烁。问题出在 OnToolHitTest。此函数调用 ListView_SubItemHitTest,出于某种非常、非常奇怪的原因,这会强制列表进行一些重绘(可能只在第一列)。如果您将鼠标指针移到列标题上,标题会暂时消失,您会看到其下方的项,非常奇怪。我还没有弄清楚为什么会发生这种情况。如果您遇到此问题,最简单的解决方法是不使用工具提示。

历史

  • 2006 年 1 月 22 日 - 版本 1.01。修复了使用 LVS_EX_HEADERDRAGDROP 时的问题。
  • 2004 年 9 月 10 日 - 版本 1.0。解决了 Windows XP 中的“热点”问题。添加了对主题的支持。添加了用户右键单击列标题时发送的消息。
  • 2004 年 8 月 28 日 - 版本 0.9。初始版本。
© . All rights reserved.