xref: /MusicPlayer2/MusicPlayer2/ListCtrlEx.cpp (revision 15db6719b6394c5944e4fa70a85b0947dcb0ae5a)
1 #include "stdafx.h"
2 #include "ListCtrlEx.h"
3 #include "MusicPlayer2.h"
4 
IMPLEMENT_DYNAMIC(CListCtrlEx,CListCtrl)5 IMPLEMENT_DYNAMIC(CListCtrlEx, CListCtrl)
6 
7 CListCtrlEx::CListCtrlEx()
8     : m_theme_color(theApp.m_app_setting_data.theme_color)
9 {
10 
11     m_drag_cursor = AfxGetApp()->LoadCursor(IDC_DRAG_CURSOR);
12 }
13 
14 
~CListCtrlEx()15 CListCtrlEx::~CListCtrlEx()
16 {
17     DestroyCursor(m_drag_cursor);
18 }
19 
20 
21 //void CListCtrlEx::SetColor(const ColorTable & colors)
22 //{
23 //	m_theme_color = colors;
24 //	if (m_hWnd != NULL)
25 //		Invalidate();
26 //}
27 
GetItemSelected(vector<int> & item_selected) const28 void CListCtrlEx::GetItemSelected(vector<int>& item_selected) const
29 {
30     item_selected.clear();
31     POSITION pos = GetFirstSelectedItemPosition();
32     if (pos != NULL)
33     {
34         while (pos)
35         {
36             int nItem = GetNextSelectedItem(pos);
37             item_selected.push_back(nItem);
38         }
39     }
40 }
41 
GetCurSel() const42 int CListCtrlEx::GetCurSel() const
43 {
44     //vector<int> item_selected;
45     //GetItemSelected(item_selected);
46     //if (!item_selected.empty())
47     //	return item_selected[0];
48     //else
49     //	return -1;
50     return GetSelectionMark();
51 }
52 
SetCurSel(int select)53 void CListCtrlEx::SetCurSel(int select)
54 {
55     int size = GetItemCount();
56     for (int i{}; i < size; i++)
57     {
58         if (i == select)
59         {
60             SetItemState(select, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);	//选中行
61             EnsureVisible(select, FALSE);		//使选中行保持可见
62         }
63         else
64         {
65             SetItemState(i, 0, LVIS_SELECTED);      //取消选中其他行
66         }
67     }
68 }
69 
SetCurSel(int first,int last)70 void CListCtrlEx::SetCurSel(int first, int last)
71 {
72     int itemCnt = GetItemCount();
73     if (first >= 0 && last < itemCnt && first <= last)
74     {
75         for (int i = 0; i < itemCnt; i++)
76         {
77             if(i>=first && i<=last)
78                 SetItemState(i, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
79             else
80                 SetItemState(i, 0, LVIS_SELECTED);
81         }
82     }
83 }
84 
SetCurSel(const vector<int> indexes)85 void CListCtrlEx::SetCurSel(const vector<int> indexes)
86 {
87     int itemCnt = GetItemCount();
88     for (int i = 0; i < itemCnt; i++)
89     {
90         auto iter = std::find(indexes.begin(), indexes.end(), i);
91         if (iter != indexes.end())
92             SetItemState(i, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
93         else
94             SetItemState(i, 0, LVIS_SELECTED);
95     }
96 }
97 
SelectAll()98 void CListCtrlEx::SelectAll()
99 {
100     int itemCnt = GetItemCount();
101     for (int i = 0; i < itemCnt; i++)
102     {
103         SetItemState(i, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
104     }
105 }
106 
SelectNone()107 void CListCtrlEx::SelectNone()
108 {
109     int itemCnt = GetItemCount();
110     for (int i = 0; i < itemCnt; i++)
111     {
112         SetItemState(i, 0, LVIS_SELECTED);
113     }
114 }
115 
SelectReverse()116 void CListCtrlEx::SelectReverse()
117 {
118     std::vector<int> selected_vect;
119     GetItemSelected(selected_vect);
120     std::set<int> selected_set;
121     for (auto n : selected_vect)
122         selected_set.insert(n);
123     int size = GetItemCount();
124     for (int i = 0; i < size; i++)
125     {
126         if(selected_set.find(i)==selected_set.end())
127             SetItemState(i, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
128         else
129             SetItemState(i, 0, LVIS_SELECTED);
130     }
131 }
132 
SetRowHeight(int height,int left_space)133 bool CListCtrlEx::SetRowHeight(int height, int left_space)
134 {
135     if (height > 0 && height <= 512)
136     {
137         CImageList imgList;		//为ClistCtrl设置一个图像列表,以设置行高
138         int width = left_space;
139         if (width < 1)
140             width = 1;
141         BOOL rtn = imgList.Create(width, height, ILC_COLOR, 1, 1);
142         if (rtn != FALSE)
143         {
144             m_row_height = height;
145             SetImageList(&imgList, LVSIL_SMALL);
146             return true;
147         }
148     }
149     return false;
150 }
151 
152 
ShowPopupMenu(CMenu * pMenu,int item_index,CWnd * pWnd)153 void CListCtrlEx::ShowPopupMenu(CMenu* pMenu, int item_index, CWnd* pWnd)
154 {
155     CPoint point;			//定义一个用于确定光标位置的位置
156     GetCursorPos(&point);	//获取当前光标的位置,以便使得菜单可以跟随光标
157     if(item_index >= 0)
158     {
159         CRect item_rect;
160         GetItemRect(item_index, item_rect, LVIR_BOUNDS);		//获取选中项目的矩形区域(以列表控件左上角为原点)
161         CRect window_rect;
162         GetWindowRect(window_rect);		//获取列表控件的矩形区域(以屏幕左上角为原点)
163         point.y = window_rect.top + item_rect.bottom;	//设置鼠标要弹出的y坐标为选中项目的下边框位置,防止右键菜单挡住选中的项目
164     }
165     pMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, pWnd); //在指定位置显示弹出菜单
166 }
167 
FillLeftSpaceAfterPaint(bool fill)168 void CListCtrlEx::FillLeftSpaceAfterPaint(bool fill)
169 {
170     m_fill_left_space_after_paint = fill;
171 }
172 
SetMouseWheelEnable(bool enable)173 void CListCtrlEx::SetMouseWheelEnable(bool enable)
174 {
175     m_mouse_wheel_enable = enable;
176 }
177 
SetItemIcon(int item,HICON icon)178 void CListCtrlEx::SetItemIcon(int item, HICON icon)
179 {
180     m_icons[item] = icon;
181     m_fill_left_space_after_paint = false;  //设置了图标后将m_fill_left_space_after_paint置为false,以确保图标部分背景显示正常
182 }
183 
DeleteItem(int nItem)184 bool CListCtrlEx::DeleteItem(int nItem)
185 {
186     bool rtn = (CListCtrl::DeleteItem(nItem) != FALSE);
187 
188     if (rtn)
189     {
190         //删除对应的图标
191         m_icons.erase(nItem);
192 
193         //删除项目后要重新调整删除项目后面所有图标的序号
194         for (int i = nItem + 1; i <= GetItemCount(); i++)
195         {
196             auto iter = m_icons.find(i);
197             if (iter != m_icons.end())
198             {
199                 HICON icon = iter->second;
200                 m_icons.erase(iter);
201                 m_icons[i - 1] = icon;
202             }
203         }
204 
205         Invalidate();       //控件重绘
206     }
207 
208     return rtn;
209 }
210 
SetListData(ListData * pListData)211 void CListCtrlEx::SetListData(ListData* pListData)
212 {
213     if (pListData == nullptr)
214         return;
215     m_pListData = pListData;
216     SetItemCount(pListData->size());
217 }
218 
SetListData(const ListData & list_data)219 void CListCtrlEx::SetListData(const ListData & list_data)
220 {
221     m_pListData = nullptr;
222     int item_num_before = GetItemCount();
223     int item_num_after = list_data.size();
224     //如果当前列表中项目的数量小于原来的,则直接清空原来列表中所有的项目,重新添加
225     if (item_num_after < item_num_before)
226     {
227         DeleteAllItems();
228         item_num_before = 0;
229     }
230     for (int i{}; i < item_num_after; i++)
231     {
232         const RowData& data_row = list_data[i];
233         if (i >= item_num_before)	//如果当前列表中的项目数量大于之前的数量,则需要在不够时插入新的项目
234         {
235             auto iter = data_row.find(0);
236             if (iter != data_row.end())
237                 InsertItem(i, iter->second.c_str());
238             else
239                 InsertItem(i, _T(""));
240         }
241         for (const auto& item : data_row)
242         {
243             SetItemText(i, item.first, item.second.c_str());
244         }
245     }
246 }
247 
GetAllText(const wchar_t * sperator)248 wstring CListCtrlEx::GetAllText(const wchar_t* sperator /*= L"\t"*/)
249 {
250     wstring str_result;
251     int item_count = GetItemCount();
252     int col_count{};
253     auto pHeader = GetHeaderCtrl();
254     if (pHeader != nullptr)
255         col_count = pHeader->GetItemCount();
256     for (int i = 0; i < item_count; i++)
257     {
258         for (int j = 0; j < col_count; j++)
259         {
260             str_result += GetItemText(i, j).GetString();
261             if (j < col_count - 1)
262                 str_result += sperator;
263         }
264         str_result += L"\r\n";
265     }
266     return str_result;
267 }
268 
BEGIN_MESSAGE_MAP(CListCtrlEx,CListCtrl)269 BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
270     ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CListCtrlEx::OnNMCustomdraw)
271     ON_WM_LBUTTONDOWN()
272     ON_WM_RBUTTONDOWN()
273     ON_WM_SETFOCUS()
274     ON_NOTIFY_REFLECT(LVN_BEGINDRAG, &CListCtrlEx::OnLvnBegindrag)
275     ON_WM_LBUTTONUP()
276     ON_WM_SETCURSOR()
277     ON_WM_ERASEBKGND()
278     ON_NOTIFY_REFLECT(LVN_GETDISPINFO, &CListCtrlEx::OnLvnGetdispinfo)
279     ON_MESSAGE(WM_TABLET_QUERYSYSTEMGESTURESTATUS, &CListCtrlEx::OnTabletQuerysystemgesturestatus)
280 END_MESSAGE_MAP()
281 
282 
283 void CListCtrlEx::OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult)
284 {
285     DWORD style = GetExtendedStyle();
286 
287 
288     *pResult = CDRF_DODEFAULT;
289     LPNMLVCUSTOMDRAW lplvdr = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
290     NMCUSTOMDRAW& nmcd = lplvdr->nmcd;
291     static bool this_item_select = false;
292     switch (lplvdr->nmcd.dwDrawStage)	//判断状态
293     {
294     case CDDS_PREPAINT:
295         *pResult = CDRF_NOTIFYITEMDRAW;
296         break;
297     case CDDS_ITEMPREPAINT:			//如果为画ITEM之前就要进行颜色的改变
298         this_item_select = false;
299         if (IsWindowEnabled())
300         {
301             //当选中行又是高亮行时设置颜色
302             if (GetItemState(nmcd.dwItemSpec, LVIS_SELECTED) == LVIS_SELECTED && nmcd.dwItemSpec == m_highlight_item)
303             {
304                 this_item_select = true;
305                 //SetItemState(nmcd.dwItemSpec, 0, LVIS_SELECTED);
306                 lplvdr->clrText = m_theme_color.light3;
307                 lplvdr->clrTextBk = m_theme_color.dark1;
308             }
309             //设置选中行的颜色
310             else if (GetItemState(nmcd.dwItemSpec, LVIS_SELECTED) == LVIS_SELECTED)
311             {
312                 this_item_select = true;
313                 //注:当使用虚拟列表时,如果取消下面一行的注释,会导致当列表选中行处理可见状态时,窗口刷新不正常,甚至主窗口OnTimer也无法响应,原因暂时不明
314                 //SetItemState(nmcd.dwItemSpec, 0, LVIS_SELECTED);
315                 lplvdr->clrText = m_theme_color.dark3;
316                 lplvdr->clrTextBk = m_theme_color.light2;
317             }
318             //设置高亮行的颜色
319             else if (nmcd.dwItemSpec == m_highlight_item)
320             {
321                 lplvdr->clrText = m_theme_color.dark2;
322                 //lplvdr->clrText = 0;
323                 lplvdr->clrTextBk = m_theme_color.light3;
324             }
325             //设置偶数行的颜色
326             else if (nmcd.dwItemSpec % 2 == 0)
327             {
328                 lplvdr->clrText = CColorConvert::m_gray_color.dark3;
329                 lplvdr->clrTextBk = CColorConvert::m_gray_color.light3;
330             }
331             //设置奇数行的颜色
332             else
333             {
334                 lplvdr->clrText = CColorConvert::m_gray_color.dark3;
335                 lplvdr->clrTextBk = CColorConvert::m_gray_color.light4;
336             }
337 
338             //用背景色填充单元格,以去掉每行前面的空白
339             if(!m_fill_left_space_after_paint)
340             {
341                 CRect rect = nmcd.rc;
342                 CDC* pDC = CDC::FromHandle(nmcd.hdc);		//获取绘图DC
343                 pDC->FillSolidRect(rect, lplvdr->clrTextBk);
344             }
345         }
346         else		//当控件被禁用时,显示文本设为灰色
347         {
348             lplvdr->clrText = GRAY(140);
349             lplvdr->clrTextBk = GRAY(240);
350         }
351         *pResult = CDRF_NOTIFYPOSTPAINT;
352         break;
353     case CDDS_ITEMPOSTPAINT:
354         if (this_item_select)
355             SetItemState(nmcd.dwItemSpec, 0xFF, LVIS_SELECTED);
356         //用背景色填充单元格左侧的空白区域
357         if(m_fill_left_space_after_paint)
358         {
359             CRect rect = nmcd.rc;
360             rect.right = rect.left + 5;
361             CDC* pDC = CDC::FromHandle(nmcd.hdc);		//获取绘图DC
362             pDC->FillSolidRect(rect, lplvdr->clrTextBk);
363         }
364         //绘制图标
365         CRect icon_rect = nmcd.rc;
366         if (!icon_rect.IsRectEmpty())
367         {
368             auto iter = m_icons.find(nmcd.dwItemSpec);
369             if (iter != m_icons.end())
370             {
371                 icon_rect.left += 5;
372                 const int icon_size{ theApp.DPI(16) };
373                 icon_rect.right = icon_rect.left + icon_size;
374                 icon_rect.top = icon_rect.top + (icon_rect.Height() - icon_size) / 2;
375                 icon_rect.bottom = icon_rect.top + icon_size;
376                 CDC* pDC = CDC::FromHandle(nmcd.hdc);
377                 CDrawCommon drawer;
378                 drawer.Create(pDC);
379                 drawer.DrawIcon(iter->second, icon_rect);
380             }
381         }
382 
383         //*pResult = CDRF_DODEFAULT;
384         break;
385     }
386 }
387 
388 
PreSubclassWindow()389 void CListCtrlEx::PreSubclassWindow()
390 {
391     // TODO: 在此添加专用代码和/或调用基类
392     CListCtrl::PreSubclassWindow();
393 
394     DWORD style = GetExtendedStyle();
395     SetExtendedStyle(style | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP);      //设置扩展样式(双缓冲绘图、整行选中,鼠标提示)
396 
397     SetBkColor(m_background_color);
398     //SetHightItem(-1);
399     SetRowHeight(theApp.DPI(22));
400 }
401 
402 
OnLButtonDown(UINT nFlags,CPoint point)403 void CListCtrlEx::OnLButtonDown(UINT nFlags, CPoint point)
404 {
405     // TODO: 在此添加消息处理程序代码和/或调用默认值
406     this->SetFocus();
407     m_dragging = false;
408     CListCtrl::OnLButtonDown(nFlags, point);
409 }
410 
411 
OnRButtonDown(UINT nFlags,CPoint point)412 void CListCtrlEx::OnRButtonDown(UINT nFlags, CPoint point)
413 {
414     // TODO: 在此添加消息处理程序代码和/或调用默认值
415     this->SetFocus();
416     m_dragging = false;
417     CListCtrl::OnRButtonDown(nFlags, point);
418 }
419 
420 
PreTranslateMessage(MSG * pMsg)421 BOOL CListCtrlEx::PreTranslateMessage(MSG* pMsg)
422 {
423     // TODO: 在此添加专用代码和/或调用基类
424 
425     //if (pMsg->message == WM_KEYDOWN || pMsg->message == WM_CHAR)		//屏蔽列表控件的键盘消息,防止每次按下一个键时列表选中行会出现讨厌的、难看的虚线框
426     //	return TRUE;
427 
428     // 按Ctrl+A全选,向父窗口发送选中项更新消息
429     if(m_enable_ctrl_a)
430     {
431         if ((GetKeyState(VK_CONTROL) & 0x80) && (pMsg->wParam == 'A'))
432         {
433             SelectAll();
434             CWnd* pParent{ GetParent() };
435             if (pParent != nullptr)
436             {
437                 pParent->SendMessage(WM_COMMAND, ID_PLAYLIST_SELECT_CHANGE);
438             }
439             return TRUE;
440         }
441     }
442 
443     if (pMsg->message == WM_MOUSEWHEEL && !m_mouse_wheel_enable)
444     {
445         CRect rect;
446         GetWindowRect(rect);
447         //根据行高*行数小于控件高度来判断是否显示垂直滚动条
448         if (m_row_height > 0 && (m_row_height + 1) * GetItemCount() < rect.Height())
449         {
450             //如果不显示滚动条,则将鼠标滚轮消息发送给父窗口
451             CWnd* pParent = GetParent();
452             pParent->SendMessage(WM_MOUSEWHEEL, pMsg->wParam, pMsg->lParam);
453             return TRUE;
454         }
455     }
456 
457     return CListCtrl::PreTranslateMessage(pMsg);
458 }
459 
460 
OnSetFocus(CWnd * pOldWnd)461 void CListCtrlEx::OnSetFocus(CWnd* pOldWnd)
462 {
463     CListCtrl::OnSetFocus(pOldWnd);
464 
465     // TODO: 在此处添加消息处理程序代码
466 
467     SendMessage(WM_KILLFOCUS);				//禁止列表控件获取焦点,防止选中行会出现难看的虚线框
468 }
469 
470 
OnLvnBegindrag(NMHDR * pNMHDR,LRESULT * pResult)471 void CListCtrlEx::OnLvnBegindrag(NMHDR *pNMHDR, LRESULT *pResult)
472 {
473     LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
474     // TODO: 在此添加控件通知处理程序代码
475     if (m_drag_enable)
476     {
477         m_dragging = true;
478     }
479 
480     *pResult = 0;
481 }
482 
483 
OnLButtonUp(UINT nFlags,CPoint point)484 void CListCtrlEx::OnLButtonUp(UINT nFlags, CPoint point)
485 {
486     // TODO: 在此添加消息处理程序代码和/或调用默认值
487     if (m_dragging)
488     {
489         CPoint pt(point);
490         int drop_index = this->HitTest(pt);     //鼠标松开时的项目序号
491         CWnd* pParent{ GetParent() };
492         if (pParent != nullptr)
493         {
494             pParent->SendMessage(WM_LIST_ITEM_DRAGGED, drop_index, 0);       //结束拖放时向父窗口发送消息,传递拖放结束位置索引
495         }
496         m_dragging = false;
497         SendMessage(WM_SETCURSOR);      //鼠标松开时刷新光标
498     }
499 
500     CListCtrl::OnLButtonUp(nFlags, point);
501 }
502 
503 
OnSetCursor(CWnd * pWnd,UINT nHitTest,UINT message)504 BOOL CListCtrlEx::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
505 {
506     // TODO: 在此添加消息处理程序代码和/或调用默认值
507     if (m_dragging)
508     {
509         ::SetCursor(m_drag_cursor);
510         return TRUE;
511     }
512 
513     return CListCtrl::OnSetCursor(pWnd, nHitTest, message);
514 }
515 
516 
OnEraseBkgnd(CDC * pDC)517 BOOL CListCtrlEx::OnEraseBkgnd(CDC* pDC)
518 {
519     // TODO: 在此添加消息处理程序代码和/或调用默认值
520 
521     //return CListCtrl::OnEraseBkgnd(pDC);
522     return TRUE;
523 }
524 
525 
OnLvnGetdispinfo(NMHDR * pNMHDR,LRESULT * pResult)526 void CListCtrlEx::OnLvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult)
527 {
528     NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
529     // TODO: 在此添加控件通知处理程序代码
530     if (pDispInfo->hdr.hwndFrom == m_hWnd && m_pListData != nullptr)
531     {
532         if (pDispInfo->item.iItem >= 0 && pDispInfo->item.iItem < static_cast<int>(m_pListData->size()))
533         {
534             auto& row_data{ m_pListData->at(pDispInfo->item.iItem) };
535             auto iter = row_data.find(pDispInfo->item.iSubItem);
536             if (iter != row_data.end())
537                 CCommon::WStringCopy(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, iter->second.c_str());
538         }
539     }
540     *pResult = 0;
541 }
542 
543 
OnTabletQuerysystemgesturestatus(WPARAM wParam,LPARAM lParam)544 afx_msg LRESULT CListCtrlEx::OnTabletQuerysystemgesturestatus(WPARAM wParam, LPARAM lParam)
545 {
546     return 0;
547 }
548