|
1
|
1 // Advanced CListControl use demo
|
|
|
2 // Subclasses a CListControl to use all its features
|
|
|
3
|
|
|
4 #include "stdafx.h"
|
|
|
5 #include "resource.h"
|
|
|
6 #include <helpers/atl-misc.h>
|
|
|
7 #include <libPPUI/CListControlComplete.h>
|
|
|
8 #include <libPPUI/CListControl-Cells.h>
|
|
|
9 #include <string>
|
|
|
10 #include <algorithm>
|
|
|
11 #include <vector>
|
|
|
12
|
|
|
13 #include <helpers/DarkMode.h>
|
|
|
14
|
|
|
15 namespace {
|
|
|
16 struct listData_t {
|
|
|
17 std::string m_key, m_value;
|
|
|
18 bool m_checkState = false;
|
|
|
19 };
|
|
|
20 static std::vector<listData_t> makeListData() {
|
|
|
21 std::vector<listData_t> data;
|
|
|
22 data.resize( 10 );
|
|
|
23 for( size_t walk = 0; walk < data.size(); ++ walk ) {
|
|
|
24 auto & rec = data[walk];
|
|
|
25 rec.m_key = (PFC_string_formatter() << "Item #" << (walk+1) ).c_str();
|
|
|
26 rec.m_value = "edit me";
|
|
|
27 }
|
|
|
28 return data;
|
|
|
29 }
|
|
|
30
|
|
|
31 // See CListControlComplete.h for base class description
|
|
|
32 typedef CListControlComplete CListControlDemoParent;
|
|
|
33
|
|
|
34 class CListControlDemo : public CListControlDemoParent {
|
|
|
35 typedef CListControlDemoParent parent_t;
|
|
|
36 public:
|
|
|
37 BEGIN_MSG_MAP_EX(CListControlDemo)
|
|
|
38 CHAIN_MSG_MAP( parent_t );
|
|
|
39 MSG_WM_CREATE(OnCreate)
|
|
|
40 MSG_WM_CONTEXTMENU(OnContextMenu)
|
|
|
41 END_MSG_MAP()
|
|
|
42
|
|
|
43
|
|
|
44 // Context menu handler
|
|
|
45 void OnContextMenu(CWindow wnd, CPoint point) {
|
|
|
46 // did we get a (-1,-1) point due to context menu key rather than right click?
|
|
|
47 // GetContextMenuPoint fixes that, returning a proper point at which the menu should be shown
|
|
|
48 point = this->GetContextMenuPoint(point);
|
|
|
49
|
|
|
50 CMenu menu;
|
|
|
51 // WIN32_OP_D() : debug build only return value check
|
|
|
52 // Used to check for obscure errors in debug builds, does nothing (ignores errors) in release build
|
|
|
53 WIN32_OP_D(menu.CreatePopupMenu());
|
|
|
54
|
|
|
55 enum { ID_TEST1 = 1, ID_TEST2, ID_SELECTALL, ID_SELECTNONE, ID_INVERTSEL };
|
|
|
56 menu.AppendMenu(MF_STRING, ID_TEST1, L"Test 1");
|
|
|
57 menu.AppendMenu(MF_STRING, ID_TEST2, L"Test 2");
|
|
|
58 menu.AppendMenu(MF_SEPARATOR);
|
|
|
59 // Note: Ctrl+A handled automatically by CListControl, no need for us to catch it
|
|
|
60 menu.AppendMenu(MF_STRING, ID_SELECTALL, L"Select all\tCtrl+A");
|
|
|
61 menu.AppendMenu(MF_STRING, ID_SELECTNONE, L"Select none");
|
|
|
62 menu.AppendMenu(MF_STRING, ID_INVERTSEL, L"Invert selection");
|
|
|
63
|
|
|
64 int cmd;
|
|
|
65 {
|
|
|
66 // Callback object to show menu command descriptions in the status bar.
|
|
|
67 // it's actually a hidden window, needs a parent HWND, where we feed our control's HWND
|
|
|
68 CMenuDescriptionMap descriptions(m_hWnd);
|
|
|
69
|
|
|
70 // Set descriptions of all our items
|
|
|
71 descriptions.Set(ID_TEST1, "This is a test item #1");
|
|
|
72 descriptions.Set(ID_TEST2, "This is a test item #2");
|
|
|
73
|
|
|
74 descriptions.Set(ID_SELECTALL, "Selects all items");
|
|
|
75 descriptions.Set(ID_SELECTNONE, "Deselects all items");
|
|
|
76 descriptions.Set(ID_INVERTSEL, "Invert selection");
|
|
|
77
|
|
|
78 cmd = menu.TrackPopupMenuEx(TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, descriptions, nullptr);
|
|
|
79 }
|
|
|
80 switch(cmd) {
|
|
|
81 case ID_TEST1:
|
|
|
82 {
|
|
|
83 pfc::string_formatter msg;
|
|
|
84 msg << "Test command #1 triggered.\r\n";
|
|
|
85 msg << this->GetSelectedCount() << " items selected.";
|
|
|
86 // popup_message : non-blocking MessageBox equivalent
|
|
|
87 popup_message::g_show(msg, "Information");
|
|
|
88 }
|
|
|
89 break;
|
|
|
90 case ID_TEST2:
|
|
|
91 {
|
|
|
92 pfc::string_formatter msg;
|
|
|
93 msg << "Test command #1 triggered.\r\n";
|
|
|
94 msg << "Selected items:\r\n";
|
|
|
95 for( size_t walk = 0; walk < GetItemCount(); ++ walk) {
|
|
|
96 if ( this->IsItemSelected( walk ) ) {
|
|
|
97 msg << m_data[walk].m_key.c_str() << "\r\n";
|
|
|
98 }
|
|
|
99 }
|
|
|
100 msg << "End selected items.";
|
|
|
101 // popup_message : non-blocking MessageBox equivalent
|
|
|
102 popup_message::g_show(msg, "Information");
|
|
|
103 }
|
|
|
104 break;
|
|
|
105 case ID_SELECTALL:
|
|
|
106 this->SelectAll(); // trivial
|
|
|
107 break;
|
|
|
108 case ID_SELECTNONE:
|
|
|
109 this->SelectNone(); // trivial
|
|
|
110 break;
|
|
|
111 case ID_INVERTSEL:
|
|
|
112 {
|
|
|
113 auto mask = this->GetSelectionMask();
|
|
|
114 this->SetSelection(
|
|
|
115 // Items which we alter - all of them
|
|
|
116 pfc::bit_array_true(),
|
|
|
117 // Selection values - NOT'd original selection mask
|
|
|
118 pfc::bit_array_not(mask)
|
|
|
119 );
|
|
|
120 // Exclusion of footer item from selection handled via CanSelectItem()
|
|
|
121 }
|
|
|
122 break;
|
|
|
123 }
|
|
|
124 }
|
|
|
125
|
|
|
126 int OnCreate(LPCREATESTRUCT lpCreateStruct) {
|
|
|
127 InitHeader(); // set up header control with columns
|
|
|
128 SetWindowText(L"List Control Demo"); // screen reader will see this
|
|
|
129 return 0;
|
|
|
130 }
|
|
|
131 void InitHeader() {
|
|
|
132 InitializeHeaderCtrl(HDS_FULLDRAG);
|
|
|
133
|
|
|
134 // never hardcode values in pixels, always use screen DPI
|
|
|
135 auto DPI = m_dpi;
|
|
|
136 AddColumn( "Check", MulDiv(60, DPI.cx, 96 ) );
|
|
|
137 AddColumn( "Name", MulDiv(100, DPI.cx, 96 ) );
|
|
|
138 AddColumn( "Value", MulDiv(100, DPI.cx, 96 ) );
|
|
|
139 }
|
|
|
140
|
|
|
141 bool CanSelectItem( size_t row ) const override {
|
|
|
142 // can not select footer
|
|
|
143 return row != footerRow();
|
|
|
144 }
|
|
|
145 size_t footerRow() const {
|
|
|
146 return m_data.size();
|
|
|
147 }
|
|
|
148 t_size GetItemCount() const override {
|
|
|
149 return m_data.size() + 1; // footer
|
|
|
150 }
|
|
|
151 void onFooterClicked() {
|
|
|
152 SelectNone();
|
|
|
153 listData_t obj = {};
|
|
|
154 obj.m_key = "New item";
|
|
|
155 size_t index = m_data.size();
|
|
|
156 m_data.push_back( std::move(obj) );
|
|
|
157 OnItemsInserted(index, 1, true);
|
|
|
158 }
|
|
|
159 void OnSubItemClicked(t_size item, t_size subItem, CPoint pt) override {
|
|
|
160 if ( item == footerRow() ) {
|
|
|
161 onFooterClicked(); return;
|
|
|
162 }
|
|
|
163 if ( subItem == 2 ) {
|
|
|
164 TableEdit_Start(item, subItem); return;
|
|
|
165 }
|
|
|
166 __super::OnSubItemClicked( item, subItem, pt );
|
|
|
167 }
|
|
|
168 bool AllowScrollbar(bool vertical) const override {
|
|
|
169 return true;
|
|
|
170 }
|
|
|
171 t_size InsertIndexFromPointEx(const CPoint & pt, bool & bInside) const override {
|
|
|
172 // Drag&drop insertion point hook, for reordering only
|
|
|
173 auto ret = __super::InsertIndexFromPointEx(pt, bInside);
|
|
|
174 bInside = false; // never drop *into* an item, only between, as we only allow reorder
|
|
|
175 if ( ret > m_data.size() ) ret = m_data.size(); // never allow drop beyond footer
|
|
|
176 return ret;
|
|
|
177 }
|
|
|
178 void RequestReorder(size_t const * order, size_t count) override {
|
|
|
179 // we've been asked to reorder the items, by either drag&drop or cursors+modifiers
|
|
|
180 // we can either reorder as requested, reorder partially if some of the items aren't moveable, or reject the request
|
|
|
181
|
|
|
182 PFC_ASSERT( count == GetItemCount() );
|
|
|
183
|
|
|
184 // Footer row cannot be moved
|
|
|
185 if ( order[footerRow()] != footerRow() ) return;
|
|
|
186
|
|
|
187 pfc::reorder_t( m_data, order, count );
|
|
|
188 this->OnItemsReordered( order, count );
|
|
|
189 }
|
|
|
190 void RemoveMask( pfc::bit_array const & mask ) {
|
|
|
191 if ( mask.get(footerRow() ) ) return; // footer row cannot be removed
|
|
|
192 auto oldCount = GetItemCount();
|
|
|
193 pfc::remove_mask_t( m_data, mask );
|
|
|
194 this->OnItemsRemoved( mask, oldCount );
|
|
|
195 }
|
|
|
196 void RequestRemoveSelection() override {
|
|
|
197 // Delete key etc
|
|
|
198 RemoveMask(GetSelectionMask());
|
|
|
199 }
|
|
|
200 void ExecuteDefaultAction(t_size index) override {
|
|
|
201 // double click, enter key, etc
|
|
|
202 if ( index == footerRow() ) onFooterClicked();
|
|
|
203 }
|
|
|
204
|
|
|
205 bool GetSubItemText(t_size item, t_size subItem, pfc::string_base & out) const override {
|
|
|
206 if ( item == footerRow() ) {
|
|
|
207 if ( subItem == 0 ) {
|
|
|
208 out = "+ add new";
|
|
|
209 return true;
|
|
|
210 }
|
|
|
211 return false;
|
|
|
212 }
|
|
|
213 auto & rec = m_data[item];
|
|
|
214 switch(subItem) {
|
|
|
215 case 0:
|
|
|
216 // pass blank string or return false to create a checkbox only column
|
|
|
217 out = "check";
|
|
|
218 return true;
|
|
|
219 case 1:
|
|
|
220 out = rec.m_key.c_str();
|
|
|
221 return true;
|
|
|
222 case 2:
|
|
|
223 out = rec.m_value.c_str();
|
|
|
224 return true;
|
|
|
225 default:
|
|
|
226 return false;
|
|
|
227 }
|
|
|
228 }
|
|
|
229
|
|
|
230 size_t GetSubItemSpan(size_t row, size_t column) const override {
|
|
|
231 if ( row == footerRow() && column == 0 ) {
|
|
|
232 return GetColumnCount();
|
|
|
233 }
|
|
|
234 return 1;
|
|
|
235 }
|
|
|
236 cellType_t GetCellType(size_t item, size_t subItem) const override {
|
|
|
237 // cellType_t is a pointer to a cell class object supplying cell behavior specification & rendering methods
|
|
|
238 // use PFC_SINGLETON to provide static instances of used cells
|
|
|
239 if ( item == footerRow() ) {
|
|
|
240 if ( subItem == 0 ) {
|
|
|
241 return & PFC_SINGLETON( CListCell_Button );
|
|
|
242 } else {
|
|
|
243 return nullptr;
|
|
|
244 }
|
|
|
245 }
|
|
|
246 switch(subItem) {
|
|
|
247 case 0:
|
|
|
248 return & PFC_SINGLETON( CListCell_Checkbox );
|
|
|
249 default:
|
|
|
250 return & PFC_SINGLETON( CListCell_Text );
|
|
|
251 }
|
|
|
252
|
|
|
253 }
|
|
|
254 bool GetCellTypeSupported() const override {
|
|
|
255 return true;
|
|
|
256 }
|
|
|
257 bool GetCellCheckState(size_t item, size_t subItem) const override {
|
|
|
258 if ( subItem == 0 ) {
|
|
|
259 auto & rec = m_data[item];
|
|
|
260 return rec.m_checkState;
|
|
|
261 }
|
|
|
262 return false;
|
|
|
263 }
|
|
|
264 void SetCellCheckState(size_t item, size_t subItem, bool value) override {
|
|
|
265 if ( subItem == 0 ) {
|
|
|
266 auto & rec = m_data[item];
|
|
|
267 if (rec.m_checkState != value) {
|
|
|
268 rec.m_checkState = value;
|
|
|
269 __super::SetCellCheckState(item, subItem, value);
|
|
|
270 }
|
|
|
271 }
|
|
|
272 }
|
|
|
273
|
|
|
274 uint32_t QueryDragDropTypes() const override {return dragDrop_reorder;}
|
|
|
275
|
|
|
276 // Inplace edit handlers
|
|
|
277 // Overrides of CTableEditHelperV2 methods
|
|
|
278 void TableEdit_SetField(t_size item, t_size subItem, const char * value) override {
|
|
|
279 if ( subItem == 2 ) {
|
|
|
280 m_data[item].m_value = value;
|
|
|
281 ReloadItem( item );
|
|
|
282 }
|
|
|
283 }
|
|
|
284 bool TableEdit_IsColumnEditable(t_size subItem) const override {
|
|
|
285 return subItem == 2;
|
|
|
286 }
|
|
|
287 private:
|
|
|
288 std::vector< listData_t > m_data = makeListData();
|
|
|
289 };
|
|
|
290
|
|
|
291 // Straightforward WTL dialog code
|
|
|
292 class CListControlAdvancedDemoDialog : public CDialogImpl<CListControlAdvancedDemoDialog> {
|
|
|
293 public:
|
|
|
294 enum { IDD = IDD_LISTCONTROL_DEMO };
|
|
|
295
|
|
|
296 BEGIN_MSG_MAP_EX(CListControlAdvancedDemoDialog)
|
|
|
297 MSG_WM_INITDIALOG(OnInitDialog)
|
|
|
298 COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnCancel)
|
|
|
299 END_MSG_MAP()
|
|
|
300 private:
|
|
|
301 void OnCancel(UINT, int, CWindow) {
|
|
|
302 DestroyWindow();
|
|
|
303 }
|
|
|
304
|
|
|
305 BOOL OnInitDialog(CWindow, LPARAM) {
|
|
|
306
|
|
|
307 // Create replacing existing windows list control
|
|
|
308 // automatically initialize position, font, etc
|
|
|
309 m_list.CreateInDialog( *this, IDC_LIST1 );
|
|
|
310
|
|
|
311 // Do this AFTER creating CListControl, so dark mode hook talks to new CListControl rather than shortlived IDC_LIST1 placeholder
|
|
|
312 m_dark.AddDialogWithControls(*this);
|
|
|
313
|
|
|
314 ShowWindow(SW_SHOW);
|
|
|
315
|
|
|
316 return TRUE; // system should set focus
|
|
|
317 }
|
|
|
318
|
|
|
319 CListControlDemo m_list;
|
|
|
320
|
|
|
321 fb2k::CDarkModeHooks m_dark;
|
|
|
322 };
|
|
|
323 }
|
|
|
324
|
|
|
325 // Called from mainmenu.cpp
|
|
|
326 void RunListControlAdvancedDemo() {
|
|
|
327 // automatically creates the dialog with object lifetime management and modeless dialog registration
|
|
|
328 fb2k::newDialog<CListControlAdvancedDemoDialog>();
|
|
|
329 }
|
|
|
330
|