-
Notifications
You must be signed in to change notification settings - Fork 35
/
listview.h
208 lines (178 loc) · 7.63 KB
/
listview.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/**
* Part of WinLamb - Win32 API Lambda Library
* https://github.com/rodrigocfd/winlamb
* Copyright 2017-present Rodrigo Cesar de Freitas Dias
* This library is released under the MIT License
*/
#pragma once
#include "internals/base_focus_pubm.h"
#include "internals/base_native_ctrl_pubm.h"
#include "internals/listview_column_collection.h"
#include "internals/listview_item_collection.h"
#include "internals/listview_styler.h"
#include "internals/member_image_list.h"
#include "menu.h"
#include "subclass.h"
#include "wnd.h"
namespace wl {
// Wrapper to listview control from Common Controls library.
class listview final :
public wnd,
public _wli::base_native_ctrl_pubm<listview>,
public _wli::base_focus_pubm<listview>
{
public:
using item = _wli::listview_item;
using item_collection = _wli::listview_item_collection;
using column_collection = _wli::listview_column_collection;
enum class view : WORD {
DETAILS = LV_VIEW_DETAILS,
ICON = LV_VIEW_ICON,
LIST = LV_VIEW_LIST,
SMALLICON = LV_VIEW_SMALLICON,
TILE = LV_VIEW_TILE
};
private:
HWND _hWnd = nullptr;
_wli::base_native_ctrl _baseNativeCtrl{_hWnd};
subclass _subclass;
menu _contextMenu;
public:
// Wraps window style changes done by Get/SetWindowLongPtr.
_wli::listview_styler<listview> style{this};
// Access to the items of the listview.
item_collection items{this->_hWnd};
// Access to the columns of the listview.
column_collection columns{this->_hWnd};
// Access to the 16x16 image list of the listview.
_wli::member_image_list<listview> imageList16{this, 16};
// Access to the 32x32 image list of the listview.
_wli::member_image_list<listview> imageList32{this, 32};
~listview() {
this->_contextMenu.destroy();
}
listview() :
wnd(_hWnd), base_native_ctrl_pubm(_baseNativeCtrl), base_focus_pubm(_hWnd)
{
this->imageList16.on_create([this]() noexcept -> void {
ListView_SetImageList(this->_hWnd, this->imageList16.himagelist(), LVSIL_SMALL);
});
this->imageList32.on_create([this]() noexcept -> void {
ListView_SetImageList(this->_hWnd, this->imageList32.himagelist(), LVSIL_NORMAL);
});
this->_subclass.on_message(WM_GETDLGCODE, [this](wm::getdlgcode p) noexcept -> LRESULT {
if (!p.is_query() && p.vkey_code() == 'A' && p.has_ctrl()) { // Ctrl+A to select all items
ListView_SetItemState(this->_hWnd, -1, LVIS_SELECTED, LVIS_SELECTED);
return DLGC_WANTCHARS;
} else if (!p.is_query() && p.vkey_code() == VK_RETURN) { // send Enter key to parent
NMLVKEYDOWN nmlvkd = {
{this->_hWnd, static_cast<WORD>(this->ctrl_id()), LVN_KEYDOWN},
VK_RETURN, 0
};
SendMessageW(GetAncestor(this->_hWnd, GA_PARENT),
WM_NOTIFY, reinterpret_cast<WPARAM>(this->_hWnd),
reinterpret_cast<LPARAM>(&nmlvkd));
return DLGC_WANTALLKEYS;
} else if (!p.is_query() && p.vkey_code() == VK_APPS) { // context menu keyboard key
this->_show_context_menu(false, p.has_ctrl(), p.has_shift());
}
return DefSubclassProc(this->_hWnd, p.message, p.wParam, p.lParam);
});
this->_subclass.on_message(WM_RBUTTONDOWN, [this](wm::rbuttondown p) noexcept -> LRESULT {
this->_show_context_menu(true, p.has_ctrl(), p.has_shift());
return 0;
});
}
listview(listview&&) = default;
listview& operator=(listview&&) = default; // movable only
listview& create(HWND hParent, int ctrlId, POINT pos, SIZE size, DWORD styles, DWORD exStyles) {
this->_baseNativeCtrl.create(hParent, ctrlId, nullptr, pos, size, WC_LISTVIEWW, styles, exStyles);
return *this;
}
listview& create(const wnd* parent, int ctrlId, POINT pos, SIZE size, DWORD styles, DWORD exStyles) {
return this->create(parent->hwnd(), ctrlId, pos, size, styles, exStyles);
}
// Ties this class instance to an existing native control.
listview& assign(HWND hCtrl) {
this->base_native_ctrl_pubm::assign(hCtrl); // hides base method
return this->_install_subclass();
}
// Ties this class instance to an existing native control.
listview& assign(HWND hParent, int ctrlId) {
this->base_native_ctrl_pubm::assign(hParent, ctrlId); // hides base method
return this->_install_subclass();
}
// Ties this class instance to an existing native control.
listview& assign(const wnd* parent, int ctrlId) {
this->base_native_ctrl_pubm::assign(parent, ctrlId); // hides base method
return this->_install_subclass();
}
// Assigns a context menu from RC file to this listview.
listview& set_context_menu(int contextMenuId) {
if (this->_contextMenu.hmenu()) {
throw std::logic_error("Trying to set listview context menu twice.");
}
this->_contextMenu.load_from_resource_submenu(contextMenuId, 0,
GetParent(this->_hWnd));
return *this;
}
// Sends a WM_SETREDRAW message to allow changes in that window to be redrawn
// or to prevent changes in that window from being redrawn.
listview& set_redraw(bool doRedraw) noexcept {
SendMessageW(this->_hWnd, WM_SETREDRAW,
static_cast<WPARAM>(static_cast<BOOL>(doRedraw)), 0);
return *this;
}
listview& set_view(view viewType) noexcept {
ListView_SetView(this->_hWnd, static_cast<DWORD>(viewType));
return *this;
}
view get_view() const noexcept {
return static_cast<view>(ListView_GetView(this->_hWnd));
}
private:
listview& _install_subclass() {
this->_subclass.install_subclass(*this);
return *this;
}
int _show_context_menu(bool followCursor, bool hasCtrl, bool hasShift) noexcept {
if (!this->_contextMenu.hmenu()) return -1; // no context menu assigned
POINT coords{};
int itemBelowCursor = -1;
if (followCursor) { // usually fired with a right-click
LVHITTESTINFO lvhti{};
GetCursorPos(&lvhti.pt); // relative to screen
ScreenToClient(this->_hWnd, &lvhti.pt); // now relative to listview
ListView_HitTest(this->_hWnd, &lvhti); // item below cursor, if any
coords = lvhti.pt;
itemBelowCursor = lvhti.iItem; // -1 if none
if (itemBelowCursor != -1) { // an item was right-clicked
if (!hasCtrl && !hasShift) {
if ((ListView_GetItemState(this->_hWnd, itemBelowCursor, LVIS_SELECTED) & LVIS_SELECTED) == 0) {
// If right-clicked item isn't currently selected, unselect all and select just it.
ListView_SetItemState(this->_hWnd, -1, 0, LVIS_SELECTED);
ListView_SetItemState(this->_hWnd, itemBelowCursor, LVIS_SELECTED, LVIS_SELECTED);
}
ListView_SetItemState(this->_hWnd, itemBelowCursor, LVIS_FOCUSED, LVIS_FOCUSED); // focus clicked
}
} else if (!hasCtrl && !hasShift) {
ListView_SetItemState(this->_hWnd, -1, 0, LVIS_SELECTED); // unselect all
}
SetFocus(this->_hWnd); // because a right-click won't set the focus by default
} else { // usually fired with the context menu keyboard key
int itemFocused = ListView_GetNextItem(this->_hWnd, -1, LVNI_FOCUSED);
if (itemFocused != -1 && ListView_IsItemVisible(this->_hWnd, itemFocused)) { // item focused and visible
RECT rcItem{};
ListView_GetItemRect(this->_hWnd, itemFocused, &rcItem, LVIR_BOUNDS); // relative to listview
coords = {rcItem.left + 16, rcItem.top + (rcItem.bottom - rcItem.top) / 2};
} else { // no focused and visible item
coords = {6, 10};
}
}
// The popup menu is created with hDlg as parent, so the menu messages go to it.
// The lvhti coordinates are relative to listview, and will be mapped into screen-relative.
this->_contextMenu.show_at_point(GetParent(this->_hWnd), coords, this->_hWnd);
return itemBelowCursor; // -1 if none
}
};
}//namespace wl