MFC的使用总结之八——CGridListCtrlEx鼠标响应事件
写在前面
MFC是一种用c++设计交互界面的的开发工具,在二三十年前比较流行,但目前比较少用了。所以现在使用MFC进行开发,相关资料的查找也比较困难。最近做了一个与MFC有关的项目,其中用到的相关知识总结于此。在使用工具时,最好的资料其实就是那个库本身,查看库内的代码总会有意想不到的收获。本人用的是VS2017专业版里面的MFC。本章时在总结七的基础上修改的。文末附代码地址。
点击表格触发事件
在主对话框中选中List Control鼠标右键选中类向导
由于List Control绑定的类是导入的CGridListCtrlEx,所以类名选择CGridListCtrlEx,选中左边的消息,可以看到CGridListCtrlEx的类里面其实已经存在鼠标响应事件了。
这里我们就选择鼠标左键单击事件,然后选择编辑代码,可以看到对应的响应程序
void CGridListCtrlEx::OnLButtonDown(UINT nFlags, CPoint point)
{
// Find out what subitem was clicked
int nRow, nCol;
CellHitTest(point, nRow, nCol);
// If not left-clicking on an actual row, then don't update focus cell
if (nRow==-1)
{
CListCtrl::OnLButtonDown(nFlags, point);
return;
}
if( GetFocus() != this )
SetFocus(); // Force focus to finish editing
int startEdit = OnClickEditStart(nRow, nCol, point, false);
if (startEdit!=2)
{
// Update the focused cell before calling CListCtrl::OnLButtonDown()
// as it might cause a row-repaint
SetFocusCell(nCol);
CListCtrl::OnLButtonDown(nFlags, point);
// LVN_BEGINDRAG message can be fired when calling parent OnLButtonDown(),
// this should not result in a start edit operation
if (GetFocusCell() != nCol)
{
SetFocusCell(nCol);
startEdit = 0;
}
// CListCtrl::OnLButtonDown() doesn't change row if clicking on subitem without fullrow selection
if (!(GetExtendedStyle() & LVS_EX_FULLROWSELECT))
{
if (nRow!=GetFocusRow())
{
SetFocusRow(nRow);
if (!(GetKeyState(VK_CONTROL) < 0) && !(GetKeyState(VK_SHIFT) < 0))
{
SelectRow(-1, false);
SelectRow(nRow, true);
}
}
}
// CListCtrl::OnLButtonDown() doesn't always cause a row-repaint
// call our own method to ensure the row is repainted
SetFocusCell(nCol, true);
}
if (startEdit!=0)
{
// This will steal the double-click event when double-clicking a cell that already have focus,
// but we cannot guess after the first click, whether the user will click a second time.
// A timer could be used but it would cause slugish behavior (http://blogs.msdn.com/oldnewthing/archive/2004/10/15/242761.aspx)
EditCell(nRow, nCol, point);
}
}
程序中的nRow, nCol就是鼠标点击表格的坐标,向其中添加测试代码在函数最后面
CString str;
str.Format(_T("这是第%d行第%d列"), nRow, nCol);
AfxMessageBox(str);
运行程序点击copenhagen就可以得到(注意行是从0开始的,列是从1开始的)
即使将表格进行拖动,显示仍旧不变
所以,我们只要将nRow, nCol;导入到需要的地方,就可以选择项目,触发各种响应。弱国每一个条目对应一个视频,这样就可以点击一个条目,程序自动播放这个条目所对应的视频。
表格中添加按钮,点击按钮触发事件
还一种是在表格中加入按钮,点击按钮来触发相应的事件。
由于CGridListCtrlEx中没有按钮的部分,需要对其进行扩展。
先新建MButton的类。
其中MButton.h
#pragma once
// CMButton
#define WM_BN_CLICK WM_USER + 100
#define WM_Menu_CLICK WM_USER + 101
#define USE_TOPINDEX_BUTTON
class CMButton : public CButton
{
DECLARE_DYNAMIC(CMButton)
public:
CMButton();
CMButton( int nItem, int nSubItem, CRect rect, HWND hParent );
virtual ~CMButton();
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClicked();
public:
int m_inItem;
int m_inSubItem;
CRect m_rect;
HWND m_hParent;
BOOL bEnable;
// afx_msg BOOL OnEraseBkgnd(CDC* pDC);
};
MButton.cpp
// MButton.cpp : 实现文件
//
#include "stdafx.h"
//#include "NewListCtrl.h"
#include "MButton.h"
// CMButton
IMPLEMENT_DYNAMIC(CMButton, CButton)
CMButton::CMButton()
{
m_inItem = 0;
m_inSubItem = 0;
ZeroMemory(m_rect, sizeof(CRect)) ;
m_hParent = NULL;
bEnable = FALSE;
}
CMButton::CMButton( int nItem, int nSubItem, CRect rect, HWND hParent )
{
m_inItem = nItem;
m_inSubItem = nSubItem;
m_rect = rect;
m_hParent = hParent;
bEnable = TRUE;
}
CMButton::~CMButton()
{
}
BEGIN_MESSAGE_MAP(CMButton, CButton)
ON_CONTROL_REFLECT(BN_CLICKED, &CMButton::OnBnClicked)
//ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
// CMButton 消息处理程序
void CMButton::OnBnClicked()
{
// TODO: 在此添加控件通知处理程序代码
// TODO: 在此添加控件通知处理程序代码
::SendMessage(AfxGetMainWnd()->m_hWnd, WM_BN_CLICK, m_inItem, m_inSubItem ); //-m_hParent
}
//BOOL CMButton::OnEraseBkgnd(CDC* pDC)
//{
// // TODO: 在此添加消息处理程序代码和/或调用默认值
//
// return CButton::OnEraseBkgnd(pDC);
//}
加入到CGridListCtrlEx目录中,并在项目中添加,更改CGridListCtrlEx.h和CGridListCtrlEx.cpp,文档太长,放文后。之后就是在GridlistDlg.h添加消息响应的定义
public:
afx_msg LRESULT onBnCLick(WPARAM wParam, LPARAM lParam);
在GridlistDlg.cpp添加映射
BEGIN_MESSAGE_MAP(CGridlistDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(WM_BN_CLICK, onBnCLick)
END_MESSAGE_MAP()
添加函数
LRESULT CGridlistDlg::onBnCLick(WPARAM wParam, LPARAM lParam)
{
int nRow = (int)wParam;
int nCol = (int)lParam;
#ifdef USE_TOPINDEX_BUTTON
int iTopIndex = m_ListCtrl.GetTopIndex();
TRACE(L"move:before:iTopIndex = %d, nRow = %d\n", iTopIndex, nRow);
nRow = iTopIndex + nRow;
#endif
CString str;
str.Format(_T("nRow =%d, nCol = %d"), nRow, nCol);
AfxMessageBox(str);
return 0;
}
修改BOOL CGridlistDlg::OnInitDialog()中的代码
// Create and attach image list
m_ImageList.Create(16, 16, ILC_COLOR16 | ILC_MASK, 1, 0);
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON1));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON2));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON3));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON4));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON5));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON6));
int nStateImageIdx = CGridColumnTraitDateTime::AppendStateImages(m_ListCtrl, m_ImageList); // Add checkboxes
m_ListCtrl.SetImageList(&m_ImageList, LVSIL_SMALL);
// Give better margin to editors
m_ListCtrl.SetCellMargin(2);
CGridRowTraitXP* pRowTrait = new CGridRowTraitXP;
m_ListCtrl.SetDefaultRowTrait(pRowTrait);
m_ListCtrl.EnableVisualStyles(true);
// Create Columns
m_ListCtrl.InsertHiddenLabelColumn(); // Requires one never uses column 0
for (int col = 0; col < m_DataModel.GetColCount(); ++col)
{
const CString& title = m_DataModel.GetColTitle(col);
CGridColumnTrait* pTrait = NULL;
if (col == 0) // Country
{
CGridColumnTraitCombo* pComboTrait = new CGridColumnTraitCombo;
const vector<CString>& countries = m_DataModel.GetCountries();
for (size_t i = 0; i < countries.size(); ++i)
pComboTrait->AddItem((DWORD_PTR)i, countries[i]);
pTrait = pComboTrait;
}
if (col == 1) // City
{
pTrait = new CGridColumnTraitEdit;
}
if (col == 2) // Year won
{
CGridColumnTraitDateTime* pDateTimeTrait = new CGridColumnTraitDateTime;
pDateTimeTrait->AddImageIndex(nStateImageIdx, _T(""), false); // Unchecked (and not editable)
pDateTimeTrait->AddImageIndex(nStateImageIdx + 1, COleDateTime(1970, 1, 1, 0, 0, 0).Format(), true); // Checked (and editable)
pDateTimeTrait->SetToggleSelection(true);
pTrait = pDateTimeTrait;
}
if (col == 3) // Year won
{
CGridColumnTraitHyperLink* pHyperLinkTrait = new CGridColumnTraitHyperLink;
pHyperLinkTrait->SetShellFilePrefix(_T("http://en.wikipedia.org/wiki/UEFA_Euro_"));
pTrait = pHyperLinkTrait;
}
m_ListCtrl.InsertColumnTrait(col + 1, title, LVCFMT_LEFT, 100, col, pTrait);
}
//添加按钮
const CString& title = _T("按钮");
CGridColumnTrait* pTrait = NULL;
m_ListCtrl.InsertColumnTrait(5, title, LVCFMT_LEFT, 100, 4, pTrait);
// Insert data into list-control by copying from datamodel
int nItem = 0;
for (size_t rowId = 0; rowId < m_DataModel.GetRowIds(); ++rowId)
{
nItem = m_ListCtrl.InsertItem(++nItem, m_DataModel.GetCellText(rowId, 0));
m_ListCtrl.SetItemData(nItem, rowId);
for (int col = 0; col < m_DataModel.GetColCount(); ++col)
{
int nCellCol = col + 1; // +1 because of hidden column
const CString& strCellText = m_DataModel.GetCellText(rowId, col);
m_ListCtrl.SetItemText(nItem, nCellCol, strCellText);
if (nCellCol == 3)
{
if (strCellText == _T(""))
m_ListCtrl.SetCellImage(nItem, nCellCol, nStateImageIdx); // unchecked
else
m_ListCtrl.SetCellImage(nItem, nCellCol, nStateImageIdx + 1); // checked
}
}
m_ListCtrl.createItemButton(rowId, 5, GetParent()->GetSafeHwnd()); //添加按钮
m_ListCtrl.SetCellImage(nItem, 1, nItem); // Assign flag-images
}
CViewConfigSectionWinApp* pColumnProfile = new CViewConfigSectionWinApp(_T("Sample List"));
pColumnProfile->AddProfile(_T("Default"));
pColumnProfile->AddProfile(_T("Special"));
m_ListCtrl.SetupColumnConfig(pColumnProfile);
运行结果如下
按钮运行的结果与点击表格运行结果其实相差不多。
其中按键上面的字符是可以自己设计的,设计的部分位于CGridListCtrlEx.cpp中
void CGridListCtrlEx::createItemButton(int nItem, int nSubItem, HWND hMain)
{
CRect rect;
CPoint pt;
pt.x = 5;
pt.y = 5;
if (!EnsureVisible(nItem, TRUE))
return;
GetSubItemRect(nItem, nSubItem, LVIR_BOUNDS, rect);
DWORD dwStyle = WS_CHILD | WS_VISIBLE;
CMButton *pButton = new CMButton(nItem, nSubItem, rect, hMain);
m_uID++;
//调整矩形区域大小
rect.TopLeft() += pt;
rect.BottomRight() -= pt;
pButton->Create(_T(">>>"), dwStyle, rect, this, m_uID);//可更改按键上符号
m_mButton.insert(make_pair(nItem, pButton));
return;
}
最终程序
GridlistDlg.h
// GridlistDlg.h: 头文件
//
#pragma once
#include “afxcmn.h”
#include “…\CGridListCtrlEx\CGridListCtrlGroups.h”
#include “CListCtrl_DataModel.h”
// CGridlistDlg 对话框
class CGridlistDlg : public CDialogEx
{
// 构造
public:
CGridlistDlg(CWnd* pParent = nullptr); // 标准构造函数
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_GRIDLIST_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
private:
CGridListCtrlGroups m_ListCtrl;
CListCtrl_DataModel m_DataModel;
CImageList m_ImageList;
CGridlistDlg(const CGridlistDlg&);
CGridlistDlg& operator=(const CGridlistDlg&);
public:
afx_msg LRESULT onBnCLick(WPARAM wParam, LPARAM lParam);
};
GridlistDlg.cpp
// GridlistDlg.cpp: 实现文件
//
#include "stdafx.h"
#include "Gridlist.h"
#include "GridlistDlg.h"
#include "afxdialogex.h"
#include "..\CGridListCtrlEx\CGridColumnTraitDateTime.h"
#include "..\CGridListCtrlEx\CGridColumnTraitEdit.h"
#include "..\CGridListCtrlEx\CGridColumnTraitCombo.h"
#include "..\CGridListCtrlEx\CGridColumnTraitHyperLink.h"
#include "..\CGridListCtrlEx\CGridRowTraitXP.h"
#include "..\CGridListCtrlEx\ViewConfigSection.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum {
IDD = IDD_ABOUTBOX };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CGridlistDlg 对话框
CGridlistDlg::CGridlistDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_GRIDLIST_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CGridlistDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST1, m_ListCtrl);
}
BEGIN_MESSAGE_MAP(CGridlistDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(WM_BN_CLICK, onBnCLick)
END_MESSAGE_MAP()
// CGridlistDlg 消息处理程序
BOOL CGridlistDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
// Create and attach image list
m_ImageList.Create(16, 16, ILC_COLOR16 | ILC_MASK, 1, 0);
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON1));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON2));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON3));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON4));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON5));
m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON6));
int nStateImageIdx = CGridColumnTraitDateTime::AppendStateImages(m_ListCtrl, m_ImageList); // Add checkboxes
m_ListCtrl.SetImageList(&m_ImageList, LVSIL_SMALL);
// Give better margin to editors
m_ListCtrl.SetCellMargin(2);
CGridRowTraitXP* pRowTrait = new CGridRowTraitXP;
m_ListCtrl.SetDefaultRowTrait(pRowTrait);
m_ListCtrl.EnableVisualStyles(true);
// Create Columns
m_ListCtrl.InsertHiddenLabelColumn(); // Requires one never uses column 0
for (int col = 0; col < m_DataModel.GetColCount(); ++col)
{
const CString& title = m_DataModel.GetColTitle(col);
CGridColumnTrait* pTrait = NULL;
if (col == 0) // Country
{
CGridColumnTraitCombo* pComboTrait = new CGridColumnTraitCombo;
const vector<CString>& countries = m_DataModel.GetCountries();
for (size_t i = 0; i < countries.size(); ++i)
pComboTrait->AddItem((DWORD_PTR)i, countries[i]);
pTrait = pComboTrait;
}
if (col == 1) // City
{
pTrait = new CGridColumnTraitEdit;
}
if (col == 2) // Year won
{
CGridColumnTraitDateTime* pDateTimeTrait = new CGridColumnTraitDateTime;
pDateTimeTrait->AddImageIndex(nStateImageIdx, _T(""), false); // Unchecked (and not editable)
pDateTimeTrait->AddImageIndex(nStateImageIdx + 1, COleDateTime(1970, 1, 1, 0, 0, 0).Format(), true); // Checked (and editable)
pDateTimeTrait->SetToggleSelection(true);
pTrait = pDateTimeTrait;
}
if (col == 3) // Year won
{
CGridColumnTraitHyperLink* pHyperLinkTrait = new CGridColumnTraitHyperLink;
pHyperLinkTrait->SetShellFilePrefix(_T("http://en.wikipedia.org/wiki/UEFA_Euro_"));
pTrait = pHyperLinkTrait;
}
m_ListCtrl.InsertColumnTrait(col + 1, title, LVCFMT_LEFT, 100, col, pTrait);
}
const CString& title = _T("按钮");
CGridColumnTrait* pTrait = NULL;
m_ListCtrl.InsertColumnTrait(5, title, LVCFMT_LEFT, 100, 4, pTrait);
// Insert data into list-control by copying from datamodel
int nItem = 0;
for (size_t rowId = 0; rowId < m_DataModel.GetRowIds(); ++rowId)
{
nItem = m_ListCtrl.InsertItem(++nItem, m_DataModel.GetCellText(rowId, 0));
m_ListCtrl.SetItemData(nItem, rowId);
for (int col = 0; col < m_DataModel.GetColCount(); ++col)
{
int nCellCol = col + 1; // +1 because of hidden column
const CString& strCellText = m_DataModel.GetCellText(rowId, col);
m_ListCtrl.SetItemText(nItem, nCellCol, strCellText);
if (nCellCol == 3)
{
if (strCellText == _T(""))
m_ListCtrl.SetCellImage(nItem, nCellCol, nStateImageIdx); // unchecked
else
m_ListCtrl.SetCellImage(nItem, nCellCol, nStateImageIdx + 1); // checked
}
}
m_ListCtrl.createItemButton(rowId, 5, GetParent(