├── .clang-tidy ├── .editorconfig ├── .gitignore ├── LICENSE ├── ListEx └── ListEx.ixx ├── README.md ├── SampleProject ├── ListEx.sln ├── ListExSample.cpp ├── ListExSample.h ├── ListExSample.rc ├── ListExSample.vcxproj ├── ListExSample.vcxproj.filters ├── ListExSampleDlg.cpp ├── ListExSampleDlg.h ├── Resource.h ├── framework.h ├── res │ ├── ListExSample.ico │ ├── ListExSample.rc2 │ └── test.ico ├── stdafx.cpp ├── stdafx.h └── targetver.h └── docs └── img └── listex_mainwnd.jpg /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '*, 2 | -cppcoreguidelines-*,-google-*,-fuchsia-*,-hicpp-*,-cert-*,-clang-*,-llvmlibc-*,-altera-*,-boost-*,-abseil-*, 3 | -bugprone-easily-swappable-parameters, 4 | -bugprone-implicit-widening-of-multiplication-result, 5 | -bugprone-narrowing-conversions, 6 | -bugprone-use-after-move, 7 | -llvm-header-guard, 8 | -llvm-include-order, 9 | -llvm-namespace-comment, 10 | -llvm-qualified-auto, 11 | -misc-include-cleaner, 12 | -misc-non-private-member-variables-in-classes, 13 | -misc-no-recursion, 14 | -misc-use-after-move, 15 | -misc-use-internal-linkage, 16 | -modernize-use-trailing-return-type, 17 | -modernize-avoid-c-arrays, 18 | -modernize-use-nodiscard, 19 | -modernize-use-ranges, 20 | -performance-no-int-to-ptr, 21 | -portability-simd-intrinsics, 22 | -readability-avoid-nested-conditional-operator, 23 | -readability-braces-around-statements, 24 | -readability-implicit-bool-conversion, 25 | -readability-magic-numbers, 26 | -readability-redundant-access-specifiers, 27 | -readability-isolate-declaration, 28 | -readability-qualified-auto, 29 | -readability-convert-member-functions-to-static, 30 | -readability-misleading-indentation, 31 | -readability-function-cognitive-complexity, 32 | -readability-identifier-length, 33 | -readability-named-parameter, 34 | google-readability-casting' 35 | 36 | HeaderFilterRegex: '' -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] 4 | cpp_generate_documentation_comments = xml 5 | cpp_indent_braces = false 6 | cpp_indent_multi_line_relative_to = innermost_parenthesis 7 | cpp_indent_within_parentheses = indent 8 | cpp_indent_preserve_within_parentheses = true 9 | cpp_indent_case_contents = true 10 | cpp_indent_case_labels = false 11 | cpp_indent_case_contents_when_block = false 12 | cpp_indent_lambda_braces_when_parameter = true 13 | cpp_indent_goto_labels = one_left 14 | cpp_indent_preprocessor = one_left 15 | cpp_indent_access_specifiers = false 16 | cpp_indent_namespace_contents = true 17 | cpp_indent_preserve_comments = true 18 | cpp_new_line_before_open_brace_namespace = same_line 19 | cpp_new_line_before_open_brace_type = same_line 20 | cpp_new_line_before_open_brace_function = ignore 21 | cpp_new_line_before_open_brace_block = same_line 22 | cpp_new_line_before_open_brace_lambda = same_line 23 | cpp_new_line_scope_braces_on_separate_lines = false 24 | cpp_new_line_close_brace_same_line_empty_type = false 25 | cpp_new_line_close_brace_same_line_empty_function = false 26 | cpp_new_line_before_catch = true 27 | cpp_new_line_before_else = true 28 | cpp_new_line_before_while_in_do_while = false 29 | cpp_space_before_function_open_parenthesis = remove 30 | cpp_space_within_parameter_list_parentheses = false 31 | cpp_space_between_empty_parameter_list_parentheses = false 32 | cpp_space_after_keywords_in_control_flow_statements = true 33 | cpp_space_within_control_flow_statement_parentheses = false 34 | cpp_space_before_lambda_open_parenthesis = false 35 | cpp_space_within_cast_parentheses = false 36 | cpp_space_after_cast_close_parenthesis = false 37 | cpp_space_within_expression_parentheses = false 38 | cpp_space_before_block_open_brace = true 39 | cpp_space_between_empty_braces = true 40 | cpp_space_before_initializer_list_open_brace = true 41 | cpp_space_within_initializer_list_braces = true 42 | cpp_space_preserve_in_initializer_list = false 43 | cpp_space_before_open_square_bracket = false 44 | cpp_space_within_square_brackets = false 45 | cpp_space_before_empty_square_brackets = false 46 | cpp_space_between_empty_square_brackets = false 47 | cpp_space_group_square_brackets = true 48 | cpp_space_within_lambda_brackets = false 49 | cpp_space_between_empty_lambda_brackets = false 50 | cpp_space_before_comma = false 51 | cpp_space_after_comma = true 52 | cpp_space_remove_around_member_operators = true 53 | cpp_space_before_inheritance_colon = true 54 | cpp_space_before_constructor_colon = true 55 | cpp_space_remove_before_semicolon = true 56 | cpp_space_after_semicolon = true 57 | cpp_space_remove_around_unary_operator = true 58 | cpp_space_around_binary_operator = insert 59 | cpp_space_around_assignment_operator = insert 60 | cpp_space_pointer_reference_alignment = ignore 61 | cpp_space_around_ternary_operator = insert 62 | cpp_wrap_preserve_blocks = one_liners 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *stacktrace.* 6 | *.zip 7 | *.rar 8 | *.exe 9 | *.pydevproject 10 | .project 11 | .metadata 12 | bin/ 13 | tmp/ 14 | *.tmp 15 | *.bak 16 | *.swp 17 | *~.nib 18 | local.properties 19 | .classpath 20 | .settings/ 21 | .loadpath 22 | 23 | # External tool builders 24 | .externalToolBuilders/ 25 | 26 | # Locally stored "Eclipse launch configurations" 27 | *.launch 28 | 29 | # CDT-specific 30 | .cproject 31 | 32 | # PDT-specific 33 | .buildpath 34 | 35 | 36 | ################# 37 | ## Visual Studio 38 | ################# 39 | 40 | ## Ignore Visual Studio temporary files, build results, and 41 | ## files generated by popular Visual Studio add-ons. 42 | 43 | .vs/ 44 | 45 | # User-specific files 46 | *.suppress 47 | *.suo 48 | *.user 49 | *.sln.docstates 50 | *.opendb 51 | *solution_suppressions.cfg 52 | 53 | # Build results 54 | 55 | [Dd]ebug*/ 56 | [Rr]elease*/ 57 | x64/ 58 | build/ 59 | [Bb]in/ 60 | [Oo]bj/ 61 | 62 | # MSTest test Results 63 | [Tt]est[Rr]esult*/ 64 | [Bb]uild[Ll]og.* 65 | 66 | *_i.c 67 | *_p.c 68 | *.ilk 69 | *.meta 70 | *.obj 71 | *.pch 72 | *.pdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.log 88 | *.scc 89 | 90 | # Visual C++ cache files 91 | ipch/ 92 | *.aps 93 | *.ncb 94 | *.opensdf 95 | *.sdf 96 | *.cachefile 97 | *.cd 98 | *.i-* 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | 105 | # Guidance Automation Toolkit 106 | *.gpState 107 | 108 | # ReSharper is a .NET coding add-in 109 | _ReSharper*/ 110 | *.[Rr]e[Ss]harper 111 | 112 | # TeamCity is a build add-in 113 | _TeamCity* 114 | 115 | # DotCover is a Code Coverage Tool 116 | *.dotCover 117 | 118 | # NCrunch 119 | *.ncrunch* 120 | .*crunch*.local.xml 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.Publish.xml 140 | *.pubxml 141 | *.publishproj 142 | 143 | # NuGet Packages Directory 144 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 145 | #packages/ 146 | 147 | # Windows Azure Build Output 148 | csx 149 | *.build.csdef 150 | 151 | # Windows Store app package directory 152 | AppPackages/ 153 | 154 | # Others 155 | sql/ 156 | *.Cache 157 | ClientBin/ 158 | [Ss]tyle[Cc]op.* 159 | ~$* 160 | *~ 161 | *.dbmdl 162 | *.[Pp]ublish.xml 163 | *.pfx 164 | *.publishsettings 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file to a newer 170 | # Visual Studio version. Backup files are not needed, because we have git ;-) 171 | _UpgradeReport_Files/ 172 | Backup*/ 173 | UpgradeLog*.XML 174 | UpgradeLog*.htm 175 | 176 | # SQL Server files 177 | App_Data/*.mdf 178 | App_Data/*.ldf 179 | 180 | ############# 181 | ## Windows detritus 182 | ############# 183 | 184 | # Windows image file caches 185 | Thumbs.db 186 | ehthumbs.db 187 | 188 | # Folder config file 189 | Desktop.ini 190 | 191 | # Recycle Bin used on file shares 192 | $RECYCLE.BIN/ 193 | 194 | # Mac crap 195 | .DS_Store 196 | 197 | 198 | ############# 199 | ## Python 200 | ############# 201 | 202 | *.py[cod] 203 | 204 | # Packages 205 | *.egg 206 | *.egg-info 207 | dist/ 208 | build/ 209 | eggs/ 210 | parts/ 211 | var/ 212 | sdist/ 213 | develop-eggs/ 214 | .installed.cfg 215 | 216 | # Installer logs 217 | pip-log.txt 218 | 219 | # Unit test / coverage reports 220 | .coverage 221 | .tox 222 | 223 | #Translations 224 | *.mo 225 | 226 | #Mr Developer 227 | .mr.developer.cfg 228 | *.db 229 | *.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2023 Jovibor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ListEx/ListEx.ixx: -------------------------------------------------------------------------------- 1 | module; 2 | /******************************************************************** 3 | * Copyright © 2018-present Jovibor https://github.com/jovibor/ * 4 | * Official git repository: https://github.com/jovibor/ListEx/ * 5 | * This code is available under the "MIT License". * 6 | ********************************************************************/ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | export module ListEx; 21 | 22 | #pragma comment(lib, "Comctl32.lib") 23 | #pragma comment(lib, "Shlwapi.lib") //StrToInt64ExW(). 24 | #pragma comment(lib, "UxTheme.lib") //SetWindowTheme(). 25 | //Setting manifest for the ComCtl32.dll version 6. 26 | #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' \ 27 | version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 28 | 29 | export namespace LISTEX { 30 | /**************************************************************** 31 | * EListExSortMode - Sorting mode. * 32 | ****************************************************************/ 33 | enum class EListExSortMode : std::uint8_t { 34 | SORT_LEX, SORT_NUMERIC 35 | }; 36 | 37 | /**************************************************************** 38 | * LISTEXCOLOR - colors for the cell. * 39 | ****************************************************************/ 40 | struct LISTEXCOLOR { 41 | COLORREF clrBk { }; //Bk color. 42 | COLORREF clrText { }; //Text color. 43 | }; 44 | using PLISTEXCOLOR = const LISTEXCOLOR*; 45 | 46 | /**************************************************************** 47 | * LISTEXCOLORINFO - struct for the LISTEX_MSG_GETCOLOR message. * 48 | ****************************************************************/ 49 | struct LISTEXCOLORINFO { 50 | NMHDR hdr { }; 51 | int iItem { }; 52 | int iSubItem { }; 53 | LISTEXCOLOR stClr; 54 | }; 55 | using PLISTEXCOLORINFO = LISTEXCOLORINFO*; 56 | 57 | /**************************************************************** 58 | * LISTEXDATAINFO - struct for the LISTEX_MSG_SETDATA message. * 59 | ****************************************************************/ 60 | struct LISTEXDATAINFO { 61 | NMHDR hdr { }; 62 | int iItem { }; 63 | int iSubItem { }; 64 | HWND hWndEdit { }; //Handle of the edit box control. 65 | LPWSTR pwszData { }; //Text that is set, or is about to be set in case of LISTEX_MSG_EDITBEFORETEXT. 66 | bool fAllowEdit { true }; //Allow cell editing or not, in case of LISTEX_MSG_EDITBEGIN. 67 | }; 68 | using PLISTEXDATAINFO = LISTEXDATAINFO*; 69 | 70 | /**************************************************************** 71 | * LISTEXICONINFO - struct for the LISTEX_MSG_GETICON message. * 72 | ****************************************************************/ 73 | struct LISTEXICONINFO { 74 | NMHDR hdr { }; 75 | int iItem { }; 76 | int iSubItem { }; 77 | int iIconIndex { -1 }; //Icon index in the list internal image-list. 78 | }; 79 | using PLISTEXICONINFO = LISTEXICONINFO*; 80 | 81 | /**************************************************************** 82 | * LISTEXTTINFO - struct for the LISTEX_MSG_GETTOOLTIP message. * 83 | ****************************************************************/ 84 | struct LISTEXTTDATA { //Tooltips data. 85 | LPCWSTR pwszText { }; //Tooltip text. 86 | LPCWSTR pwszCaption { }; //Tooltip caption. 87 | }; 88 | struct LISTEXTTINFO { 89 | NMHDR hdr { }; 90 | int iItem { }; 91 | int iSubItem { }; 92 | LISTEXTTDATA stData; 93 | }; 94 | using PLISTEXTTINFO = LISTEXTTINFO*; 95 | 96 | /**************************************************************** 97 | * LISTEXLINKINFO - struct for the LISTEX_MSG_LINKCLICK message. * 98 | ****************************************************************/ 99 | struct LISTEXLINKINFO { 100 | NMHDR hdr { }; 101 | int iItem { }; 102 | int iSubItem { }; 103 | POINT ptClick { }; //A point where the link was clicked. 104 | LPCWSTR pwszText { }; //Link text. 105 | }; 106 | using PLISTEXLINKINFO = LISTEXLINKINFO*; 107 | 108 | /********************************************************************************** 109 | * LISTEXCOLORS - All CListEx colors. * 110 | **********************************************************************************/ 111 | struct LISTEXCOLORS { 112 | COLORREF clrListText { GetSysColor(COLOR_WINDOWTEXT) }; //List text color. 113 | COLORREF clrListTextLink { RGB(0, 0, 200) }; //List hyperlink text color. 114 | COLORREF clrListTextSel { GetSysColor(COLOR_HIGHLIGHTTEXT) }; //Selected item text color. 115 | COLORREF clrListTextLinkSel { RGB(250, 250, 250) }; //List hyperlink text color in selected cell. 116 | COLORREF clrListBkOdd { GetSysColor(COLOR_WINDOW) }; //List Bk color of the odd rows. 117 | COLORREF clrListBkEven { GetSysColor(COLOR_WINDOW) }; //List Bk color of the even rows. 118 | COLORREF clrListBkSel { GetSysColor(COLOR_HIGHLIGHT) }; //Selected item bk color. 119 | COLORREF clrListGrid { RGB(220, 220, 220) }; //List grid color. 120 | COLORREF clrTooltipText { 0xFFFFFFFFUL }; //Tooltip text color, 0xFFFFFFFFUL for current Theme color. 121 | COLORREF clrTooltipBk { 0xFFFFFFFFUL }; //Tooltip bk color, 0xFFFFFFFFUL for current Theme color. 122 | COLORREF clrHdrText { GetSysColor(COLOR_WINDOWTEXT) }; //List header text color. 123 | COLORREF clrHdrBk { GetSysColor(COLOR_WINDOW) }; //List header bk color. 124 | COLORREF clrHdrHglInact { GetSysColor(COLOR_GRADIENTINACTIVECAPTION) };//Header highlight inactive. 125 | COLORREF clrHdrHglAct { GetSysColor(COLOR_GRADIENTACTIVECAPTION) }; //Header highlight active. 126 | COLORREF clrNWABk { GetSysColor(COLOR_WINDOW) }; //Bk of Non Working Area. 127 | }; 128 | using PCLISTEXCOLORS = const LISTEXCOLORS*; 129 | 130 | /********************************************************************************** 131 | * LISTEXCREATE - Main initialization helper struct for CListEx::Create method. * 132 | **********************************************************************************/ 133 | struct LISTEXCREATE { 134 | HWND hWndParent { }; //Parent window. 135 | PCLISTEXCOLORS pColors { }; //CListEx colors. 136 | const LOGFONTW* pLFList { }; //CListEx LOGFONT. 137 | const LOGFONTW* pLFHdr { }; //Header LOGFONT. 138 | RECT rect { }; //Initial rect. 139 | UINT uID { }; //CListEx Control ID. 140 | DWORD dwStyle { }; //Window styles. 141 | DWORD dwExStyle { }; //Extended window styles. 142 | DWORD dwSizeFontList { 9 }; //List font default size in logical points. 143 | DWORD dwSizeFontHdr { 9 }; //Header font default size in logical points. 144 | DWORD dwTTStyleCell { }; //Cell's tooltip Window styles. 145 | DWORD dwTTStyleLink { }; //Link's tooltip Window styles. 146 | DWORD dwTTDelayTime { }; //Tooltip delay before showing up, in ms. 147 | DWORD dwTTShowTime { 5000 }; //Tooltip show up time, in ms. 148 | DWORD dwWidthGrid { 1 }; //Width of the list grid. 149 | DWORD dwHdrHeight { }; //Header height. 150 | POINT ptTTOffset { .x { 3 }, .y { -20 } }; //Tooltip offset from a cursor pos. Doesn't work for TTS_BALLOON. 151 | bool fDialogCtrl { false }; //If it's a list within dialog? 152 | bool fSortable { false }; //Is list sortable, by clicking on the header column? 153 | bool fLinks { false }; //Enable links support. 154 | bool fLinkUnderline { true }; //Links are displayed underlined or not. 155 | bool fLinkTooltip { true }; //Show links' toolips or not. 156 | bool fHighLatency { false }; //Do not redraw window until scroll thumb is released. 157 | bool fEditSingleClick { false }; //Cells editable with single mouse click or double? 158 | }; 159 | 160 | /******************************************** 161 | * LISTEXHDRICON - Icon for header column. * 162 | ********************************************/ 163 | struct LISTEXHDRICON { 164 | POINT pt { }; //Coords of the icon's top-left corner in the header item's rect. 165 | int iIndex { -1 }; //Icon index in the header's image list. 166 | bool fClickable { true }; //Is icon sending LISTEX_MSG_HDRICONCLICK message when clicked. 167 | }; 168 | 169 | //WM_NOTIFY codes (NMHDR.code values). 170 | 171 | constexpr auto LISTEX_MSG_EDITBEGIN { 0x1000U }; //Edit in-place box is about to display. 172 | constexpr auto LISTEX_MSG_GETCOLOR { 0x1001U }; //Get cell color. 173 | constexpr auto LISTEX_MSG_GETICON { 0x1002U }; //Get cell icon. 174 | constexpr auto LISTEX_MSG_GETTOOLTIP { 0x1003U }; //Get cell tool-tip data. 175 | constexpr auto LISTEX_MSG_HDRICONCLICK { 0x1004U }; //Header's icon has been clicked. 176 | constexpr auto LISTEX_MSG_HDRRBTNDOWN { 0x1005U }; //Header's WM_RBUTTONDOWN message. 177 | constexpr auto LISTEX_MSG_HDRRBTNUP { 0x1006U }; //Header's WM_RBUTTONUP message. 178 | constexpr auto LISTEX_MSG_LINKCLICK { 0x1007U }; //Hyperlink has been clicked. 179 | constexpr auto LISTEX_MSG_SETDATA { 0x1008U }; //Item text has been edited/changed. 180 | } 181 | 182 | namespace LISTEX::INTERNAL::wnd { 183 | auto DefSubclassProc(const MSG& msg) -> LRESULT { 184 | return ::DefSubclassProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); 185 | } 186 | 187 | //Replicates GET_X_LPARAM macro from the windowsx.h. 188 | [[nodiscard]] constexpr int GetXLPARAM(LPARAM lParam) { 189 | return (static_cast(static_cast(static_cast((static_cast(lParam)) & 0xFFFF)))); 190 | } 191 | 192 | [[nodiscard]] constexpr int GetYLPARAM(LPARAM lParam) { 193 | return GetXLPARAM(lParam >> 16); 194 | } 195 | 196 | class CPoint : public POINT { 197 | public: 198 | CPoint() : POINT { } { } 199 | CPoint(POINT pt) : POINT { pt } { } 200 | CPoint(int x, int y) : POINT { .x { x }, .y { y } } { } 201 | operator LPPOINT() { return this; } 202 | operator const POINT*()const { return this; } 203 | bool operator==(POINT pt)const { return x == pt.x && y == pt.y; } 204 | bool operator!=(POINT pt)const { return !(*this == pt); } 205 | CPoint& operator=(POINT pt) { *this = pt; return *this; } 206 | CPoint operator+(POINT pt)const { return { x + pt.x, y + pt.y }; } 207 | CPoint operator-(POINT pt)const { return { x - pt.x, y - pt.y }; } 208 | void Offset(int iX, int iY) { x += iX; y += iY; } 209 | void Offset(POINT pt) { Offset(pt.x, pt.y); } 210 | }; 211 | 212 | class CRect : public RECT { 213 | public: 214 | CRect() : RECT { } { } 215 | CRect(int iLeft, int iTop, int iRight, int iBottom) : RECT { .left { iLeft }, .top { iTop }, 216 | .right { iRight }, .bottom { iBottom } } { 217 | } 218 | CRect(const RECT rc) { ::CopyRect(this, &rc); } 219 | CRect(LPCRECT pRC) { ::CopyRect(this, pRC); } 220 | CRect(POINT pt, SIZE size) { left = pt.x; right = left + size.cx; top = pt.y; bottom = top + size.cy; } 221 | CRect(POINT topLeft, POINT botRight) { 222 | left = topLeft.x; top = topLeft.y; right = botRight.x; bottom = botRight.y; 223 | } 224 | operator LPRECT() { return this; } 225 | operator LPCRECT()const { return this; } 226 | bool operator==(RECT rc)const { return ::EqualRect(this, &rc); } 227 | bool operator!=(RECT rc)const { return !(*this == rc); } 228 | CRect& operator=(RECT rc) { ::CopyRect(this, &rc); return *this; } 229 | [[nodiscard]] auto BottomRight()const->CPoint { return { { .x { right }, .y { bottom } } }; }; 230 | void DeflateRect(int x, int y) { ::InflateRect(this, -x, -y); } 231 | void DeflateRect(SIZE size) { ::InflateRect(this, -size.cx, -size.cy); } 232 | void DeflateRect(LPCRECT pRC) { left += pRC->left; top += pRC->top; right -= pRC->right; bottom -= pRC->bottom; } 233 | void DeflateRect(int l, int t, int r, int b) { left += l; top += t; right -= r; bottom -= b; } 234 | [[nodiscard]] int Height()const { return bottom - top; } 235 | [[nodiscard]] bool IsRectEmpty()const { return ::IsRectEmpty(this); } 236 | [[nodiscard]] bool IsRectNull()const { return (left == 0 && right == 0 && top == 0 && bottom == 0); } 237 | void OffsetRect(int x, int y) { ::OffsetRect(this, x, y); } 238 | void OffsetRect(POINT pt) { ::OffsetRect(this, pt.x, pt.y); } 239 | [[nodiscard]] bool PtInRect(POINT pt)const { return ::PtInRect(this, pt); } 240 | void SetRect(int x1, int y1, int x2, int y2) { ::SetRect(this, x1, y1, x2, y2); } 241 | void SetRectEmpty() { ::SetRectEmpty(this); } 242 | [[nodiscard]] auto TopLeft()const->CPoint { return { { .x { left }, .y { top } } }; }; 243 | [[nodiscard]] int Width()const { return right - left; } 244 | }; 245 | 246 | class CDC { 247 | public: 248 | CDC() = default; 249 | CDC(HDC hDC) : m_hDC(hDC) { } 250 | ~CDC() = default; 251 | operator HDC()const { return m_hDC; } 252 | void AbortDoc()const { ::AbortDoc(m_hDC); } 253 | void DeleteDC()const { ::DeleteDC(m_hDC); } 254 | HDC GetHDC()const { return m_hDC; } 255 | void GetTextMetricsW(LPTEXTMETRICW pTM)const { ::GetTextMetricsW(m_hDC, pTM); } 256 | auto SetBkColor(COLORREF clr)const->COLORREF { return ::SetBkColor(m_hDC, clr); } 257 | void DrawEdge(LPRECT pRC, UINT uEdge, UINT uFlags)const { ::DrawEdge(m_hDC, pRC, uEdge, uFlags); } 258 | void DrawFocusRect(LPCRECT pRc)const { ::DrawFocusRect(m_hDC, pRc); } 259 | int DrawTextW(std::wstring_view wsv, LPRECT pRect, UINT uFormat)const { 260 | return DrawTextW(wsv.data(), static_cast(wsv.size()), pRect, uFormat); 261 | } 262 | int DrawTextW(LPCWSTR pwszText, int iSize, LPRECT pRect, UINT uFormat)const { 263 | return ::DrawTextW(m_hDC, pwszText, iSize, pRect, uFormat); 264 | } 265 | int EndDoc()const { return ::EndDoc(m_hDC); } 266 | int EndPage()const { return ::EndPage(m_hDC); } 267 | void FillSolidRect(LPCRECT pRC, COLORREF clr)const { 268 | ::SetBkColor(m_hDC, clr); ::ExtTextOutW(m_hDC, 0, 0, ETO_OPAQUE, pRC, nullptr, 0, nullptr); 269 | } 270 | [[nodiscard]] auto GetClipBox()const->CRect { RECT rc; ::GetClipBox(m_hDC, &rc); return rc; } 271 | bool LineTo(POINT pt)const { return LineTo(pt.x, pt.y); } 272 | bool LineTo(int x, int y)const { return ::LineTo(m_hDC, x, y); } 273 | bool MoveTo(POINT pt)const { return MoveTo(pt.x, pt.y); } 274 | bool MoveTo(int x, int y)const { return ::MoveToEx(m_hDC, x, y, nullptr); } 275 | bool Polygon(const POINT* pPT, int iCount)const { return ::Polygon(m_hDC, pPT, iCount); } 276 | int SetMapMode(int iMode)const { return ::SetMapMode(m_hDC, iMode); } 277 | auto SetTextColor(COLORREF clr)const->COLORREF { return ::SetTextColor(m_hDC, clr); } 278 | auto SetViewportOrg(int iX, int iY)const->POINT { POINT pt; ::SetViewportOrgEx(m_hDC, iX, iY, &pt); return pt; } 279 | auto SelectObject(HGDIOBJ hObj)const->HGDIOBJ { return ::SelectObject(m_hDC, hObj); } 280 | int StartDocW(const DOCINFO* pDI)const { return ::StartDocW(m_hDC, pDI); } 281 | int StartPage()const { return ::StartPage(m_hDC); } 282 | void TextOutW(int iX, int iY, LPCWSTR pwszText, int iSize)const { ::TextOutW(m_hDC, iX, iY, pwszText, iSize); } 283 | void TextOutW(int iX, int iY, std::wstring_view wsv)const { 284 | TextOutW(iX, iY, wsv.data(), static_cast(wsv.size())); 285 | } 286 | protected: 287 | HDC m_hDC; 288 | }; 289 | 290 | class CPaintDC final : public CDC { 291 | public: 292 | CPaintDC(HWND hWnd) : m_hWnd(hWnd) { assert(::IsWindow(hWnd)); m_hDC = ::BeginPaint(m_hWnd, &m_PS); } 293 | ~CPaintDC() { ::EndPaint(m_hWnd, &m_PS); } 294 | private: 295 | PAINTSTRUCT m_PS; 296 | HWND m_hWnd; 297 | }; 298 | 299 | class CMemDC final : public CDC { 300 | public: 301 | CMemDC(HDC hDC, RECT rc); 302 | ~CMemDC(); 303 | private: 304 | HDC m_hDCOrig; 305 | HBITMAP m_hBmp; 306 | RECT m_rc; 307 | }; 308 | 309 | CMemDC::CMemDC(HDC hDC, RECT rc) : m_hDCOrig(hDC), m_rc(rc) 310 | { 311 | m_hDC = ::CreateCompatibleDC(m_hDCOrig); 312 | assert(m_hDC != nullptr); 313 | const auto iWidth = m_rc.right - m_rc.left; 314 | const auto iHeight = m_rc.bottom - m_rc.top; 315 | m_hBmp = ::CreateCompatibleBitmap(m_hDCOrig, iWidth, iHeight); 316 | assert(m_hBmp != nullptr); 317 | ::SelectObject(m_hDC, m_hBmp); 318 | } 319 | 320 | CMemDC::~CMemDC() 321 | { 322 | const auto iWidth = m_rc.right - m_rc.left; 323 | const auto iHeight = m_rc.bottom - m_rc.top; 324 | ::BitBlt(m_hDCOrig, m_rc.left, m_rc.top, iWidth, iHeight, m_hDC, m_rc.left, m_rc.top, SRCCOPY); 325 | ::DeleteObject(m_hBmp); 326 | ::DeleteDC(m_hDC); 327 | } 328 | } 329 | 330 | namespace LISTEX::INTERNAL { 331 | class CListExHdr final { 332 | public: 333 | void DeleteColumn(int iIndex); 334 | [[nodiscard]] auto GetClientRect()const->RECT; 335 | [[nodiscard]] int GetColumnDataAlign(int iIndex)const; 336 | [[nodiscard]] auto GetHiddenCount()const->UINT; 337 | [[nodiscard]] auto GetImageList(int iList = HDSIL_NORMAL)const->HIMAGELIST; 338 | bool GetItem(int iPos, HDITEMW* pHDI)const; 339 | [[nodiscard]] int GetItemCount()const; 340 | [[nodiscard]] auto GetItemRect(int iIndex)const->RECT; 341 | void HideColumn(int iIndex, bool fHide); 342 | [[nodiscard]] bool IsColumnHidden(int iIndex)const; //Column index. 343 | [[nodiscard]] bool IsColumnSortable(int iIndex)const; 344 | [[nodiscard]] bool IsColumnEditable(int iIndex)const; 345 | auto ProcessMsg(const MSG& msg) -> LRESULT; 346 | void RedrawWindow()const; 347 | void SetDPIScale(float flScale); 348 | void SetHeight(DWORD dwHeight); 349 | void SetFont(const LOGFONTW& lf); 350 | void SetColor(const LISTEXCOLORS& lcs); 351 | void SetColumnColor(int iColumn, COLORREF clrBk, COLORREF clrText); 352 | void SetColumnDataAlign(int iColumn, int iAlign); 353 | void SetColumnIcon(int iColumn, const LISTEXHDRICON& stIcon); 354 | void SetColumnSortable(int iColumn, bool fSortable); 355 | void SetColumnEditable(int iColumn, bool fEditable); 356 | void SetImageList(HIMAGELIST pList, int iList = HDSIL_NORMAL); 357 | void SetItem(int nPos, const HDITEMW& item); 358 | void SetSortable(bool fSortable); 359 | void SetSortArrow(int iColumn, bool fAscending); 360 | void SubclassHeader(HWND hWndHeader); 361 | private: 362 | struct HIDDEN; 363 | struct HDRICON { //Header column icon. 364 | LISTEXHDRICON stIcon; //Icon data struct. 365 | bool fLMPressed { false }; //Left mouse button pressed atm. 366 | }; 367 | struct COLUMNDATA { 368 | UINT uID { }; //Column ID 369 | HDRICON icon; //Column icon. 370 | LISTEXCOLOR clr; //Column colors, text/bk. 371 | int iDataAlign { LVCFMT_LEFT }; 372 | bool fEditable { false }; 373 | bool fSortable { true }; //By default columns are sortable, unless explicitly set to false. 374 | }; 375 | void AddColumnData(const COLUMNDATA& data); 376 | [[nodiscard]] UINT ColumnIndexToID(int iIndex)const; //Returns unique column ID. Must be > 0. 377 | [[nodiscard]] int ColumnIDToIndex(UINT uID)const; 378 | [[nodiscard]] auto GetParent() -> HWND; 379 | [[nodiscard]] auto GetHdrColor(UINT ID)const->PLISTEXCOLOR; 380 | [[nodiscard]] auto GetColumnData(UINT uID) -> COLUMNDATA*; 381 | [[nodiscard]] auto GetColumnData(UINT uID)const->const COLUMNDATA*; 382 | [[nodiscard]] auto GetHdrIcon(UINT ID) -> HDRICON*; 383 | [[nodiscard]] auto GetListParent() -> HWND; 384 | [[nodiscard]] int HitTest(HDHITTESTINFO hhti); 385 | [[nodiscard]] bool IsEditable(UINT ID)const; 386 | [[nodiscard]] auto IsHidden(UINT ID)const->const HIDDEN*; //Internal ColumnID. 387 | [[nodiscard]] bool IsSortable(UINT ID)const; 388 | [[nodiscard]] bool IsWindow()const; 389 | auto OnDestroy() -> LRESULT; 390 | void OnDrawItem(HDC hDC, int iItem, RECT rc, bool fPressed, bool fHighl); 391 | auto OnLayout(const MSG& msg) -> LRESULT; 392 | auto OnLButtonDown(const MSG& msg) -> LRESULT; 393 | auto OnLButtonUp(const MSG& msg) -> LRESULT; 394 | auto OnPaint() -> LRESULT; 395 | auto OnRButtonUp(const MSG& msg) -> LRESULT; 396 | auto OnRButtonDown(const MSG& msg) -> LRESULT; 397 | static auto CALLBACK SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, 398 | UINT_PTR uIDSubclass, DWORD_PTR dwRefData)->LRESULT; 399 | private: 400 | std::vector m_vecHidden; //Hidden columns. 401 | std::vector m_vecColumnData; 402 | HWND m_hWnd { }; //Header window. 403 | HFONT m_hFntHdr { }; 404 | COLORREF m_clrBkNWA { }; //Bk of non working area. 405 | COLORREF m_clrText { }; 406 | COLORREF m_clrBk { }; 407 | COLORREF m_clrHglInactive { }; 408 | COLORREF m_clrHglActive { }; 409 | DWORD m_dwHeaderHeight { 19 }; //Standard (default) height. 410 | UINT m_uSortColumn { 0 }; //ColumnID to draw sorting triangle at. 0 is to avoid triangle before first clicking. 411 | float m_flDPIScale { 1.0F }; 412 | bool m_fSortable { false }; //List-is-sortable global flog. Need to draw sortable triangle or not? 413 | bool m_fSortAscending { }; //Sorting type. 414 | bool m_fLMousePressed { }; 415 | }; 416 | 417 | //Hidden columns. 418 | struct CListExHdr::HIDDEN { 419 | UINT uID { }; 420 | int iPrevPos { }; 421 | int iPrevWidth { }; 422 | }; 423 | } 424 | 425 | using namespace LISTEX::INTERNAL; 426 | 427 | void CListExHdr::DeleteColumn(int iIndex) 428 | { 429 | if (const auto ID = ColumnIndexToID(iIndex); ID > 0) { 430 | std::erase_if(m_vecHidden, [=](const HIDDEN& ref) { return ref.uID == ID; }); 431 | std::erase_if(m_vecColumnData, [=](const COLUMNDATA& ref) { return ref.uID == ID; }); 432 | } 433 | } 434 | 435 | auto CListExHdr::GetClientRect()const->RECT 436 | { 437 | assert(IsWindow()); 438 | RECT rc; 439 | ::GetClientRect(m_hWnd, &rc); 440 | return rc; 441 | } 442 | 443 | UINT CListExHdr::GetHiddenCount()const 444 | { 445 | return static_cast(m_vecHidden.size()); 446 | } 447 | 448 | auto CListExHdr::GetImageList(int iList)const->HIMAGELIST 449 | { 450 | assert(IsWindow()); 451 | return reinterpret_cast(::SendMessageW(m_hWnd, HDM_GETIMAGELIST, iList, 0L)); 452 | } 453 | 454 | bool CListExHdr::GetItem(int iPos, HDITEMW* pHDI)const 455 | { 456 | assert(IsWindow()); 457 | return ::SendMessageW(m_hWnd, HDM_GETITEMW, iPos, reinterpret_cast(pHDI)); 458 | } 459 | 460 | int CListExHdr::GetItemCount()const 461 | { 462 | assert(IsWindow()); 463 | return static_cast(::SendMessageW(m_hWnd, HDM_GETITEMCOUNT, 0, 0L)); 464 | } 465 | 466 | auto CListExHdr::GetItemRect(int iIndex)const->RECT 467 | { 468 | RECT rc; 469 | ::SendMessageW(m_hWnd, HDM_GETITEMRECT, iIndex, reinterpret_cast(&rc)); 470 | return rc; 471 | } 472 | 473 | int CListExHdr::GetColumnDataAlign(int iIndex)const 474 | { 475 | if (const auto pData = GetColumnData(ColumnIndexToID(iIndex)); pData != nullptr) { 476 | return pData->iDataAlign; 477 | } 478 | 479 | return -1; 480 | } 481 | 482 | void CListExHdr::HideColumn(int iIndex, bool fHide) 483 | { 484 | const auto iColumnsCount = GetItemCount(); 485 | if (iIndex >= iColumnsCount) { 486 | return; 487 | } 488 | 489 | const auto ID = ColumnIndexToID(iIndex); 490 | std::vector vecInt(iColumnsCount, 0); 491 | ListView_GetColumnOrderArray(GetParent(), iColumnsCount, vecInt.data()); 492 | HDITEMW hdi { .mask { HDI_WIDTH } }; 493 | GetItem(iIndex, &hdi); 494 | 495 | if (fHide) { //Hide column. 496 | HIDDEN* pHidden; 497 | if (const auto it = std::find_if(m_vecHidden.begin(), m_vecHidden.end(), 498 | [=](const HIDDEN& ref) { return ref.uID == ID; }); it == m_vecHidden.end()) { 499 | pHidden = &m_vecHidden.emplace_back(HIDDEN { .uID { ID } }); 500 | } 501 | else { pHidden = &*it; } 502 | 503 | pHidden->iPrevWidth = hdi.cxy; 504 | if (const auto iter = std::find(vecInt.begin(), vecInt.end(), iIndex); iter != vecInt.end()) { 505 | pHidden->iPrevPos = static_cast(iter - vecInt.begin()); 506 | std::rotate(iter, iter + 1, vecInt.end()); //Moving hiding column to the end of the column array. 507 | } 508 | 509 | ListView_SetColumnOrderArray(GetParent(), iColumnsCount, vecInt.data()); 510 | hdi.cxy = 0; 511 | SetItem(iIndex, hdi); 512 | } 513 | else { //Show column. 514 | const auto pHidden = IsHidden(ID); 515 | if (pHidden == nullptr) { //No such column is hidden. 516 | return; 517 | } 518 | 519 | if (const auto iterRight = std::find(vecInt.rbegin(), vecInt.rend(), iIndex); iterRight != vecInt.rend()) { 520 | const auto iterMid = iterRight + 1; 521 | const auto iterEnd = vecInt.rend() - pHidden->iPrevPos; 522 | if (iterMid < iterEnd) { 523 | std::rotate(iterRight, iterMid, iterEnd); //Moving hidden column id back to its previous place. 524 | } 525 | } 526 | 527 | ListView_SetColumnOrderArray(GetParent(), iColumnsCount, vecInt.data()); 528 | hdi.cxy = pHidden->iPrevWidth; 529 | SetItem(iIndex, hdi); 530 | std::erase_if(m_vecHidden, [=](const HIDDEN& ref) { return ref.uID == ID; }); 531 | } 532 | 533 | RedrawWindow(); 534 | } 535 | 536 | bool CListExHdr::IsColumnHidden(int iIndex)const 537 | { 538 | return IsHidden(ColumnIndexToID(iIndex)) != nullptr; 539 | } 540 | 541 | bool CListExHdr::IsColumnSortable(int iIndex)const 542 | { 543 | return IsSortable(ColumnIndexToID(iIndex)); 544 | } 545 | 546 | bool CListExHdr::IsColumnEditable(int iIndex)const 547 | { 548 | return IsEditable(ColumnIndexToID(iIndex)); 549 | } 550 | 551 | auto CListExHdr::ProcessMsg(const MSG& msg)->LRESULT 552 | { 553 | switch (msg.message) { 554 | case HDM_LAYOUT: return OnLayout(msg); 555 | case WM_DESTROY: return OnDestroy(); 556 | case WM_LBUTTONDOWN: return OnLButtonDown(msg); 557 | case WM_LBUTTONUP: return OnLButtonUp(msg); 558 | case WM_PAINT: return OnPaint(); 559 | case WM_RBUTTONDOWN: return OnRButtonDown(msg); 560 | case WM_RBUTTONUP: return OnRButtonUp(msg); 561 | default: return wnd::DefSubclassProc(msg); 562 | } 563 | } 564 | 565 | void CListExHdr::RedrawWindow()const 566 | { 567 | assert(IsWindow()); 568 | ::RedrawWindow(m_hWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); 569 | } 570 | 571 | void CListExHdr::SetDPIScale(float flScale) 572 | { 573 | m_flDPIScale = flScale; 574 | RedrawWindow(); 575 | } 576 | 577 | void CListExHdr::SetFont(const LOGFONTW& lf) 578 | { 579 | ::DeleteObject(m_hFntHdr); 580 | m_hFntHdr = ::CreateFontIndirectW(&lf); 581 | 582 | //If new font's height is higher than current height (m_dwHeaderHeight), we adjust current height as well. 583 | TEXTMETRICW tm; 584 | const auto hDC = ::GetDC(m_hWnd); 585 | ::SelectObject(hDC, m_hFntHdr); 586 | ::GetTextMetricsW(hDC, &tm); 587 | ::ReleaseDC(m_hWnd, hDC); 588 | const DWORD dwHeightFont = tm.tmHeight + tm.tmExternalLeading + 4; 589 | if (dwHeightFont > m_dwHeaderHeight) { 590 | SetHeight(dwHeightFont); 591 | } 592 | } 593 | 594 | void CListExHdr::SetHeight(DWORD dwHeight) 595 | { 596 | m_dwHeaderHeight = dwHeight; 597 | } 598 | 599 | void CListExHdr::SetColor(const LISTEXCOLORS& lcs) 600 | { 601 | m_clrText = lcs.clrHdrText; 602 | m_clrBk = lcs.clrHdrBk; 603 | m_clrBkNWA = lcs.clrNWABk; 604 | m_clrHglInactive = lcs.clrHdrHglInact; 605 | m_clrHglActive = lcs.clrHdrHglAct; 606 | 607 | RedrawWindow(); 608 | } 609 | 610 | void CListExHdr::SetColumnColor(int iColumn, COLORREF clrBk, COLORREF clrText) 611 | { 612 | const auto ID = ColumnIndexToID(iColumn); 613 | assert(ID > 0); 614 | if (ID == 0) { 615 | return; 616 | } 617 | 618 | if (clrText == -1) { 619 | clrText = m_clrText; 620 | } 621 | 622 | if (const auto pData = GetColumnData(ID); pData != nullptr) { 623 | pData->clr = { .clrBk { clrBk }, .clrText { clrText } }; 624 | } 625 | else { 626 | AddColumnData({ .uID { ID }, .clr { .clrBk { clrBk }, .clrText { clrText } } }); 627 | } 628 | 629 | RedrawWindow(); 630 | } 631 | 632 | void CListExHdr::SetColumnDataAlign(int iColumn, int iAlign) 633 | { 634 | const auto ID = ColumnIndexToID(iColumn); 635 | if (ID == 0) { 636 | assert(false); 637 | return; 638 | } 639 | 640 | if (const auto pData = GetColumnData(ID); pData != nullptr) { 641 | pData->iDataAlign = iAlign; 642 | } 643 | else { 644 | AddColumnData({ .uID { ID }, .clr { .clrBk { m_clrBk }, .clrText { m_clrText } }, .iDataAlign { iAlign } }); 645 | } 646 | } 647 | 648 | void CListExHdr::SetColumnIcon(int iColumn, const LISTEXHDRICON& stIcon) 649 | { 650 | const auto ID = ColumnIndexToID(iColumn); 651 | assert(ID > 0); 652 | if (ID == 0) { 653 | return; 654 | } 655 | 656 | if (const auto pData = GetColumnData(ID); pData != nullptr) { 657 | pData->icon.stIcon = stIcon; 658 | } 659 | else { 660 | AddColumnData({ .uID { ID }, .icon { .stIcon { stIcon } }, .clr { .clrBk { m_clrBk }, .clrText { m_clrText } } }); 661 | } 662 | 663 | RedrawWindow(); 664 | } 665 | 666 | void CListExHdr::SetColumnSortable(int iColumn, bool fSortable) 667 | { 668 | const auto ID = ColumnIndexToID(iColumn); 669 | if (ID == 0) { 670 | assert(false); 671 | return; 672 | } 673 | 674 | if (const auto pData = GetColumnData(ID); pData != nullptr) { 675 | pData->fSortable = fSortable; 676 | } 677 | else { 678 | AddColumnData({ .uID { ID }, .clr { .clrBk { m_clrBk }, .clrText { m_clrText } }, .fSortable { fSortable } }); 679 | } 680 | } 681 | 682 | void CListExHdr::SetColumnEditable(int iColumn, bool fEditable) 683 | { 684 | const auto ID = ColumnIndexToID(iColumn); 685 | if (ID == 0) { 686 | assert(false); 687 | return; 688 | } 689 | 690 | if (const auto pData = GetColumnData(ID); pData != nullptr) { 691 | pData->fEditable = fEditable; 692 | } 693 | else { 694 | AddColumnData({ .uID { ID }, .clr { .clrBk { m_clrBk }, .clrText { m_clrText } }, .fEditable { fEditable } }); 695 | } 696 | } 697 | 698 | void CListExHdr::SetImageList(HIMAGELIST pList, int iList) 699 | { 700 | ::SendMessageW(m_hWnd, HDM_SETIMAGELIST, iList, reinterpret_cast(pList)); 701 | } 702 | 703 | void CListExHdr::SetSortable(bool fSortable) 704 | { 705 | m_fSortable = fSortable; 706 | RedrawWindow(); 707 | } 708 | 709 | void CListExHdr::SetSortArrow(int iColumn, bool fAscending) 710 | { 711 | UINT ID { 0 }; 712 | if (iColumn >= 0) { 713 | ID = ColumnIndexToID(iColumn); 714 | assert(ID > 0); 715 | if (ID == 0) { 716 | return; 717 | } 718 | } 719 | 720 | m_uSortColumn = ID; 721 | m_fSortAscending = fAscending; 722 | RedrawWindow(); 723 | } 724 | 725 | void CListExHdr::SubclassHeader(HWND hWndHeader) 726 | { 727 | assert(hWndHeader != nullptr); 728 | ::SetWindowSubclass(hWndHeader, SubclassProc, reinterpret_cast(this), 0); 729 | m_hWnd = hWndHeader; 730 | } 731 | 732 | 733 | //CListExHdr private methods: 734 | 735 | void CListExHdr::AddColumnData(const COLUMNDATA& data) 736 | { 737 | m_vecColumnData.emplace_back(data); 738 | } 739 | 740 | UINT CListExHdr::ColumnIndexToID(int iIndex)const 741 | { 742 | //Each column has unique internal identifier in HDITEMW::lParam. 743 | HDITEMW hdi { .mask { HDI_LPARAM } }; 744 | const auto ret = GetItem(iIndex, &hdi); 745 | 746 | return ret ? static_cast(hdi.lParam) : 0; 747 | } 748 | 749 | int CListExHdr::ColumnIDToIndex(UINT uID)const 750 | { 751 | for (int iterColumns = 0; iterColumns < GetItemCount(); ++iterColumns) { 752 | HDITEMW hdi { .mask { HDI_LPARAM } }; 753 | GetItem(iterColumns, &hdi); 754 | if (static_cast(hdi.lParam) == uID) { 755 | return iterColumns; 756 | } 757 | } 758 | 759 | return -1; 760 | } 761 | 762 | auto CListExHdr::GetParent()->HWND 763 | { 764 | assert(IsWindow()); 765 | return ::GetParent(m_hWnd); 766 | } 767 | 768 | auto CListExHdr::GetHdrColor(UINT ID)const->PLISTEXCOLOR 769 | { 770 | const auto pData = GetColumnData(ID); 771 | return pData != nullptr ? &pData->clr : nullptr; 772 | } 773 | 774 | auto CListExHdr::GetColumnData(UINT uID)->COLUMNDATA* 775 | { 776 | const auto iter = std::find_if(m_vecColumnData.begin(), m_vecColumnData.end(), [=](const COLUMNDATA& ref) { 777 | return ref.uID == uID; }); 778 | 779 | return iter == m_vecColumnData.end() ? nullptr : &*iter; 780 | } 781 | 782 | auto CListExHdr::GetColumnData(UINT uID)const->const COLUMNDATA* 783 | { 784 | const auto iter = std::find_if(m_vecColumnData.begin(), m_vecColumnData.end(), [=](const COLUMNDATA& ref) { 785 | return ref.uID == uID; }); 786 | 787 | return iter == m_vecColumnData.end() ? nullptr : &*iter; 788 | } 789 | 790 | auto CListExHdr::GetHdrIcon(UINT ID)->CListExHdr::HDRICON* 791 | { 792 | if (GetImageList() == nullptr) { 793 | return nullptr; 794 | } 795 | 796 | const auto pData = GetColumnData(ID); 797 | return pData != nullptr && pData->icon.stIcon.iIndex != -1 ? &pData->icon : nullptr; 798 | } 799 | 800 | void CListExHdr::SetItem(int nPos, const HDITEMW& item) 801 | { 802 | assert(IsWindow()); 803 | ::SendMessageW(m_hWnd, HDM_SETITEMW, nPos, reinterpret_cast(&item)); 804 | } 805 | 806 | auto CListExHdr::GetListParent()->HWND 807 | { 808 | const auto hWnd = GetParent(); 809 | assert(hWnd != nullptr); 810 | return ::GetParent(hWnd); 811 | } 812 | 813 | int CListExHdr::HitTest(const HDHITTESTINFO hhti) 814 | { 815 | assert(IsWindow()); 816 | return static_cast(::SendMessageW(m_hWnd, HDM_HITTEST, 0, reinterpret_cast(&hhti))); 817 | } 818 | 819 | auto CListExHdr::IsHidden(UINT ID)const->const HIDDEN* 820 | { 821 | const auto it = std::find_if(m_vecHidden.begin(), m_vecHidden.end(), [=](const HIDDEN& ref) { return ref.uID == ID; }); 822 | return it != m_vecHidden.end() ? &*it : nullptr; 823 | } 824 | 825 | bool CListExHdr::IsSortable(UINT ID)const 826 | { 827 | const auto pData = GetColumnData(ID); 828 | return pData == nullptr || pData->fSortable; //It's sortable unless found explicitly as false. 829 | } 830 | 831 | bool CListExHdr::IsWindow()const 832 | { 833 | return ::IsWindow(m_hWnd); 834 | } 835 | 836 | bool CListExHdr::IsEditable(UINT ID)const 837 | { 838 | const auto pData = GetColumnData(ID); 839 | return pData != nullptr && pData->fEditable; //It's editable only if found explicitly as true. 840 | } 841 | 842 | auto CListExHdr::OnDestroy()->LRESULT 843 | { 844 | m_vecHidden.clear(); 845 | m_vecColumnData.clear(); 846 | 847 | return 0; 848 | } 849 | 850 | void CListExHdr::OnDrawItem(HDC hDC, int iItem, RECT rc, bool fPressed, bool fHighl) 851 | { 852 | const wnd::CRect rcOrig(rc); 853 | const wnd::CDC dc(hDC); 854 | //Non working area after last column. Or if column is resized to zero width. 855 | if (iItem < 0 || rcOrig.IsRectEmpty()) { 856 | dc.FillSolidRect(rcOrig, m_clrBkNWA); 857 | return; 858 | } 859 | 860 | const auto ID = ColumnIndexToID(iItem); 861 | const auto pClr = GetHdrColor(ID); 862 | const auto clrText { pClr != nullptr ? pClr->clrText : m_clrText }; 863 | const auto clrBk { fHighl ? (fPressed ? m_clrHglActive : m_clrHglInactive) 864 | : (pClr != nullptr ? pClr->clrBk : m_clrBk) }; 865 | 866 | dc.FillSolidRect(rcOrig, clrBk); 867 | dc.SetTextColor(clrText); 868 | dc.SelectObject(m_hFntHdr); 869 | 870 | //Set item's text buffer first char to zero, then getting item's text and Draw it. 871 | wchar_t buffText[256] { L'\0' }; 872 | HDITEMW hdItem { .mask { HDI_FORMAT | HDI_TEXT }, .pszText { buffText }, .cchTextMax { std::size(buffText) } }; 873 | GetItem(iItem, &hdItem); 874 | 875 | //Draw icon for column, if any. 876 | auto iTextIndentLeft { 5 }; //Left text indent. 877 | if (const auto pIcon = GetHdrIcon(ID); pIcon != nullptr) { //If column has an icon. 878 | const auto pImgList = GetImageList(LVSIL_NORMAL); 879 | int iCX { }; 880 | int iCY { }; 881 | ::ImageList_GetIconSize(pImgList, &iCX, &iCY); //Icon dimensions. 882 | const auto pt = rcOrig.TopLeft() + pIcon->stIcon.pt; 883 | ::ImageList_DrawEx(pImgList, pIcon->stIcon.iIndex, dc, pt.x, pt.y, 0, 0, CLR_NONE, CLR_NONE, ILD_NORMAL); 884 | iTextIndentLeft += pIcon->stIcon.pt.x + iCX; 885 | } 886 | 887 | UINT uFormat { DT_LEFT }; 888 | switch (hdItem.fmt & HDF_JUSTIFYMASK) { 889 | case HDF_CENTER: 890 | uFormat = DT_CENTER; 891 | break; 892 | case HDF_RIGHT: 893 | uFormat = DT_RIGHT; 894 | break; 895 | default: 896 | break; 897 | } 898 | 899 | constexpr auto iTextIndentRight { 4 }; 900 | wnd::CRect rcText { rcOrig.left + iTextIndentLeft, rcOrig.top, rcOrig.right - iTextIndentRight, rcOrig.bottom }; 901 | if (std::wstring_view(buffText).find(L'\n') != std::wstring_view::npos) { 902 | //If it's multiline text, first — calculate rect for the text, 903 | //with DT_CALCRECT flag (not drawing anything), 904 | //and then calculate rect for final vertical text alignment. 905 | wnd::CRect rcCalcText; 906 | dc.DrawTextW(buffText, rcCalcText, uFormat | DT_CALCRECT); 907 | rcText.top = rcText.Height() / 2 - rcCalcText.Height() / 2; 908 | } 909 | else { 910 | uFormat |= DT_VCENTER | DT_SINGLELINE; 911 | } 912 | 913 | //Draw Header Text. 914 | dc.DrawTextW(buffText, rcText, uFormat); 915 | 916 | //Draw sortable triangle (arrow). 917 | if (m_fSortable && IsSortable(ID) && ID == m_uSortColumn) { 918 | static const auto penArrow = ::CreatePen(PS_SOLID, 1, RGB(90, 90, 90)); 919 | dc.SelectObject(penArrow); 920 | dc.SelectObject(::GetSysColorBrush(COLOR_3DFACE)); 921 | 922 | if (m_fSortAscending) { //Draw the UP arrow. 923 | const auto lXTop = static_cast(10 * m_flDPIScale); 924 | const auto lYTop = static_cast(3 * m_flDPIScale); 925 | const auto lXLeft = static_cast(15 * m_flDPIScale); 926 | const auto lXRight = static_cast(5 * m_flDPIScale); 927 | const auto lYLeftRight = static_cast(8 * m_flDPIScale); 928 | const POINT arrPt[] { { .x { rcOrig.right - lXTop }, .y { lYTop } }, 929 | { .x { rcOrig.right - lXLeft }, .y { lYLeftRight } }, 930 | { .x { rcOrig.right - lXRight }, .y { lYLeftRight } } }; 931 | dc.Polygon(arrPt, 3); 932 | } 933 | else { //Draw the DOWN arrow. 934 | const auto lXLeft = static_cast(15 * m_flDPIScale); 935 | const auto lXRight = static_cast(5 * m_flDPIScale); 936 | const auto lYLeftRight = static_cast(3 * m_flDPIScale); 937 | const auto lXBottom = static_cast(10 * m_flDPIScale); 938 | const auto lYBottom = static_cast(8 * m_flDPIScale); 939 | const POINT arrPt[] { { .x { rcOrig.right - lXLeft }, .y { lYLeftRight } }, 940 | { .x { rcOrig.right - lXRight }, .y { lYLeftRight } }, 941 | { .x { rcOrig.right - lXBottom }, .y { lYBottom } }, }; 942 | dc.Polygon(arrPt, 3); 943 | } 944 | } 945 | 946 | static const auto penGrid = ::CreatePen(PS_SOLID, 2, ::GetSysColor(COLOR_3DFACE)); 947 | dc.SelectObject(penGrid); 948 | dc.MoveTo(rcOrig.TopLeft()); 949 | dc.LineTo(rcOrig.left, rcOrig.bottom); 950 | if (iItem == GetItemCount() - 1) { //Last item. 951 | dc.MoveTo(rcOrig.right, rcOrig.top); 952 | dc.LineTo(rcOrig.BottomRight()); 953 | } 954 | } 955 | 956 | auto CListExHdr::OnLayout(const MSG& msg)->LRESULT 957 | { 958 | wnd::DefSubclassProc(msg); 959 | const auto pHDL = reinterpret_cast(msg.lParam); 960 | pHDL->pwpos->cy = m_dwHeaderHeight; //New header height. 961 | pHDL->prc->top = m_dwHeaderHeight; //Decreasing list's height begining by the new header's height. 962 | 963 | return TRUE; 964 | } 965 | 966 | auto CListExHdr::OnLButtonDown(const MSG& msg)->LRESULT 967 | { 968 | wnd::DefSubclassProc(msg); 969 | 970 | const POINT pt { .x { wnd::GetXLPARAM(msg.lParam) }, .y { wnd::GetYLPARAM(msg.lParam) } }; 971 | if (const auto iItem = HitTest({ .pt { pt } }); iItem >= 0) { 972 | m_fLMousePressed = true; 973 | ::SetCapture(m_hWnd); 974 | const auto ID = ColumnIndexToID(iItem); 975 | if (const auto pIcon = GetHdrIcon(ID); pIcon != nullptr 976 | && pIcon->stIcon.fClickable && IsHidden(ID) == nullptr) { 977 | int iCX { }; 978 | int iCY { }; 979 | ::ImageList_GetIconSize(GetImageList(), &iCX, &iCY); 980 | const wnd::CRect rcColumn = GetItemRect(iItem); 981 | 982 | const auto ptIcon = rcColumn.TopLeft() + pIcon->stIcon.pt; 983 | if (const wnd::CRect rcIcon(ptIcon, SIZE { iCX, iCY }); rcIcon.PtInRect(pt)) { 984 | pIcon->fLMPressed = true; 985 | return 0; //Do not invoke default handler. 986 | } 987 | } 988 | } 989 | 990 | return wnd::DefSubclassProc(msg); 991 | } 992 | 993 | auto CListExHdr::OnLButtonUp(const MSG& msg)->LRESULT 994 | { 995 | wnd::DefSubclassProc(msg); 996 | 997 | m_fLMousePressed = false; 998 | ::ReleaseCapture(); 999 | RedrawWindow(); 1000 | 1001 | const POINT point { .x { wnd::GetXLPARAM(msg.lParam) }, .y { wnd::GetYLPARAM(msg.lParam) } }; 1002 | if (const auto iItem = HitTest({ .pt { point } }); iItem >= 0) { 1003 | for (auto& ref : m_vecColumnData) { 1004 | if (!ref.icon.fLMPressed || ref.icon.stIcon.iIndex == -1) { 1005 | continue; 1006 | } 1007 | 1008 | int iCX { }; 1009 | int iCY { }; 1010 | ::ImageList_GetIconSize(GetImageList(), &iCX, &iCY); 1011 | const wnd::CRect rcColumn = GetItemRect(iItem); 1012 | const auto pt = rcColumn.TopLeft() + ref.icon.stIcon.pt; 1013 | const wnd::CRect rcIcon(pt, SIZE { iCX, iCY }); 1014 | 1015 | if (rcIcon.PtInRect(point)) { 1016 | for (auto& iterData : m_vecColumnData) { 1017 | iterData.icon.fLMPressed = false; //Remove fLMPressed flag from all columns. 1018 | } 1019 | 1020 | const auto hWndParent = GetParent(); 1021 | const auto uCtrlId = static_cast(::GetDlgCtrlID(hWndParent)); 1022 | const NMHEADERW hdr { .hdr { hWndParent, uCtrlId, LISTEX_MSG_HDRICONCLICK }, 1023 | .iItem { ColumnIDToIndex(ref.uID) } }; 1024 | ::SendMessageW(GetListParent(), WM_NOTIFY, static_cast(uCtrlId), reinterpret_cast(&hdr)); 1025 | } 1026 | break; 1027 | } 1028 | } 1029 | 1030 | return 0; 1031 | } 1032 | 1033 | auto CListExHdr::OnPaint()->LRESULT 1034 | { 1035 | const wnd::CPaintDC dcPaint(m_hWnd); 1036 | wnd::CRect rcClient; 1037 | ::GetClientRect(m_hWnd, rcClient); 1038 | const wnd::CMemDC dcMem(dcPaint, rcClient); 1039 | wnd::CRect rcItem; 1040 | const auto iItems = GetItemCount(); 1041 | LONG lMax = 0; 1042 | 1043 | for (int iItem = 0; iItem < iItems; ++iItem) { 1044 | POINT ptCur; 1045 | ::GetCursorPos(&ptCur); 1046 | ::ScreenToClient(m_hWnd, &ptCur); 1047 | const auto iHit = HitTest({ .pt { ptCur } }); 1048 | const auto fHighl = iHit == iItem; 1049 | const auto fPressed = m_fLMousePressed && fHighl; 1050 | rcItem = GetItemRect(iItem); 1051 | OnDrawItem(dcMem, iItem, rcItem, fPressed, fHighl); 1052 | lMax = (std::max)(lMax, rcItem.right); 1053 | } 1054 | 1055 | // Draw "tail border": 1056 | if (iItems == 0) { 1057 | rcItem = rcClient; 1058 | ++rcItem.right; 1059 | } 1060 | else { 1061 | rcItem.left = lMax; 1062 | rcItem.right = rcClient.right + 1; 1063 | } 1064 | 1065 | OnDrawItem(dcMem, -1, rcItem, FALSE, FALSE); 1066 | 1067 | return 0; 1068 | } 1069 | 1070 | auto CListExHdr::OnRButtonDown(const MSG& msg)->LRESULT 1071 | { 1072 | const POINT pt { .x { wnd::GetXLPARAM(msg.lParam) }, .y { wnd::GetYLPARAM(msg.lParam) } }; 1073 | const auto hWndParent = GetParent(); 1074 | const auto uCtrlId = static_cast(::GetDlgCtrlID(hWndParent)); 1075 | const auto iItem = HitTest({ .pt { pt } }); 1076 | NMHEADERW hdr { .hdr { hWndParent, uCtrlId, LISTEX_MSG_HDRRBTNDOWN }, .iItem { iItem } }; 1077 | ::SendMessageW(GetListParent(), WM_NOTIFY, static_cast(uCtrlId), reinterpret_cast(&hdr)); 1078 | 1079 | return wnd::DefSubclassProc(msg); 1080 | } 1081 | 1082 | auto CListExHdr::OnRButtonUp(const MSG& msg)->LRESULT 1083 | { 1084 | const POINT pt { .x { wnd::GetXLPARAM(msg.lParam) }, .y { wnd::GetYLPARAM(msg.lParam) } }; 1085 | const auto hWndParent = GetParent(); 1086 | const auto uCtrlId = static_cast(::GetDlgCtrlID(hWndParent)); 1087 | const auto iItem = HitTest({ .pt { pt } }); 1088 | NMHEADERW hdr { { hWndParent, uCtrlId, LISTEX_MSG_HDRRBTNUP } }; 1089 | hdr.iItem = iItem; 1090 | ::SendMessageW(GetListParent(), WM_NOTIFY, static_cast(uCtrlId), reinterpret_cast(&hdr)); 1091 | 1092 | return wnd::DefSubclassProc(msg); 1093 | } 1094 | 1095 | auto CListExHdr::SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, 1096 | UINT_PTR uIDSubclass, DWORD_PTR /*dwRefData*/) -> LRESULT { 1097 | if (uMsg == WM_NCDESTROY) { 1098 | ::RemoveWindowSubclass(hWnd, SubclassProc, uIDSubclass); 1099 | } 1100 | 1101 | const auto pHdr = reinterpret_cast(uIDSubclass); 1102 | return pHdr->ProcessMsg({ .hwnd { hWnd }, .message { uMsg }, .wParam { wParam }, .lParam { lParam } }); 1103 | } 1104 | 1105 | 1106 | //CListEx. 1107 | namespace LISTEX { 1108 | export class CListEx final { 1109 | public: 1110 | bool Create(const LISTEXCREATE& lcs); 1111 | void CreateDialogCtrl(UINT uCtrlID, HWND hWndParent); 1112 | bool DeleteAllItems(); 1113 | bool DeleteColumn(int iIndex); 1114 | bool DeleteItem(int iItem); 1115 | void DrawItem(LPDRAWITEMSTRUCT pDIS); 1116 | bool EnsureVisible(int iItem, bool fPartialOK)const; 1117 | [[nodiscard]] auto GetColors()const->const LISTEXCOLORS &; 1118 | [[nodiscard]] auto GetColumnSortMode(int iColumn)const->EListExSortMode; 1119 | [[nodiscard]] int GetDlgCtrlID()const; 1120 | [[nodiscard]] auto GetExtendedStyle()const->DWORD; 1121 | [[nodiscard]] auto GetFont()const->LOGFONTW; 1122 | [[nodiscard]] auto GetHWND()const->HWND; 1123 | [[nodiscard]] auto GetImageList(int iList = HDSIL_NORMAL)const->HIMAGELIST; 1124 | void GetItem(LVITEMW* pItem)const; 1125 | [[nodiscard]] int GetItemCount()const; 1126 | [[nodiscard]] auto GetItemData(int iItem)const->DWORD_PTR; 1127 | [[nodiscard]] auto GetItemRect(int iItem, int iArea)const->RECT; 1128 | [[nodiscard]] auto GetItemText(int iItem, int iSubItem)const->std::wstring; 1129 | [[nodiscard]] int GetNextItem(int iItem, int iFlags)const; 1130 | [[nodiscard]] auto GetSelectedCount()const->UINT; 1131 | [[nodiscard]] int GetSelectionMark()const; 1132 | [[nodiscard]] int GetSortColumn()const; 1133 | [[nodiscard]] bool GetSortAscending()const; 1134 | [[nodiscard]] auto GetSubItemRect(int iItem, int iSubItem, int iArea)const->RECT; 1135 | [[nodiscard]] int GetTopIndex()const; 1136 | void HideColumn(int iIndex, bool fHide); 1137 | bool HitTest(LVHITTESTINFO* pHTI)const; 1138 | int InsertColumn(int iCol, const LVCOLUMNW* pColumn)const; 1139 | int InsertColumn(int iCol, const LVCOLUMNW* pColumn, int iDataAlign, bool fEditable = false); 1140 | int InsertColumn(int iCol, LPCWSTR pwszName, int iFormat = LVCFMT_LEFT, int iWidth = -1, 1141 | int iSubItem = -1, int iDataAlign = LVCFMT_LEFT, bool fEditable = false); 1142 | int InsertItem(const LVITEMW* pItem)const; 1143 | int InsertItem(int iItem, LPCWSTR pwszName)const; 1144 | int InsertItem(int iItem, LPCWSTR pwszName, int iImage)const; 1145 | int InsertItem(UINT uMask, int iItem, LPCWSTR pwszName, UINT uState, UINT uStateMask, int iImage, LPARAM lParam)const; 1146 | [[nodiscard]] bool IsCreated()const; 1147 | [[nodiscard]] bool IsColumnSortable(int iColumn); 1148 | [[nodiscard]] auto MapIndexToID(UINT uIndex)const->UINT; 1149 | [[nodiscard]] auto MapIDToIndex(UINT uID)const->UINT; 1150 | void MeasureItem(LPMEASUREITEMSTRUCT pMIS); 1151 | auto ProcessMsg(const MSG& msg) -> LRESULT; 1152 | void RedrawWindow()const; 1153 | void ResetSort(); //Reset all the sort by any column to its default state. 1154 | void Scroll(SIZE size)const; 1155 | void SetColors(const LISTEXCOLORS& lcs); 1156 | void SetColumn(int iCol, const LVCOLUMNW* pColumn)const; 1157 | void SetColumnEditable(int iColumn, bool fEditable); 1158 | void SetColumnSortMode(int iColumn, bool fSortable, EListExSortMode eSortMode = EListExSortMode::SORT_LEX); 1159 | auto SetExtendedStyle(DWORD dwExStyle)const->DWORD; 1160 | void SetFont(const LOGFONTW& lf); 1161 | void SetHdrColumnColor(int iColumn, COLORREF clrBk, COLORREF clrText = -1); 1162 | void SetHdrColumnIcon(int iColumn, const LISTEXHDRICON& stIcon); //Icon for a given column. 1163 | void SetHdrFont(const LOGFONTW& lf); 1164 | void SetHdrHeight(DWORD dwHeight); 1165 | void SetHdrImageList(HIMAGELIST pList); 1166 | auto SetImageList(HIMAGELIST hList, int iListType) -> HIMAGELIST; 1167 | bool SetItem(const LVITEMW* pItem)const; 1168 | void SetItemCountEx(int iCount, DWORD dwFlags = LVSICF_NOINVALIDATEALL)const; 1169 | bool SetItemData(int iItem, DWORD_PTR dwData)const; 1170 | bool SetItemState(int iItem, LVITEMW* pItem)const; 1171 | bool SetItemState(int iItem, UINT uState, UINT uStateMask)const; 1172 | void SetItemText(int iItem, int iSubItem, LPCWSTR pwszText); 1173 | void SetRedraw(bool fRedraw)const; 1174 | void SetSortable(bool fSortable, PFNLVCOMPARE pfnCompare = nullptr, 1175 | EListExSortMode eSortMode = EListExSortMode::SORT_LEX); 1176 | void SetWindowPos(HWND hWndAfter, int iX, int iY, int iWidth, int iHeight, UINT uFlags = SWP_NOACTIVATE | SWP_NOZORDER); 1177 | bool ShowWindow(int iCmdShow)const; 1178 | void SortItemsEx(PFNLVCOMPARE pfnCompare, DWORD_PTR dwData)const; 1179 | void Update(int iItem)const; 1180 | static auto CALLBACK DefCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)->int; 1181 | private: 1182 | struct ITEMDATA; 1183 | bool EditInPlaceShow(bool fShow = true); 1184 | [[nodiscard]] long GetFontSize()const; 1185 | [[nodiscard]] auto GetHeader() -> CListExHdr& { return m_Hdr; } 1186 | void FontSizeIncDec(bool fInc); 1187 | [[nodiscard]] auto GetCustomColor(int iItem, int iSubItem)const->std::optional; 1188 | [[nodiscard]] int GetIcon(int iItem, int iSubItem)const; //Does cell have an icon associated. 1189 | [[nodiscard]] auto GetTooltip(int iItem, int iSubItem)const->std::optional; 1190 | [[nodiscard]] bool IsWindow()const; 1191 | auto OnCommand(const MSG& msg) -> LRESULT; 1192 | auto OnDestroy() -> LRESULT; 1193 | void OnEditInPlaceEnterPressed(); 1194 | void OnEditInPlaceKillFocus(); 1195 | auto OnEraseBkgnd() -> LRESULT; 1196 | auto OnHScroll(const MSG& msg) -> LRESULT; 1197 | auto OnLButtonDblClk(const MSG& msg) -> LRESULT; 1198 | auto OnLButtonDown(const MSG& msg) -> LRESULT; 1199 | auto OnLButtonUp(const MSG& msg) -> LRESULT; 1200 | auto OnMouseMove(const MSG& msg) -> LRESULT; 1201 | auto OnMouseWheel(const MSG& msg) -> LRESULT; 1202 | auto OnNotify(const MSG& msg) -> LRESULT; 1203 | void OnNotifyEditInPlace(NMHDR* pNMHDR); 1204 | auto OnPaint() -> LRESULT; 1205 | auto OnTimer(const MSG& msg) -> LRESULT; 1206 | auto OnVScroll(const MSG& msg) -> LRESULT; 1207 | auto ParseItemData(int iItem, int iSubitem) -> std::vector; 1208 | void RecalcMeasure()const; 1209 | void SetFontSize(long lSize); 1210 | void TTCellShow(bool fShow, bool fTimer = false); 1211 | void TTLinkShow(bool fShow, bool fTimer = false); 1212 | void TTHLShow(bool fShow, UINT uRow); //Tooltips for high latency mode. 1213 | static auto CALLBACK SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, 1214 | UINT_PTR uIDSubclass, DWORD_PTR dwRefData)->LRESULT; 1215 | static auto CALLBACK EditSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, 1216 | UINT_PTR uIDSubclass, DWORD_PTR dwRefData)->LRESULT; 1217 | private: 1218 | static constexpr ULONG_PTR m_uIDTTTCellActivate { 0x01 }; //Cell tool-tip activate-timer ID. 1219 | static constexpr ULONG_PTR m_uIDTTTCellCheck { 0x02 }; //Cell tool-tip check-timer ID. 1220 | static constexpr ULONG_PTR m_uIDTTTLinkActivate { 0x03 }; //Link tool-tip activate-timer ID. 1221 | static constexpr ULONG_PTR m_uIDTTTLinkCheck { 0x04 }; //Link tool-tip check-timer ID. 1222 | static constexpr auto m_uIDEditInPlace { 0x01U }; //In place edit-box ID. 1223 | struct COLUMNDATA { 1224 | int iIndex { }; 1225 | EListExSortMode eSortMode { }; 1226 | }; 1227 | CListExHdr m_Hdr; //List header control. 1228 | HWND m_hWnd { }; //Main window. 1229 | HFONT m_hFntList { }; //Default list font. 1230 | HFONT m_hFntListUnderline { }; //Underlined list font, for links. 1231 | HPEN m_hPenGrid { }; //Pen for list lines between cells. 1232 | HWND m_hWndCellTT { }; //Cells tool-tip window. 1233 | HWND m_hWndLinkTT { }; //Links tool-tip window. 1234 | HWND m_hWndRowTT { }; //Tooltip window for row in m_fHighLatency mode. 1235 | HWND m_hWndEditInPlace { }; //Edit box for in-place cells editing. 1236 | wnd::CRect m_rcLinkCurr; //Current link's rect; 1237 | LISTEXCOLORS m_stColors { }; 1238 | std::vector m_vecColumnData; //Column data. 1239 | std::wstring m_wstrTTText; //Tool-tip current text. 1240 | std::wstring m_wstrTTCaption; //Tool-tip current caption. 1241 | std::chrono::steady_clock::time_point m_tmTT; //Start time of the tooltip. 1242 | LVHITTESTINFO m_htiCurrCell { }; //Cells hit struct for tool-tip. 1243 | LVHITTESTINFO m_htiCurrLink { }; //Links hit struct for tool-tip. 1244 | LVHITTESTINFO m_htiEdit { }; //Hit struct for in-place editing. 1245 | PFNLVCOMPARE m_pfnCompare { }; //Pointer to a user provided compare func. 1246 | DWORD m_dwGridWidth { }; //Grid width. 1247 | DWORD m_dwTTDelayTime { }; //Tooltip delay before showing, in milliseconds. 1248 | DWORD m_dwTTShowTime { }; //Tooltip show up time, in milliseconds. 1249 | POINT m_ptTTOffset { }; //Tooltip offset from the cursor point. 1250 | UINT m_uHLItem { }; //High latency Vscroll item. 1251 | int m_iSortColumn { -1 }; //Currently clicked header column. 1252 | int m_iLOGPIXELSY { }; //GetDeviceCaps(LOGPIXELSY) constant. 1253 | EListExSortMode m_eDefSortMode { EListExSortMode::SORT_LEX }; //Default sorting mode. 1254 | bool m_fCreated { false }; //Is created. 1255 | bool m_fHighLatency { false }; //High latency flag. 1256 | bool m_fSortable { false }; //Is list sortable. 1257 | bool m_fSortAsc { false }; //Sorting type (ascending, descending). 1258 | bool m_fLinks { false }; //Enable links support. 1259 | bool m_fLinkUnderline { false };//Links are displayed underlined or not. 1260 | bool m_fLinkTooltip { false }; //Show links toolips. 1261 | bool m_fEditSingleClick { false };//Cells editable with single mouse click or double? 1262 | bool m_fVirtual { false }; //Whether list is virtual (LVS_OWNERDATA) or not. 1263 | bool m_fCellTTActive { false }; //Is cell's tool-tip shown atm. 1264 | bool m_fLinkTTActive { false }; //Is link's tool-tip shown atm. 1265 | bool m_fLDownAtLink { false }; //Left mouse down on link. 1266 | bool m_fHLFlag { false }; //High latency Vscroll flag. 1267 | }; 1268 | 1269 | //Text and links in the cell. 1270 | struct CListEx::ITEMDATA { 1271 | ITEMDATA(int iIconIndex, wnd::CRect rc) : rc(rc), iIconIndex(iIconIndex) { }; //Ctor for just image index. 1272 | ITEMDATA(std::wstring_view wsvText, std::wstring_view wsvLink, std::wstring_view wsvTitle, 1273 | wnd::CRect rc, bool fLink = false, bool fTitle = false) : 1274 | wstrText(wsvText), wstrLink(wsvLink), wstrTitle(wsvTitle), rc(rc), fLink(fLink), fTitle(fTitle) { 1275 | } 1276 | std::wstring wstrText; //Visible text. 1277 | std::wstring wstrLink; //Text within link tag. 1278 | std::wstring wstrTitle; //Text within title <...title="textFromHere"> tag. 1279 | wnd::CRect rc; //Rect text belongs to. 1280 | int iIconIndex { -1 }; //Icon index in the image list, if any. 1281 | bool fLink { false }; //Is it just a text (wstrLink is empty) or text with link? 1282 | bool fTitle { false }; //Is it a link with custom title (wstrTitle is not empty)? 1283 | }; 1284 | } 1285 | 1286 | using namespace LISTEX; 1287 | 1288 | bool CListEx::Create(const LISTEXCREATE& lcs) 1289 | { 1290 | assert(!IsCreated()); 1291 | if (IsCreated()) { 1292 | return false; 1293 | } 1294 | 1295 | HWND hWnd { }; //Main window. 1296 | //Header subclassing is available only in the LVS_REPORT mode. 1297 | auto dwStyle = lcs.dwStyle | WS_CHILD | LVS_REPORT | LVS_OWNERDRAWFIXED; 1298 | if (lcs.fDialogCtrl) { 1299 | hWnd = ::GetDlgItem(lcs.hWndParent, lcs.uID); 1300 | if (hWnd == nullptr) { 1301 | assert(false); 1302 | return false; 1303 | } 1304 | 1305 | dwStyle |= ::GetWindowLongPtrW(hWnd, GWL_STYLE); 1306 | ::SetWindowLongPtrW(hWnd, GWL_STYLE, dwStyle); 1307 | } 1308 | else { 1309 | const wnd::CRect rc = lcs.rect; 1310 | if (hWnd = ::CreateWindowExW(lcs.dwExStyle, WC_LISTVIEWW, nullptr, dwStyle, rc.left, rc.top, rc.Width(), 1311 | rc.Height(), lcs.hWndParent, reinterpret_cast(static_cast(lcs.uID)), nullptr, this); 1312 | hWnd == nullptr) { 1313 | assert(false); 1314 | return false; 1315 | } 1316 | } 1317 | 1318 | if (::SetWindowSubclass(hWnd, SubclassProc, reinterpret_cast(this), 0) == FALSE) { 1319 | assert(false); 1320 | return false; 1321 | } 1322 | 1323 | GetHeader().SubclassHeader(::GetDlgItem(hWnd, 0)); 1324 | 1325 | m_fVirtual = dwStyle & LVS_OWNERDATA; 1326 | m_hWnd = hWnd; 1327 | 1328 | if (lcs.pColors != nullptr) { 1329 | m_stColors = *lcs.pColors; 1330 | } 1331 | 1332 | m_fSortable = lcs.fSortable; 1333 | m_fLinks = lcs.fLinks; 1334 | m_fLinkUnderline = lcs.fLinkUnderline; 1335 | m_fLinkTooltip = lcs.fLinkTooltip; 1336 | m_fHighLatency = lcs.fHighLatency; 1337 | m_fEditSingleClick = lcs.fEditSingleClick; 1338 | m_dwGridWidth = lcs.dwWidthGrid; 1339 | m_dwTTDelayTime = lcs.dwTTDelayTime; 1340 | m_dwTTShowTime = lcs.dwTTShowTime; 1341 | m_ptTTOffset = lcs.ptTTOffset; 1342 | 1343 | //Cell Tooltip. 1344 | if (m_hWndCellTT = ::CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, nullptr, lcs.dwTTStyleCell, CW_USEDEFAULT, 1345 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, nullptr, nullptr); m_hWndCellTT == nullptr) { 1346 | assert(false); 1347 | return false; 1348 | } 1349 | 1350 | //The TTF_TRACK flag should not be used with the TTS_BALLOON tooltips, because in this case 1351 | //the balloon will always be positioned _below_ provided coordinates. 1352 | const TTTOOLINFOW ttiCell { .cbSize { sizeof(TTTOOLINFOW) }, 1353 | .uFlags { static_cast(lcs.dwTTStyleCell & TTS_BALLOON ? 0 : TTF_TRACK) } }; 1354 | ::SendMessageW(m_hWndCellTT, TTM_ADDTOOLW, 0, reinterpret_cast(&ttiCell)); 1355 | ::SendMessageW(m_hWndCellTT, TTM_SETMAXTIPWIDTH, 0, static_cast(400)); //to allow use of newline \n. 1356 | if (m_stColors.clrTooltipText != 0xFFFFFFFFUL || m_stColors.clrTooltipBk != 0xFFFFFFFFUL) { 1357 | //To prevent Windows from changing theme of cells tooltip window. 1358 | //Without this call Windows draws Tooltips with current Theme colors, and 1359 | //TTM_SETTIPTEXTCOLOR/TTM_SETTIPBKCOLOR have no effect. 1360 | ::SetWindowTheme(m_hWndCellTT, nullptr, L""); 1361 | ::SendMessageW(m_hWndCellTT, TTM_SETTIPTEXTCOLOR, static_cast(m_stColors.clrTooltipText), 0); 1362 | ::SendMessageW(m_hWndCellTT, TTM_SETTIPBKCOLOR, static_cast(m_stColors.clrTooltipBk), 0); 1363 | } 1364 | 1365 | //Link Tooltip. 1366 | if (m_hWndLinkTT = ::CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, nullptr, lcs.dwTTStyleLink, CW_USEDEFAULT, 1367 | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, nullptr, nullptr); m_hWndLinkTT == nullptr) { 1368 | assert(false); 1369 | return false; 1370 | } 1371 | 1372 | const TTTOOLINFOW ttiLink { .cbSize { sizeof(TTTOOLINFOW) }, 1373 | .uFlags { static_cast(lcs.dwTTStyleLink & TTS_BALLOON ? 0 : TTF_TRACK) } }; 1374 | ::SendMessageW(m_hWndLinkTT, TTM_ADDTOOLW, 0, reinterpret_cast(&ttiLink)); 1375 | ::SendMessageW(m_hWndLinkTT, TTM_SETMAXTIPWIDTH, 0, static_cast(400)); //to allow use of newline \n. 1376 | 1377 | if (m_fHighLatency) { //Tooltip for HighLatency mode. 1378 | if (m_hWndRowTT = ::CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, nullptr, 1379 | TTS_NOANIMATE | TTS_NOFADE | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, 1380 | CW_USEDEFAULT, CW_USEDEFAULT, m_hWnd, nullptr, nullptr, nullptr); m_hWndRowTT == nullptr) { 1381 | assert(false); 1382 | return false; 1383 | } 1384 | 1385 | const TTTOOLINFOW ttiHL { .cbSize { sizeof(TTTOOLINFOW) }, .uFlags { static_cast(TTF_TRACK) } }; 1386 | ::SendMessageW(m_hWndRowTT, TTM_ADDTOOLW, 0, reinterpret_cast(&ttiHL)); 1387 | } 1388 | 1389 | const auto hDC = ::GetDC(m_hWnd); 1390 | m_iLOGPIXELSY = ::GetDeviceCaps(hDC, LOGPIXELSY); 1391 | NONCLIENTMETRICSW ncm { .cbSize { sizeof(NONCLIENTMETRICSW) } }; 1392 | ::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0); //Get System Default UI Font. 1393 | ncm.lfMessageFont.lfHeight = -::MulDiv(lcs.dwSizeFontList, m_iLOGPIXELSY, 72); 1394 | LOGFONTW lfList { lcs.pLFList != nullptr ? *lcs.pLFList : ncm.lfMessageFont }; 1395 | m_hFntList = ::CreateFontIndirectW(&lfList); 1396 | lfList.lfUnderline = TRUE; 1397 | m_hFntListUnderline = ::CreateFontIndirectW(&lfList); 1398 | ncm.lfMessageFont.lfHeight = -::MulDiv(lcs.dwSizeFontHdr, m_iLOGPIXELSY, 72); 1399 | const LOGFONTW lfHdr { lcs.pLFHdr != nullptr ? *lcs.pLFHdr : ncm.lfMessageFont }; 1400 | const auto fntDefault = ::CreateFontIndirectW(&lfHdr); 1401 | TEXTMETRICW tm; 1402 | ::SelectObject(hDC, fntDefault); 1403 | ::GetTextMetricsW(hDC, &tm); 1404 | const DWORD dwHdrHeight = lcs.dwHdrHeight == 0 ? 1405 | tm.tmHeight + tm.tmExternalLeading + ::MulDiv(5, m_iLOGPIXELSY, 72) : lcs.dwHdrHeight; //Header is a bit higher than list rows. 1406 | ::ReleaseDC(m_hWnd, hDC); 1407 | 1408 | m_hPenGrid = ::CreatePen(PS_SOLID, m_dwGridWidth, m_stColors.clrListGrid); 1409 | ::SetClassLongPtrW(m_hWnd, GCLP_HCURSOR, 0); //To prevent cursor from blinking. 1410 | RecalcMeasure(); 1411 | 1412 | m_fCreated = true; 1413 | 1414 | GetHeader().SetDPIScale(static_cast(m_iLOGPIXELSY) / USER_DEFAULT_SCREEN_DPI); 1415 | GetHeader().SetColor(m_stColors); 1416 | GetHeader().SetSortable(lcs.fSortable); 1417 | SetHdrHeight(dwHdrHeight); 1418 | SetHdrFont(lfHdr); 1419 | Update(0); 1420 | 1421 | return true; 1422 | } 1423 | 1424 | void CListEx::CreateDialogCtrl(UINT uCtrlID, HWND hWndParent) 1425 | { 1426 | Create({ .hWndParent { hWndParent }, .uID { uCtrlID }, .fDialogCtrl { true } }); 1427 | } 1428 | 1429 | bool CListEx::DeleteAllItems() 1430 | { 1431 | assert(IsCreated()); 1432 | if (!IsCreated()) { 1433 | return FALSE; 1434 | } 1435 | 1436 | ::DestroyWindow(m_hWndEditInPlace); 1437 | 1438 | return ::SendMessageW(m_hWnd, LVM_DELETEALLITEMS, 0, 0L) != 0; 1439 | } 1440 | 1441 | bool CListEx::DeleteColumn(int iIndex) 1442 | { 1443 | assert(IsCreated()); 1444 | if (!IsCreated()) { 1445 | return FALSE; 1446 | } 1447 | 1448 | std::erase_if(m_vecColumnData, [=](const COLUMNDATA& ref) { return ref.iIndex == iIndex; }); 1449 | GetHeader().DeleteColumn(iIndex); 1450 | 1451 | return ::SendMessageW(m_hWnd, LVM_DELETECOLUMN, iIndex, 0) != 0; 1452 | } 1453 | 1454 | bool CListEx::DeleteItem(int iItem) 1455 | { 1456 | assert(IsCreated()); 1457 | if (!IsCreated()) { 1458 | return FALSE; 1459 | } 1460 | 1461 | return ::SendMessageW(m_hWnd, LVM_DELETEITEM, iItem, 0L) != 0; 1462 | } 1463 | 1464 | bool CListEx::EnsureVisible(int iItem, bool fPartialOK)const 1465 | { 1466 | assert(IsCreated()); 1467 | if (!IsCreated()) { 1468 | return { }; 1469 | } 1470 | 1471 | return static_cast(::SendMessageW(m_hWnd, LVM_ENSUREVISIBLE, iItem, MAKELPARAM(fPartialOK, 0))); 1472 | } 1473 | 1474 | auto CListEx::GetColors()const->const LISTEXCOLORS& { 1475 | return m_stColors; 1476 | } 1477 | 1478 | auto CListEx::GetColumnSortMode(int iColumn)const->EListExSortMode 1479 | { 1480 | assert(IsCreated()); 1481 | 1482 | if (const auto it = std::find_if(m_vecColumnData.begin(), m_vecColumnData.end(), 1483 | [=](const COLUMNDATA& ref) { return ref.iIndex == iColumn; }); it != m_vecColumnData.end()) { 1484 | return it->eSortMode; 1485 | } 1486 | 1487 | return m_eDefSortMode; 1488 | } 1489 | 1490 | int CListEx::GetDlgCtrlID()const 1491 | { 1492 | assert(IsCreated()); 1493 | if (!IsCreated()) { 1494 | return { }; 1495 | } 1496 | 1497 | return ::GetDlgCtrlID(m_hWnd); 1498 | } 1499 | 1500 | auto CListEx::GetExtendedStyle()const->DWORD 1501 | { 1502 | assert(IsCreated()); 1503 | if (!IsCreated()) { 1504 | return { }; 1505 | } 1506 | 1507 | return static_cast(::SendMessageW(m_hWnd, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0)); 1508 | } 1509 | 1510 | auto CListEx::GetFont()const->LOGFONTW 1511 | { 1512 | assert(IsCreated()); 1513 | if (!IsCreated()) { 1514 | return { }; 1515 | } 1516 | 1517 | LOGFONTW lf { }; 1518 | ::GetObjectW(m_hFntList, sizeof(lf), &lf); 1519 | 1520 | return lf; 1521 | } 1522 | 1523 | bool CListEx::EditInPlaceShow(bool fShow) 1524 | { 1525 | if (!fShow) { 1526 | ::DestroyWindow(m_hWndEditInPlace); 1527 | return false; 1528 | } 1529 | 1530 | //Get Column data alignment. 1531 | const auto iAlignment = GetHeader().GetColumnDataAlign(m_htiEdit.iSubItem); 1532 | const DWORD dwStyle = iAlignment == LVCFMT_LEFT ? ES_LEFT : (iAlignment == LVCFMT_RIGHT ? ES_RIGHT : ES_CENTER); 1533 | auto rcCell = GetSubItemRect(m_htiEdit.iItem, m_htiEdit.iSubItem, LVIR_BOUNDS); 1534 | if (m_htiEdit.iSubItem == 0) { //Clicked on item (first column). 1535 | rcCell.right = GetItemRect(m_htiEdit.iItem, LVIR_LABEL).right; 1536 | } 1537 | 1538 | ::DestroyWindow(m_hWndEditInPlace); 1539 | const auto iWidth = rcCell.right - rcCell.left; 1540 | const auto iHeight = rcCell.bottom - rcCell.top; 1541 | m_hWndEditInPlace = ::CreateWindowExW(0, WC_EDITW, nullptr, dwStyle | WS_BORDER | WS_CHILD | ES_AUTOHSCROLL, 1542 | rcCell.left, rcCell.top, iWidth, iHeight, m_hWnd, reinterpret_cast(static_cast(m_uIDEditInPlace)), 1543 | nullptr, nullptr); 1544 | ::SetWindowSubclass(m_hWndEditInPlace, EditSubclassProc, reinterpret_cast(this), 0); 1545 | 1546 | const auto uCtrlId = static_cast(GetDlgCtrlID()); 1547 | const auto wstrText = GetItemText(m_htiEdit.iItem, m_htiEdit.iSubItem); 1548 | wchar_t buff[256]; 1549 | buff[wstrText.copy(buff, 255)] = 0; //Null terminating the buffer after copy not more than 255 wchars. 1550 | const LISTEXDATAINFO ldi { .hdr { .hwndFrom { m_hWnd }, .idFrom { uCtrlId }, .code { LISTEX_MSG_EDITBEGIN } }, 1551 | .iItem { m_htiEdit.iItem }, .iSubItem { m_htiEdit.iSubItem }, .hWndEdit { m_hWndEditInPlace }, 1552 | .pwszData { buff } }; 1553 | ::SendMessageW(::GetParent(m_hWnd), WM_NOTIFY, static_cast(uCtrlId), reinterpret_cast(&ldi)); 1554 | if (!ldi.fAllowEdit) { //User explicitly declined displaying of the edit-box. 1555 | ::DestroyWindow(m_hWndEditInPlace); 1556 | return false; 1557 | } 1558 | 1559 | ::SendMessageW(m_hWndEditInPlace, WM_SETFONT, reinterpret_cast(m_hFntList), FALSE); 1560 | ::SetWindowTextW(m_hWndEditInPlace, ldi.pwszData); 1561 | ::ShowWindow(m_hWndEditInPlace, SW_SHOW); 1562 | ::SetFocus(m_hWndEditInPlace); 1563 | 1564 | return true; 1565 | } 1566 | 1567 | long CListEx::GetFontSize()const 1568 | { 1569 | assert(IsCreated()); 1570 | if (!IsCreated()) { 1571 | return { }; 1572 | } 1573 | 1574 | LOGFONTW lf { }; 1575 | ::GetObjectW(m_hFntList, sizeof(lf), &lf); 1576 | 1577 | return lf.lfHeight; 1578 | } 1579 | 1580 | auto CListEx::GetHWND()const->HWND 1581 | { 1582 | if (!IsCreated()) { 1583 | return { }; 1584 | } 1585 | 1586 | return m_hWnd; 1587 | } 1588 | 1589 | auto CListEx::GetImageList(int iList)const->HIMAGELIST 1590 | { 1591 | assert(IsCreated()); 1592 | if (!IsCreated()) { 1593 | return { }; 1594 | } 1595 | 1596 | return reinterpret_cast(::SendMessageW(m_hWnd, LVM_GETIMAGELIST, iList, 0L)); 1597 | } 1598 | 1599 | void CListEx::GetItem(LVITEMW* pItem)const 1600 | { 1601 | assert(IsCreated()); 1602 | if (!IsCreated()) { 1603 | return; 1604 | } 1605 | 1606 | ::SendMessageW(m_hWnd, LVM_GETITEMW, 0, reinterpret_cast(pItem)); 1607 | } 1608 | 1609 | int CListEx::GetItemCount()const 1610 | { 1611 | assert(IsCreated()); 1612 | if (!IsCreated()) { 1613 | return -1; 1614 | } 1615 | 1616 | return static_cast(::SendMessageW(m_hWnd, LVM_GETITEMCOUNT, 0, 0L)); 1617 | } 1618 | 1619 | auto CListEx::GetItemData(int iItem)const->DWORD_PTR 1620 | { 1621 | LVITEMW lvi { .mask { LVIF_PARAM }, .iItem { iItem } }; 1622 | GetItem(&lvi); 1623 | 1624 | return lvi.lParam; 1625 | } 1626 | 1627 | auto CListEx::GetItemRect(int iItem, int iArea)const->RECT 1628 | { 1629 | assert(IsCreated()); 1630 | if (!IsCreated()) { 1631 | return { }; 1632 | } 1633 | 1634 | assert(iArea == LVIR_BOUNDS || iArea == LVIR_ICON || iArea == LVIR_LABEL || iArea == LVIR_SELECTBOUNDS); 1635 | 1636 | RECT rc { .left { iArea } }; 1637 | const bool ret = ::SendMessageW(m_hWnd, LVM_GETITEMRECT, iItem, reinterpret_cast(&rc)); 1638 | 1639 | return ret ? rc : RECT { }; 1640 | } 1641 | 1642 | auto CListEx::GetItemText(int iItem, int iSubItem)const->std::wstring 1643 | { 1644 | assert(IsCreated()); 1645 | if (!IsCreated()) { 1646 | return { }; 1647 | } 1648 | 1649 | //Temporary buffer for string data to receive. 1650 | //In virtual mode, when responding to the LVN_GETDISPINFO notification message, client code can copy 1651 | //data to the .pszText pointed buffer, or can set the .pszText pointer to client own data. 1652 | //But list control will copy that data to the provided original buffer anyway. 1653 | wchar_t buff[256]; 1654 | const LVITEMW lvi { .iSubItem { iSubItem }, .pszText { buff }, .cchTextMax { 256 } }; 1655 | ::SendMessageW(m_hWnd, LVM_GETITEMTEXTW, static_cast(iItem), reinterpret_cast(&lvi)); 1656 | 1657 | return buff; 1658 | } 1659 | 1660 | int CListEx::GetNextItem(int iItem, int iFlags)const 1661 | { 1662 | assert(IsCreated()); 1663 | if (!IsCreated()) { 1664 | return { }; 1665 | } 1666 | 1667 | return static_cast(::SendMessageW(m_hWnd, LVM_GETNEXTITEM, iItem, MAKELPARAM(iFlags, 0))); 1668 | } 1669 | 1670 | auto CListEx::GetSelectedCount()const->UINT 1671 | { 1672 | assert(IsCreated()); 1673 | if (!IsCreated()) { 1674 | return { }; 1675 | } 1676 | 1677 | return static_cast(::SendMessageW(m_hWnd, LVM_GETSELECTEDCOUNT, 0, 0L)); 1678 | } 1679 | 1680 | int CListEx::GetSelectionMark()const 1681 | { 1682 | assert(IsCreated()); 1683 | if (!IsCreated()) { 1684 | return { }; 1685 | } 1686 | 1687 | return static_cast(::SendMessageW(m_hWnd, LVM_GETSELECTIONMARK, 0, 0)); 1688 | } 1689 | 1690 | int CListEx::GetSortColumn()const 1691 | { 1692 | assert(IsCreated()); 1693 | if (!IsCreated()) { 1694 | return -1; 1695 | } 1696 | 1697 | return m_iSortColumn; 1698 | } 1699 | 1700 | bool CListEx::GetSortAscending()const 1701 | { 1702 | assert(IsCreated()); 1703 | if (!IsCreated()) { 1704 | return false; 1705 | } 1706 | 1707 | return m_fSortAsc; 1708 | } 1709 | 1710 | auto CListEx::GetSubItemRect(int iItem, int iSubItem, int iArea)const->RECT 1711 | { 1712 | assert(IsCreated()); 1713 | if (!IsCreated()) { 1714 | return { }; 1715 | } 1716 | 1717 | assert(iArea == LVIR_BOUNDS || iArea == LVIR_ICON || iArea == LVIR_LABEL || iArea == LVIR_SELECTBOUNDS); 1718 | 1719 | RECT rc { .left { iArea }, .top { iSubItem } }; 1720 | const bool ret = ::SendMessageW(m_hWnd, LVM_GETSUBITEMRECT, iItem, reinterpret_cast(&rc)); 1721 | 1722 | return ret ? rc : RECT { }; 1723 | } 1724 | 1725 | int CListEx::GetTopIndex()const 1726 | { 1727 | assert(IsCreated()); 1728 | if (!IsCreated()) { 1729 | return { }; 1730 | } 1731 | 1732 | return static_cast(::SendMessageW(m_hWnd, LVM_GETTOPINDEX, 0, 0)); 1733 | } 1734 | 1735 | void CListEx::HideColumn(int iIndex, bool fHide) 1736 | { 1737 | assert(IsCreated()); 1738 | if (!IsCreated()) { 1739 | return; 1740 | } 1741 | 1742 | GetHeader().HideColumn(iIndex, fHide); 1743 | RedrawWindow(); 1744 | } 1745 | 1746 | bool CListEx::HitTest(LVHITTESTINFO* pHTI)const 1747 | { 1748 | assert(IsCreated()); 1749 | if (!IsCreated()) { 1750 | return false; 1751 | } 1752 | 1753 | return ::SendMessageW(m_hWnd, LVM_SUBITEMHITTEST, 0, reinterpret_cast(pHTI)) != -1; 1754 | } 1755 | 1756 | int CListEx::InsertColumn(int iCol, const LVCOLUMNW* pColumn)const 1757 | { 1758 | return static_cast(::SendMessageW(m_hWnd, LVM_INSERTCOLUMNW, iCol, reinterpret_cast(pColumn))); 1759 | } 1760 | 1761 | int CListEx::InsertColumn(int iCol, const LVCOLUMNW* pColumn, int iDataAlign, bool fEditable) 1762 | { 1763 | assert(IsCreated()); 1764 | if (!IsCreated()) { 1765 | return -1; 1766 | } 1767 | 1768 | auto& refHdr = GetHeader(); 1769 | const auto uHiddenCount = refHdr.GetHiddenCount(); 1770 | 1771 | //Checking that the new column ID (nCol) not greater than the count of 1772 | //the header items minus count of the already hidden columns. 1773 | if (uHiddenCount > 0 && iCol >= static_cast(refHdr.GetItemCount() - uHiddenCount)) { 1774 | iCol = refHdr.GetItemCount() - uHiddenCount; 1775 | } 1776 | 1777 | const auto iNewIndex = InsertColumn(iCol, pColumn); 1778 | 1779 | //Assigning each column a unique internal random identifier. 1780 | std::random_device rd; 1781 | std::mt19937 gen(rd()); 1782 | std::uniform_int_distribution distrib(1, (std::numeric_limits::max)()); 1783 | refHdr.SetItem(iNewIndex, { .mask { HDI_LPARAM }, .lParam { static_cast(distrib(gen)) } }); 1784 | refHdr.SetColumnDataAlign(iNewIndex, iDataAlign); 1785 | 1786 | //First (zero index) column is always left-aligned by default, no matter what the pColumn->fmt is set to. 1787 | //To change the alignment a user must explicitly call the SetColumn after the InsertColumn. 1788 | //This call here is just to remove that absurd limitation. 1789 | const LVCOLUMNW stCol { .mask { LVCF_FMT }, .fmt { pColumn->fmt } }; 1790 | SetColumn(iNewIndex, &stCol); 1791 | 1792 | //All new columns are not editable by default. 1793 | if (fEditable) { 1794 | SetColumnEditable(iNewIndex, true); 1795 | } 1796 | 1797 | return iNewIndex; 1798 | } 1799 | 1800 | int CListEx::InsertColumn(int iCol, LPCWSTR pwszName, int iFormat, int iWidth, int iSubItem, int iDataAlign, bool fEditable) 1801 | { 1802 | assert(IsCreated()); 1803 | if (!IsCreated()) { 1804 | return -1; 1805 | } 1806 | 1807 | const LVCOLUMNW lvcol { .mask { LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT }, .fmt { iFormat }, 1808 | .cx { iWidth }, .pszText { const_cast(pwszName) }, .iSubItem { iSubItem } }; 1809 | return InsertColumn(iCol, &lvcol, iDataAlign, fEditable); 1810 | } 1811 | 1812 | int CListEx::InsertItem(const LVITEMW* pItem)const 1813 | { 1814 | assert(IsCreated()); 1815 | if (!IsCreated()) { 1816 | return -1; 1817 | } 1818 | 1819 | return static_cast(::SendMessageW(m_hWnd, LVM_INSERTITEMW, 0, reinterpret_cast(pItem))); 1820 | } 1821 | 1822 | int CListEx::InsertItem(int iItem, LPCWSTR pwszName)const 1823 | { 1824 | return InsertItem(LVIF_TEXT, iItem, pwszName, 0, 0, 0, 0); 1825 | } 1826 | 1827 | int CListEx::InsertItem(int iItem, LPCWSTR pwszName, int iImage)const 1828 | { 1829 | return InsertItem(LVIF_TEXT | LVIF_IMAGE, iItem, pwszName, 0, 0, iImage, 0); 1830 | } 1831 | 1832 | int CListEx::InsertItem(UINT uMask, int iItem, LPCWSTR pwszName, UINT uState, UINT uStateMask, int iImage, LPARAM lParam)const 1833 | { 1834 | const LVITEMW item { .mask { uMask }, .iItem { iItem }, .state { uState }, .stateMask { uStateMask }, 1835 | .pszText { const_cast(pwszName) }, .iImage { iImage }, .lParam { lParam } }; 1836 | return InsertItem(&item); 1837 | } 1838 | 1839 | bool CListEx::IsCreated()const 1840 | { 1841 | return m_fCreated; 1842 | } 1843 | 1844 | bool CListEx::IsColumnSortable(int iColumn) 1845 | { 1846 | return GetHeader().IsColumnSortable(iColumn); 1847 | } 1848 | 1849 | auto CListEx::MapIndexToID(UINT uIndex)const->UINT 1850 | { 1851 | assert(IsWindow()); 1852 | return static_cast(::SendMessageW(m_hWnd, LVM_MAPINDEXTOID, static_cast(uIndex), 0)); 1853 | } 1854 | 1855 | auto CListEx::MapIDToIndex(UINT uID)const->UINT 1856 | { 1857 | assert(IsWindow()); 1858 | return static_cast(::SendMessageW(m_hWnd, LVM_MAPIDTOINDEX, static_cast(uID), 0)); 1859 | } 1860 | 1861 | auto CListEx::ProcessMsg(const MSG& msg)->LRESULT 1862 | { 1863 | switch (msg.message) { 1864 | case WM_COMMAND: return OnCommand(msg); 1865 | case WM_DESTROY: return OnDestroy(); 1866 | case WM_ERASEBKGND: return OnEraseBkgnd(); 1867 | case WM_HSCROLL: return OnHScroll(msg); 1868 | case WM_LBUTTONDBLCLK: return OnLButtonDblClk(msg); 1869 | case WM_LBUTTONDOWN: return OnLButtonDown(msg); 1870 | case WM_LBUTTONUP: return OnLButtonUp(msg); 1871 | case WM_MOUSEMOVE: return OnMouseMove(msg); 1872 | case WM_MOUSEWHEEL: return OnMouseWheel(msg); 1873 | case WM_NOTIFY: return OnNotify(msg); 1874 | case WM_PAINT: return OnPaint(); 1875 | case WM_TIMER: return OnTimer(msg); 1876 | case WM_VSCROLL: return OnVScroll(msg); 1877 | default: return wnd::DefSubclassProc(msg); 1878 | } 1879 | } 1880 | 1881 | void CListEx::ResetSort() 1882 | { 1883 | m_iSortColumn = -1; 1884 | GetHeader().SetSortArrow(-1, false); 1885 | } 1886 | 1887 | void CListEx::Scroll(SIZE size)const 1888 | { 1889 | ::SendMessageW(m_hWnd, LVM_SCROLL, size.cx, size.cy); 1890 | } 1891 | 1892 | void CListEx::SetColors(const LISTEXCOLORS& lcs) 1893 | { 1894 | assert(IsCreated()); 1895 | if (!IsCreated()) { 1896 | return; 1897 | } 1898 | 1899 | m_stColors = lcs; 1900 | GetHeader().SetColor(lcs); 1901 | RedrawWindow(); 1902 | } 1903 | 1904 | void CListEx::SetColumn(int iCol, const LVCOLUMNW* pColumn)const 1905 | { 1906 | assert(IsCreated()); 1907 | if (!IsCreated()) { 1908 | return; 1909 | } 1910 | 1911 | ::SendMessageW(m_hWnd, LVM_SETCOLUMNW, iCol, reinterpret_cast(pColumn)); 1912 | } 1913 | 1914 | void CListEx::SetColumnEditable(int iColumn, bool fEditable) 1915 | { 1916 | assert(IsCreated()); 1917 | if (!IsCreated()) { 1918 | return; 1919 | } 1920 | 1921 | GetHeader().SetColumnEditable(iColumn, fEditable); 1922 | } 1923 | 1924 | void CListEx::SetColumnSortMode(int iColumn, bool fSortable, EListExSortMode eSortMode) 1925 | { 1926 | if (const auto it = std::find_if(m_vecColumnData.begin(), m_vecColumnData.end(), 1927 | [=](const COLUMNDATA& ref) { return ref.iIndex == iColumn; }); it != m_vecColumnData.end()) { 1928 | it->eSortMode = eSortMode; 1929 | } 1930 | else { m_vecColumnData.emplace_back(COLUMNDATA { .iIndex { iColumn }, .eSortMode { eSortMode } }); } 1931 | 1932 | GetHeader().SetColumnSortable(iColumn, fSortable); 1933 | } 1934 | 1935 | auto CListEx::SetExtendedStyle(DWORD dwExStyle)const->DWORD 1936 | { 1937 | assert(IsCreated()); 1938 | if (!IsCreated()) { 1939 | return { }; 1940 | } 1941 | 1942 | return static_cast(::SendMessageW(m_hWnd, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, dwExStyle)); 1943 | } 1944 | 1945 | void CListEx::SetFont(const LOGFONTW& lf) 1946 | { 1947 | assert(IsCreated()); 1948 | if (!IsCreated()) { 1949 | return; 1950 | } 1951 | 1952 | ::DeleteObject(m_hFntList); 1953 | m_hFntList = ::CreateFontIndirectW(&lf); 1954 | LOGFONTW lfu { lf }; 1955 | lfu.lfUnderline = TRUE; 1956 | ::DeleteObject(m_hFntListUnderline); 1957 | m_hFntListUnderline = ::CreateFontIndirectW(&lfu); 1958 | 1959 | RecalcMeasure(); 1960 | Update(0); 1961 | 1962 | if (::IsWindow(m_hWndEditInPlace)) { //If m_hWndEditInPlace is active, ammend its rect. 1963 | wnd::CRect rcCell = GetSubItemRect(m_htiEdit.iItem, m_htiEdit.iSubItem, LVIR_BOUNDS); 1964 | if (m_htiEdit.iSubItem == 0) { //Clicked on item (first column). 1965 | auto rcLabel = GetItemRect(m_htiEdit.iItem, LVIR_LABEL); 1966 | rcCell.right = rcLabel.right; 1967 | } 1968 | ::SetWindowPos(m_hWndEditInPlace, nullptr, rcCell.left, rcCell.top, rcCell.Width(), rcCell.Height(), SWP_NOZORDER); 1969 | ::SendMessageW(m_hWndEditInPlace, WM_SETFONT, reinterpret_cast(m_hFntList), FALSE); 1970 | } 1971 | } 1972 | 1973 | void CListEx::SetHdrHeight(DWORD dwHeight) 1974 | { 1975 | assert(IsCreated()); 1976 | if (!IsCreated()) { 1977 | return; 1978 | } 1979 | 1980 | GetHeader().SetHeight(dwHeight); 1981 | Update(0); 1982 | GetHeader().RedrawWindow(); 1983 | } 1984 | 1985 | void CListEx::SetHdrImageList(HIMAGELIST pList) 1986 | { 1987 | assert(IsCreated()); 1988 | if (!IsCreated()) { 1989 | return; 1990 | } 1991 | 1992 | GetHeader().SetImageList(pList); 1993 | } 1994 | 1995 | auto CListEx::SetImageList(HIMAGELIST hList, int iListType)->HIMAGELIST 1996 | { 1997 | assert(IsCreated()); 1998 | if (!IsCreated()) { 1999 | return { }; 2000 | } 2001 | 2002 | return reinterpret_cast(::SendMessageW(m_hWnd, LVM_SETIMAGELIST, iListType, reinterpret_cast(hList))); 2003 | } 2004 | 2005 | bool CListEx::SetItem(const LVITEMW* pItem)const 2006 | { 2007 | assert(IsCreated()); 2008 | if (!IsCreated()) { 2009 | return { }; 2010 | } 2011 | 2012 | return ::SendMessageW(m_hWnd, LVM_SETITEMW, 0, reinterpret_cast(pItem)) != 0; 2013 | } 2014 | 2015 | void CListEx::SetItemCountEx(int iCount, DWORD dwFlags)const 2016 | { 2017 | assert(IsCreated()); 2018 | if (!IsCreated()) { 2019 | return; 2020 | } 2021 | 2022 | assert(dwFlags == 0 || m_fVirtual); 2023 | ::SendMessageW(m_hWnd, LVM_SETITEMCOUNT, iCount, dwFlags); 2024 | } 2025 | 2026 | bool CListEx::SetItemData(int iItem, DWORD_PTR dwData)const 2027 | { 2028 | assert(IsCreated()); 2029 | if (!IsCreated()) { 2030 | return { }; 2031 | } 2032 | 2033 | const LVITEMW lvi { .mask { LVIF_PARAM }, .iItem { iItem }, .lParam { static_cast(dwData) } }; 2034 | return SetItem(&lvi); 2035 | } 2036 | 2037 | bool CListEx::SetItemState(int iItem, LVITEMW* pItem)const 2038 | { 2039 | assert(IsCreated()); 2040 | if (!IsCreated()) { 2041 | return false; 2042 | } 2043 | 2044 | return static_cast(::SendMessageW(m_hWnd, LVM_SETITEMSTATE, iItem, reinterpret_cast(pItem))); 2045 | } 2046 | 2047 | bool CListEx::SetItemState(int iItem, UINT uState, UINT uStateMask)const 2048 | { 2049 | assert(IsCreated()); 2050 | if (!IsCreated()) { 2051 | return false; 2052 | } 2053 | 2054 | LVITEMW lvi { .state { uState }, .stateMask { uStateMask } }; 2055 | return SetItemState(iItem, &lvi); 2056 | } 2057 | 2058 | void CListEx::SetItemText(int iItem, int iSubItem, LPCWSTR pwszText) 2059 | { 2060 | assert(IsCreated()); 2061 | if (!IsCreated()) { 2062 | return; 2063 | } 2064 | 2065 | LVITEMW lvi { .iSubItem { iSubItem }, .pszText { const_cast(pwszText) } }; 2066 | ::SendMessageW(m_hWnd, LVM_SETITEMTEXTW, iItem, reinterpret_cast(&lvi)); 2067 | } 2068 | 2069 | void CListEx::SetRedraw(bool fRedraw)const 2070 | { 2071 | assert(IsCreated()); 2072 | if (!IsCreated()) { 2073 | return; 2074 | } 2075 | 2076 | ::SendMessageW(m_hWnd, WM_SETREDRAW, fRedraw, 0); 2077 | } 2078 | 2079 | void CListEx::SetHdrFont(const LOGFONTW& lf) 2080 | { 2081 | assert(IsCreated()); 2082 | if (!IsCreated()) { 2083 | return; 2084 | } 2085 | 2086 | GetHeader().SetFont(lf); 2087 | Update(0); 2088 | GetHeader().RedrawWindow(); 2089 | } 2090 | 2091 | void CListEx::SetHdrColumnColor(int iColumn, COLORREF clrBk, COLORREF clrText) 2092 | { 2093 | assert(IsCreated()); 2094 | if (!IsCreated()) { 2095 | return; 2096 | } 2097 | 2098 | GetHeader().SetColumnColor(iColumn, clrBk, clrText); 2099 | Update(0); 2100 | GetHeader().RedrawWindow(); 2101 | } 2102 | 2103 | void CListEx::SetHdrColumnIcon(int iColumn, const LISTEXHDRICON& stIcon) 2104 | { 2105 | assert(IsCreated()); 2106 | if (!IsCreated()) { 2107 | return; 2108 | } 2109 | 2110 | GetHeader().SetColumnIcon(iColumn, stIcon); 2111 | } 2112 | 2113 | void CListEx::SetSortable(bool fSortable, PFNLVCOMPARE pfnCompare, EListExSortMode eSortMode) 2114 | { 2115 | assert(IsCreated()); 2116 | if (!IsCreated()) { 2117 | return; 2118 | } 2119 | 2120 | m_fSortable = fSortable; 2121 | m_pfnCompare = pfnCompare; 2122 | m_eDefSortMode = eSortMode; 2123 | 2124 | GetHeader().SetSortable(fSortable); 2125 | } 2126 | 2127 | void CListEx::SetWindowPos(HWND hWndAfter, int iX, int iY, int iWidth, int iHeight, UINT uFlags) 2128 | { 2129 | assert(IsCreated()); 2130 | if (!IsCreated()) { 2131 | return; 2132 | } 2133 | 2134 | ::SetWindowPos(m_hWnd, hWndAfter, iX, iY, iWidth, iHeight, uFlags); 2135 | } 2136 | 2137 | bool CListEx::ShowWindow(int iCmdShow)const 2138 | { 2139 | assert(IsCreated()); 2140 | if (!IsCreated()) { 2141 | return false; 2142 | } 2143 | 2144 | return ::ShowWindow(m_hWnd, iCmdShow) != FALSE; 2145 | } 2146 | 2147 | void CListEx::SortItemsEx(PFNLVCOMPARE pfnCompare, DWORD_PTR dwData)const 2148 | { 2149 | assert(IsCreated()); 2150 | if (!IsCreated()) { 2151 | return; 2152 | } 2153 | 2154 | assert(!m_fVirtual); 2155 | ::SendMessageW(m_hWnd, LVM_SORTITEMSEX, dwData, reinterpret_cast(pfnCompare)); 2156 | } 2157 | 2158 | void CListEx::Update(int iItem)const 2159 | { 2160 | assert(IsCreated()); 2161 | if (!IsCreated()) { 2162 | return; 2163 | } 2164 | 2165 | ::SendMessageW(m_hWnd, LVM_UPDATE, iItem, 0L); 2166 | } 2167 | 2168 | int CALLBACK CListEx::DefCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) 2169 | { 2170 | const auto* const pListCtrl = reinterpret_cast(lParamSort); 2171 | const auto iSortColumn = pListCtrl->GetSortColumn(); 2172 | const auto eSortMode = pListCtrl->GetColumnSortMode(iSortColumn); 2173 | const auto wstrItem1 = pListCtrl->GetItemText(static_cast(lParam1), iSortColumn); 2174 | const auto wstrItem2 = pListCtrl->GetItemText(static_cast(lParam2), iSortColumn); 2175 | 2176 | int iCompare { }; 2177 | switch (eSortMode) { 2178 | case EListExSortMode::SORT_LEX: 2179 | iCompare = wstrItem1.compare(wstrItem2); 2180 | break; 2181 | case EListExSortMode::SORT_NUMERIC: 2182 | { 2183 | LONGLONG llData1 { }; 2184 | LONGLONG llData2 { }; 2185 | ::StrToInt64ExW(wstrItem1.data(), STIF_SUPPORT_HEX, &llData1); 2186 | ::StrToInt64ExW(wstrItem2.data(), STIF_SUPPORT_HEX, &llData2); 2187 | iCompare = llData1 != llData2 ? (llData1 - llData2 < 0 ? -1 : 1) : 0; 2188 | } 2189 | break; 2190 | } 2191 | 2192 | int iResult = 0; 2193 | if (pListCtrl->GetSortAscending()) { 2194 | if (iCompare < 0) { 2195 | iResult = -1; 2196 | } 2197 | else if (iCompare > 0) { 2198 | iResult = 1; 2199 | } 2200 | } 2201 | else { 2202 | if (iCompare < 0) { 2203 | iResult = 1; 2204 | } 2205 | else if (iCompare > 0) { 2206 | iResult = -1; 2207 | } 2208 | } 2209 | 2210 | return iResult; 2211 | } 2212 | 2213 | 2214 | //CListEx private methods: 2215 | 2216 | void CListEx::DrawItem(LPDRAWITEMSTRUCT pDIS) 2217 | { 2218 | if (!IsCreated() || pDIS->hwndItem != m_hWnd) 2219 | return; 2220 | 2221 | const auto iItem = pDIS->itemID; 2222 | constexpr auto iTextIndentTop = 1; //To compensate what is added in the MeasureItem. 2223 | constexpr auto iTextIndentLeft = 2; 2224 | 2225 | switch (pDIS->itemAction) { 2226 | case ODA_SELECT: 2227 | case ODA_DRAWENTIRE: 2228 | { 2229 | const wnd::CDC cdc(pDIS->hDC); 2230 | const auto clrBkCurrRow = (iItem % 2) ? m_stColors.clrListBkEven : m_stColors.clrListBkOdd; 2231 | const auto& refHdr = GetHeader(); 2232 | const auto iColumns = refHdr.GetItemCount(); 2233 | for (auto iSubitem = 0; iSubitem < iColumns; ++iSubitem) { 2234 | if (refHdr.IsColumnHidden(iSubitem)) { 2235 | continue; 2236 | } 2237 | 2238 | COLORREF clrText; 2239 | COLORREF clrBk; 2240 | COLORREF clrTextLink; 2241 | 2242 | //Subitems' draw routine. 2243 | //Colors depending on whether subitem selected or not, and has tooltip or not. 2244 | if (pDIS->itemState & ODS_SELECTED) { 2245 | clrText = m_stColors.clrListTextSel; 2246 | clrBk = m_stColors.clrListBkSel; 2247 | clrTextLink = m_stColors.clrListTextLinkSel; 2248 | } 2249 | else { 2250 | clrTextLink = m_stColors.clrListTextLink; 2251 | 2252 | if (const auto optClr = GetCustomColor(iItem, iSubitem); optClr) { 2253 | //Check for default colors (-1). 2254 | clrText = optClr->clrText == -1 ? m_stColors.clrListText : optClr->clrText; 2255 | clrBk = optClr->clrBk == -1 ? clrBkCurrRow : optClr->clrBk; 2256 | } 2257 | else { 2258 | clrText = m_stColors.clrListText; 2259 | clrBk = clrBkCurrRow; 2260 | } 2261 | } 2262 | 2263 | wnd::CRect rcBounds = GetSubItemRect(iItem, iSubitem, LVIR_BOUNDS); 2264 | cdc.FillSolidRect(rcBounds, clrBk); 2265 | 2266 | for (const auto& itItemData : ParseItemData(iItem, iSubitem)) { 2267 | if (itItemData.iIconIndex > -1) { 2268 | ::ImageList_DrawEx(GetImageList(LVSIL_NORMAL), itItemData.iIconIndex, cdc, 2269 | itItemData.rc.left, itItemData.rc.top, 0, 0, CLR_NONE, CLR_NONE, ILD_NORMAL); 2270 | continue; 2271 | } 2272 | 2273 | if (itItemData.fLink) { 2274 | cdc.SetTextColor(clrTextLink); 2275 | if (m_fLinkUnderline) { 2276 | cdc.SelectObject(m_hFntListUnderline); 2277 | } 2278 | } 2279 | else { 2280 | cdc.SetTextColor(clrText); 2281 | cdc.SelectObject(m_hFntList); 2282 | } 2283 | 2284 | ::ExtTextOutW(cdc, itItemData.rc.left + iTextIndentLeft, itItemData.rc.top + iTextIndentTop, 2285 | ETO_CLIPPED, rcBounds, itItemData.wstrText.data(), static_cast(itItemData.wstrText.size()), nullptr); 2286 | } 2287 | 2288 | //Drawing subitem's rect lines. 2289 | //If SetExtendedStyle(LVS_EX_GRIDLINES) is set, lines are drawn automatically but through whole client rect. 2290 | if (m_dwGridWidth > 0) { 2291 | cdc.SelectObject(m_hPenGrid); 2292 | cdc.MoveTo(rcBounds.TopLeft()); 2293 | cdc.LineTo(rcBounds.right, rcBounds.top); //Top line. 2294 | cdc.MoveTo(rcBounds.TopLeft()); 2295 | cdc.LineTo(rcBounds.left, rcBounds.bottom); //Left line. 2296 | cdc.MoveTo(rcBounds.left, rcBounds.bottom); 2297 | cdc.LineTo(rcBounds.BottomRight()); //Bottom line. 2298 | if (iSubitem == iColumns - 1) { //Drawing a right line only for the last column. 2299 | rcBounds.right -= 1; //To overcome a glitch with a last line disappearing if resizing a header. 2300 | cdc.MoveTo(rcBounds.right, rcBounds.top); 2301 | cdc.LineTo(rcBounds.BottomRight()); //Right line. 2302 | } 2303 | } 2304 | 2305 | //Draw focus rect (marquee). 2306 | if ((pDIS->itemState & ODS_FOCUS) && !(pDIS->itemState & ODS_SELECTED)) { 2307 | cdc.DrawFocusRect(rcBounds); 2308 | } 2309 | } 2310 | } 2311 | break; 2312 | default: 2313 | break; 2314 | } 2315 | } 2316 | 2317 | void CListEx::FontSizeIncDec(bool fInc) 2318 | { 2319 | const auto lFontSize = ::MulDiv(-GetFontSize(), 72, m_iLOGPIXELSY) + (fInc ? 1 : -1); 2320 | SetFontSize(lFontSize); 2321 | } 2322 | 2323 | auto CListEx::GetCustomColor(int iItem, int iSubItem)const->std::optional 2324 | { 2325 | if (iItem < 0 || iSubItem < 0) { 2326 | return std::nullopt; 2327 | } 2328 | 2329 | //Asking parent for color. 2330 | const auto iCtrlID = GetDlgCtrlID(); 2331 | const LISTEXCOLORINFO lci { .hdr { .hwndFrom { m_hWnd }, .idFrom { static_cast(iCtrlID) }, 2332 | .code { LISTEX_MSG_GETCOLOR } }, .iItem { iItem }, .iSubItem { iSubItem }, 2333 | .stClr { .clrBk { 0xFFFFFFFFUL }, .clrText { 0xFFFFFFFFUL } } }; 2334 | ::SendMessageW(::GetParent(m_hWnd), WM_NOTIFY, static_cast(iCtrlID), reinterpret_cast(&lci)); 2335 | if (lci.stClr.clrBk != -1 || lci.stClr.clrText != -1) { 2336 | return lci.stClr; 2337 | } 2338 | 2339 | return std::nullopt; 2340 | } 2341 | 2342 | int CListEx::GetIcon(int iItem, int iSubItem)const 2343 | { 2344 | if (GetImageList(LVSIL_NORMAL) == nullptr) { 2345 | return -1; //-1 is the default, when no image for cell is set. 2346 | } 2347 | 2348 | //Asking parent for the icon index in image list. 2349 | const auto uCtrlID = static_cast(GetDlgCtrlID()); 2350 | const LISTEXICONINFO lii { .hdr { .hwndFrom { m_hWnd }, .idFrom { uCtrlID }, .code { LISTEX_MSG_GETICON } }, 2351 | .iItem { iItem }, .iSubItem { iSubItem } }; 2352 | ::SendMessageW(::GetParent(m_hWnd), WM_NOTIFY, static_cast(uCtrlID), reinterpret_cast(&lii)); 2353 | return lii.iIconIndex; //By default it's -1, meaning no icon. 2354 | } 2355 | 2356 | auto CListEx::GetTooltip(int iItem, int iSubItem)const->std::optional 2357 | { 2358 | if (iItem < 0 || iSubItem < 0) { 2359 | return std::nullopt; 2360 | } 2361 | 2362 | //Asking parent for tooltip. 2363 | const auto iCtrlID = GetDlgCtrlID(); 2364 | const LISTEXTTINFO ltti { .hdr { .hwndFrom { m_hWnd }, .idFrom { static_cast(iCtrlID) }, 2365 | .code { LISTEX_MSG_GETTOOLTIP } }, .iItem { iItem }, .iSubItem { iSubItem } }; 2366 | if (::SendMessageW(::GetParent(m_hWnd), WM_NOTIFY, static_cast(iCtrlID), reinterpret_cast(<ti)); 2367 | ltti.stData.pwszText != nullptr) { //If tooltip text was set by the Parent. 2368 | return ltti.stData; 2369 | } 2370 | 2371 | return std::nullopt; 2372 | } 2373 | 2374 | bool CListEx::IsWindow()const 2375 | { 2376 | return ::IsWindow(m_hWnd); 2377 | } 2378 | 2379 | void CListEx::MeasureItem(LPMEASUREITEMSTRUCT pMIS) 2380 | { 2381 | if (!IsCreated() || pMIS->CtlID != static_cast(GetDlgCtrlID())) 2382 | return; 2383 | 2384 | //Set row height according to current font's height. 2385 | const auto hDC = ::GetDC(m_hWnd); 2386 | ::SelectObject(hDC, m_hFntList); 2387 | TEXTMETRICW tm; 2388 | ::GetTextMetricsW(hDC, &tm); 2389 | ::ReleaseDC(m_hWnd, hDC); 2390 | pMIS->itemHeight = tm.tmHeight + tm.tmExternalLeading + 2; 2391 | } 2392 | 2393 | auto CListEx::OnCommand(const MSG& msg)->LRESULT 2394 | { 2395 | const auto uCtrlID = LOWORD(msg.wParam); //Control ID. 2396 | const auto uCode = HIWORD(msg.wParam); 2397 | if (uCtrlID == m_uIDEditInPlace && uCode == EN_KILLFOCUS) { 2398 | OnEditInPlaceKillFocus(); 2399 | return 0; 2400 | } 2401 | 2402 | return 1; 2403 | } 2404 | 2405 | auto CListEx::OnDestroy()->LRESULT 2406 | { 2407 | ::DestroyWindow(m_hWndCellTT); 2408 | ::DestroyWindow(m_hWndLinkTT); 2409 | ::DestroyWindow(m_hWndRowTT); 2410 | ::DestroyWindow(m_hWndEditInPlace); 2411 | ::DeleteObject(m_hFntList); 2412 | ::DeleteObject(m_hFntListUnderline); 2413 | ::DeleteObject(m_hPenGrid); 2414 | m_vecColumnData.clear(); 2415 | m_fCreated = false; 2416 | 2417 | return 0; 2418 | } 2419 | 2420 | void CListEx::OnEditInPlaceEnterPressed() 2421 | { 2422 | //Notifying parent about cell's text changing. 2423 | wchar_t buff[256]; 2424 | ::GetWindowTextW(m_hWndEditInPlace, buff, 256); 2425 | const auto uCtrlId = static_cast(GetDlgCtrlID()); 2426 | const LISTEXDATAINFO ldi { .hdr { .hwndFrom { m_hWnd }, .idFrom { uCtrlId }, .code { LISTEX_MSG_SETDATA } }, 2427 | .iItem { m_htiEdit.iItem }, .iSubItem { m_htiEdit.iSubItem }, .hWndEdit { m_hWndEditInPlace }, 2428 | .pwszData { buff } }; 2429 | ::SendMessageW(::GetParent(m_hWnd), WM_NOTIFY, static_cast(uCtrlId), reinterpret_cast(&ldi)); 2430 | 2431 | if (!m_fVirtual) { //If it's not Virtual mode we set new text to a cell. 2432 | SetItemText(m_htiEdit.iItem, m_htiEdit.iSubItem, buff); 2433 | } 2434 | 2435 | OnEditInPlaceKillFocus(); 2436 | } 2437 | 2438 | void CListEx::OnEditInPlaceKillFocus() 2439 | { 2440 | ::DestroyWindow(m_hWndEditInPlace); 2441 | RedrawWindow(); 2442 | } 2443 | 2444 | auto CListEx::OnEraseBkgnd()->LRESULT 2445 | { 2446 | return TRUE; 2447 | } 2448 | 2449 | auto CListEx::OnHScroll(const MSG& msg)->LRESULT 2450 | { 2451 | GetHeader().RedrawWindow(); 2452 | wnd::DefSubclassProc(msg); 2453 | 2454 | return 0; 2455 | } 2456 | 2457 | auto CListEx::OnLButtonDblClk(const MSG& msg)->LRESULT 2458 | { 2459 | const POINT pt { .x { wnd::GetXLPARAM(msg.lParam) }, .y { wnd::GetYLPARAM(msg.lParam) } }; 2460 | LVHITTESTINFO hti { .pt { pt } }; 2461 | HitTest(&hti); 2462 | if (hti.iItem < 0 || hti.iSubItem < 0) 2463 | return wnd::DefSubclassProc(msg); 2464 | 2465 | if (!m_fEditSingleClick && GetHeader().IsColumnEditable(hti.iSubItem)) { 2466 | m_htiEdit = hti; 2467 | return EditInPlaceShow() ? 0 : wnd::DefSubclassProc(msg); 2468 | } 2469 | 2470 | return wnd::DefSubclassProc(msg); 2471 | } 2472 | 2473 | auto CListEx::OnLButtonDown(const MSG& msg)->LRESULT 2474 | { 2475 | const POINT pt { .x { wnd::GetXLPARAM(msg.lParam) }, .y { wnd::GetYLPARAM(msg.lParam) } }; 2476 | LVHITTESTINFO hti { .pt { pt } }; 2477 | HitTest(&hti); 2478 | 2479 | //Destroy edit-box if clicked in any place other than current edit-box cell. 2480 | if (hti.iItem != m_htiEdit.iItem || hti.iSubItem != m_htiEdit.iSubItem) { 2481 | EditInPlaceShow(false); 2482 | } 2483 | 2484 | if (hti.iItem < 0 || hti.iSubItem < 0) 2485 | return wnd::DefSubclassProc(msg); 2486 | 2487 | const auto vecText = ParseItemData(hti.iItem, hti.iSubItem); 2488 | if (const auto itFind = std::find_if(vecText.begin(), vecText.end(), [&](const ITEMDATA& item) { 2489 | return item.fLink && item.rc.PtInRect(pt); }); itFind != vecText.end()) { 2490 | m_fLDownAtLink = true; 2491 | m_rcLinkCurr = itFind->rc; 2492 | return 0; //Do not call default handler. 2493 | } 2494 | 2495 | if (m_fEditSingleClick && GetHeader().IsColumnEditable(hti.iSubItem)) { 2496 | m_htiEdit = hti; 2497 | return EditInPlaceShow() ? 0 : wnd::DefSubclassProc(msg); 2498 | } 2499 | 2500 | return wnd::DefSubclassProc(msg); 2501 | } 2502 | 2503 | auto CListEx::OnLButtonUp(const MSG& msg)->LRESULT 2504 | { 2505 | const POINT pt { .x { wnd::GetXLPARAM(msg.lParam) }, .y { wnd::GetYLPARAM(msg.lParam) } }; 2506 | LVHITTESTINFO hti { .pt { pt } }; 2507 | HitTest(&hti); 2508 | if (hti.iItem < 0 || hti.iSubItem < 0) { 2509 | m_fLDownAtLink = false; 2510 | return wnd::DefSubclassProc(msg); 2511 | } 2512 | 2513 | if (m_fLDownAtLink) { 2514 | const auto vecText = ParseItemData(hti.iItem, hti.iSubItem); 2515 | if (const auto iterFind = std::find_if(vecText.begin(), vecText.end(), [&](const ITEMDATA& item) { 2516 | return item.fLink && item.rc == m_rcLinkCurr; }); iterFind != vecText.end()) { 2517 | m_rcLinkCurr.SetRectEmpty(); 2518 | const auto uCtrlId = static_cast(GetDlgCtrlID()); 2519 | const LISTEXLINKINFO lli { .hdr { .hwndFrom { m_hWnd }, .idFrom { uCtrlId }, .code { LISTEX_MSG_LINKCLICK } }, 2520 | .iItem { hti.iItem }, .iSubItem { hti.iSubItem }, .ptClick { pt }, .pwszText { iterFind->wstrLink.data() } }; 2521 | ::SendMessageW(::GetParent(m_hWnd), WM_NOTIFY, static_cast(uCtrlId), reinterpret_cast(&lli)); 2522 | return 0; 2523 | } 2524 | } 2525 | 2526 | m_fLDownAtLink = false; 2527 | 2528 | return wnd::DefSubclassProc(msg); 2529 | } 2530 | 2531 | auto CListEx::OnMouseWheel(const MSG& msg)->LRESULT 2532 | { 2533 | const auto zDelta = GET_WHEEL_DELTA_WPARAM(msg.wParam); 2534 | const auto nFlags = GET_KEYSTATE_WPARAM(msg.wParam); 2535 | if (nFlags == MK_CONTROL) { 2536 | FontSizeIncDec(zDelta > 0); 2537 | return 0; 2538 | } 2539 | 2540 | GetHeader().RedrawWindow(); 2541 | 2542 | return wnd::DefSubclassProc(msg); 2543 | } 2544 | 2545 | auto CListEx::OnMouseMove(const MSG& msg)->LRESULT 2546 | { 2547 | static const auto hCurArrow = static_cast(::LoadImageW(nullptr, IDC_ARROW, IMAGE_CURSOR, 0, 0, 2548 | LR_DEFAULTSIZE | LR_SHARED)); 2549 | static const auto hCurHand = static_cast(::LoadImageW(nullptr, IDC_HAND, IMAGE_CURSOR, 0, 0, 2550 | LR_DEFAULTSIZE | LR_SHARED)); 2551 | const POINT pt { .x { wnd::GetXLPARAM(msg.lParam) }, .y { wnd::GetYLPARAM(msg.lParam) } }; 2552 | 2553 | LVHITTESTINFO hti { .pt { pt } }; 2554 | HitTest(&hti); 2555 | bool fLinkRect { false }; //Cursor at link's rect area? 2556 | if (hti.iItem >= 0 && hti.iSubItem >= 0) { 2557 | auto vecText = ParseItemData(hti.iItem, hti.iSubItem); //Non const to allow std::move(itLink->wstrLink). 2558 | auto itLink = std::find_if(vecText.begin(), vecText.end(), //Non const to allow std::move(itLink->wstrLink). 2559 | [&](const ITEMDATA& item) { return item.fLink && item.rc.PtInRect(pt); }); 2560 | if (itLink != vecText.end()) { 2561 | fLinkRect = true; 2562 | if (m_fLinkTooltip && !m_fLDownAtLink && m_rcLinkCurr != itLink->rc) { 2563 | TTLinkShow(false); 2564 | m_fLinkTTActive = true; 2565 | m_rcLinkCurr = itLink->rc; 2566 | m_htiCurrLink.iItem = hti.iItem; 2567 | m_htiCurrLink.iSubItem = hti.iSubItem; 2568 | m_wstrTTText = itLink->fTitle ? std::move(itLink->wstrTitle) : std::move(itLink->wstrLink); 2569 | if (m_dwTTDelayTime > 0) { 2570 | ::SetTimer(m_hWnd, m_uIDTTTLinkActivate, m_dwTTDelayTime, nullptr); //Activate link's tooltip after delay. 2571 | } 2572 | else { TTLinkShow(true); } //Activate immediately. 2573 | } 2574 | } 2575 | } 2576 | 2577 | ::SetCursor(fLinkRect ? hCurHand : hCurArrow); 2578 | 2579 | if (fLinkRect) { //Link's rect is under the cursor. 2580 | if (m_fCellTTActive) { //If there is a cell's tooltip atm, hide it. 2581 | TTCellShow(false); 2582 | } 2583 | return 0; //Do not process further, cursor is on the link's rect. 2584 | } 2585 | 2586 | m_rcLinkCurr.SetRectEmpty(); //If cursor is not over any link. 2587 | m_fLDownAtLink = false; 2588 | 2589 | if (m_fLinkTTActive) { //If there was a link's tool-tip shown, hide it. 2590 | TTLinkShow(false); 2591 | } 2592 | 2593 | if (const auto optTT = GetTooltip(hti.iItem, hti.iSubItem); optTT) { 2594 | //Check if cursor is still in the same cell's rect. If so - just leave. 2595 | if (m_htiCurrCell.iItem != hti.iItem || m_htiCurrCell.iSubItem != hti.iSubItem) { 2596 | m_fCellTTActive = true; 2597 | m_htiCurrCell.iItem = hti.iItem; 2598 | m_htiCurrCell.iSubItem = hti.iSubItem; 2599 | m_wstrTTText = optTT->pwszText; //Tooltip text. 2600 | m_wstrTTCaption = optTT->pwszCaption ? optTT->pwszCaption : L""; //Tooltip caption. 2601 | if (m_dwTTDelayTime > 0) { 2602 | ::SetTimer(m_hWnd, m_uIDTTTCellActivate, m_dwTTDelayTime, nullptr); //Activate cell's tooltip after delay. 2603 | } 2604 | else { TTCellShow(true); }; //Activate immediately. 2605 | } 2606 | } 2607 | else { 2608 | if (m_fCellTTActive) { 2609 | TTCellShow(false); 2610 | } 2611 | else { 2612 | m_htiCurrCell.iItem = -1; 2613 | m_htiCurrCell.iSubItem = -1; 2614 | } 2615 | } 2616 | 2617 | return 0; 2618 | } 2619 | 2620 | auto CListEx::OnNotify(const MSG& msg)->LRESULT 2621 | { 2622 | if (!m_fCreated) { 2623 | return wnd::DefSubclassProc(msg); 2624 | } 2625 | 2626 | //HDN_ITEMCLICK messages should be handled here first, to set m_fSortAscending 2627 | //and m_iSortColumn. And only then this message goes to parent window in form 2628 | //of HDN_ITEMCLICK and LVN_COLUMNCLICK. 2629 | //If we execute this code in LVN_COLUMNCLICK handler, it will be handled only 2630 | //AFTER the parent window handles LVN_COLUMNCLICK. 2631 | //Briefly: CListEx::OnLvnColumnClick fires up only AFTER LVN_COLUMNCLICK sent to the parent. 2632 | 2633 | const auto pNMHDR = reinterpret_cast(msg.lParam); 2634 | const auto pNMLV = reinterpret_cast(msg.lParam); 2635 | switch (pNMHDR->idFrom) { 2636 | case 0: //Header control. 2637 | switch (pNMHDR->code) { 2638 | case HDN_BEGINDRAG: 2639 | case HDN_BEGINTRACKA: 2640 | case HDN_BEGINTRACKW: 2641 | if (GetHeader().IsColumnHidden(pNMLV->iItem)) { 2642 | return TRUE; //Return TRUE to disable further drag/track processing by the Header control. 2643 | } 2644 | break; 2645 | case HDN_ITEMCLICKA: 2646 | case HDN_ITEMCLICKW: 2647 | if (m_fSortable) { 2648 | if (IsColumnSortable(pNMLV->iItem)) { 2649 | m_fSortAsc = pNMLV->iItem == m_iSortColumn ? !m_fSortAsc : true; 2650 | GetHeader().SetSortArrow(pNMLV->iItem, m_fSortAsc); 2651 | m_iSortColumn = pNMLV->iItem; 2652 | } 2653 | else { 2654 | m_iSortColumn = -1; 2655 | } 2656 | 2657 | if (!m_fVirtual) { 2658 | SortItemsEx(m_pfnCompare ? m_pfnCompare : DefCompareFunc, reinterpret_cast(this)); 2659 | } 2660 | } break; 2661 | default: break; 2662 | } 2663 | break; 2664 | case m_uIDEditInPlace: //Edit in-place control. 2665 | switch (pNMHDR->code) { 2666 | case VK_RETURN: 2667 | case VK_ESCAPE: 2668 | OnNotifyEditInPlace(pNMHDR); 2669 | break; 2670 | default: break; 2671 | } 2672 | default: break; 2673 | } 2674 | 2675 | return wnd::DefSubclassProc(msg); 2676 | } 2677 | 2678 | void CListEx::OnNotifyEditInPlace(NMHDR* pNMHDR) 2679 | { 2680 | switch (pNMHDR->code) { 2681 | case VK_RETURN: 2682 | OnEditInPlaceEnterPressed(); 2683 | break; 2684 | case VK_ESCAPE: 2685 | OnEditInPlaceKillFocus(); 2686 | break; 2687 | default: 2688 | break; 2689 | } 2690 | } 2691 | 2692 | auto CListEx::OnPaint()->LRESULT 2693 | { 2694 | const wnd::CPaintDC dcPaint(m_hWnd); 2695 | wnd::CRect rcClient; 2696 | ::GetClientRect(m_hWnd, rcClient); 2697 | const wnd::CRect rcHdr = GetHeader().GetClientRect(); 2698 | rcClient.top += rcHdr.Height(); 2699 | rcClient.bottom += rcHdr.Height(); 2700 | if (rcClient.IsRectEmpty()) { 2701 | return 0; 2702 | } 2703 | 2704 | const wnd::CMemDC dcMem(dcPaint, rcClient); //To avoid flickering drawing to CMemDC, excluding list header area. 2705 | ::GetClipBox(dcMem, rcClient); 2706 | dcMem.FillSolidRect(rcClient, m_stColors.clrNWABk); 2707 | 2708 | return ::DefSubclassProc(m_hWnd, WM_PAINT, reinterpret_cast(dcMem.GetHDC()), 0); 2709 | } 2710 | 2711 | auto CListEx::OnTimer(const MSG& msg)->LRESULT 2712 | { 2713 | const auto nIDEvent = static_cast(msg.wParam); 2714 | if (nIDEvent != m_uIDTTTCellActivate && nIDEvent != m_uIDTTTLinkActivate 2715 | && nIDEvent != m_uIDTTTCellCheck && nIDEvent != m_uIDTTTLinkCheck) { 2716 | return wnd::DefSubclassProc(msg); 2717 | } 2718 | 2719 | POINT ptCur; 2720 | ::GetCursorPos(&ptCur); 2721 | POINT ptCurClient { ptCur }; 2722 | ::ScreenToClient(m_hWnd, &ptCurClient); 2723 | LVHITTESTINFO hitInfo { .pt { ptCurClient } }; 2724 | HitTest(&hitInfo); 2725 | 2726 | switch (nIDEvent) { 2727 | case m_uIDTTTCellActivate: 2728 | ::KillTimer(m_hWnd, m_uIDTTTCellActivate); 2729 | if (m_htiCurrCell.iItem == hitInfo.iItem && m_htiCurrCell.iSubItem == hitInfo.iSubItem) { 2730 | TTCellShow(true); 2731 | } 2732 | break; 2733 | case m_uIDTTTLinkActivate: 2734 | ::KillTimer(m_hWnd, m_uIDTTTLinkActivate); 2735 | if (m_rcLinkCurr.PtInRect(ptCurClient)) { 2736 | TTLinkShow(true); 2737 | } 2738 | break; 2739 | case m_uIDTTTCellCheck: 2740 | { 2741 | //If cursor has left cell's rect, or time run out. 2742 | const auto msElapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - m_tmTT).count(); 2743 | if (m_htiCurrCell.iItem != hitInfo.iItem || m_htiCurrCell.iSubItem != hitInfo.iSubItem) { 2744 | TTCellShow(false); 2745 | } 2746 | else if (msElapsed >= m_dwTTShowTime) { 2747 | TTCellShow(false, true); 2748 | } 2749 | } 2750 | break; 2751 | case m_uIDTTTLinkCheck: 2752 | { 2753 | //If cursor has left link subitem's rect, or time run out. 2754 | const auto msElapsed = std::chrono::duration(std::chrono::high_resolution_clock::now() - m_tmTT).count(); 2755 | if (m_htiCurrLink.iItem != hitInfo.iItem || m_htiCurrLink.iSubItem != hitInfo.iSubItem) { 2756 | TTLinkShow(false); 2757 | } 2758 | else if (msElapsed >= m_dwTTShowTime) { 2759 | TTLinkShow(false, true); 2760 | } 2761 | } 2762 | break; 2763 | default: 2764 | wnd::DefSubclassProc(msg); 2765 | break; 2766 | } 2767 | 2768 | return 0; 2769 | } 2770 | 2771 | auto CListEx::OnVScroll(const MSG& msg)->LRESULT 2772 | { 2773 | const auto nSBCode = LOWORD(msg.wParam); 2774 | if (m_fVirtual && m_fHighLatency) { 2775 | if (nSBCode != SB_THUMBTRACK) { 2776 | //If there was SB_THUMBTRACK message previously, calculate the scroll amount (up/down) 2777 | //by multiplying item's row height by difference between current (top) and nPos row. 2778 | //Scroll may be negative therefore. 2779 | if (m_fHLFlag) { 2780 | const wnd::CRect rc = GetItemRect(m_uHLItem, LVIR_LABEL); 2781 | const SIZE size(0, (m_uHLItem - GetTopIndex()) * rc.Height()); 2782 | Scroll(size); 2783 | m_fHLFlag = false; 2784 | } 2785 | TTHLShow(false, 0); 2786 | wnd::DefSubclassProc(msg); 2787 | } 2788 | else { 2789 | m_fHLFlag = true; 2790 | SCROLLINFO si { .cbSize { sizeof(SCROLLINFO) }, .fMask { SIF_ALL } }; 2791 | ::GetScrollInfo(m_hWnd, SB_VERT, &si); 2792 | m_uHLItem = si.nTrackPos; //si.nTrackPos is in fact a row number. 2793 | TTHLShow(true, m_uHLItem); 2794 | } 2795 | } 2796 | else { 2797 | wnd::DefSubclassProc(msg); 2798 | } 2799 | 2800 | return 0; 2801 | } 2802 | 2803 | auto CListEx::ParseItemData(int iItem, int iSubitem)->std::vector 2804 | { 2805 | constexpr auto iIndentRc { 4 }; 2806 | const auto wstrText = GetItemText(iItem, iSubitem); 2807 | const std::wstring_view wsvText = wstrText; 2808 | wnd::CRect rcTextOrig = GetSubItemRect(iItem, iSubitem, LVIR_LABEL); //Original rect of the subitem's text. 2809 | if (iSubitem > 0) { //Not needed for item itself (not subitem). 2810 | rcTextOrig.left += iIndentRc; 2811 | } 2812 | 2813 | std::vector vecData; 2814 | int iImageWidth { 0 }; 2815 | 2816 | if (const auto iIndex = GetIcon(iItem, iSubitem); iIndex > -1) { //If cell has icon. 2817 | IMAGEINFO stII; 2818 | ::ImageList_GetImageInfo(GetImageList(LVSIL_NORMAL), iIndex, &stII); 2819 | iImageWidth = stII.rcImage.right - stII.rcImage.left; 2820 | const auto iImgHeight = stII.rcImage.bottom - stII.rcImage.top; 2821 | const auto iImgTop = iImgHeight < rcTextOrig.Height() ? 2822 | rcTextOrig.top + (rcTextOrig.Height() / 2 - iImgHeight / 2) : rcTextOrig.top; 2823 | const RECT rcImage(rcTextOrig.left, iImgTop, rcTextOrig.left + iImageWidth, iImgTop + iImgHeight); 2824 | vecData.emplace_back(iIndex, rcImage); 2825 | rcTextOrig.left += iImageWidth + iIndentRc; //Offset rect for image width. 2826 | } 2827 | 2828 | std::size_t nPosCurr { 0 }; //Current position in the parsed string. 2829 | wnd::CRect rcTextCurr; //Current rect. 2830 | const auto hDC = ::GetDC(m_hWnd); 2831 | 2832 | while (nPosCurr != std::wstring_view::npos) { 2833 | static constexpr std::wstring_view wsvTagLink { L"" }; 2835 | static constexpr std::wstring_view wsvTagLast { L"" }; 2836 | static constexpr std::wstring_view wsvTagTitle { L"title=" }; 2837 | static constexpr std::wstring_view wsvQuote { L"\"" }; 2838 | 2839 | //Searching the string for a pattern. 2840 | if (const std::size_t nPosTagLink { wsvText.find(wsvTagLink, nPosCurr) }, //Start position of the opening tag "". 2845 | nPosTagFirstClose { wsvText.find(wsvTagFirstClose, nPosLinkCloseQuote + wsvQuote.size()) }, 2846 | //Start position of the enclosing tag "". 2847 | nPosTagLast { wsvText.find(wsvTagLast, nPosTagFirstClose + wsvTagFirstClose.size()) }; 2848 | m_fLinks && nPosTagLink != std::wstring_view::npos && nPosLinkOpenQuote != std::wstring_view::npos 2849 | && nPosLinkCloseQuote != std::wstring_view::npos && nPosTagFirstClose != std::wstring_view::npos 2850 | && nPosTagLast != std::wstring_view::npos) { 2851 | ::SelectObject(hDC, m_hFntList); 2852 | SIZE size; 2853 | 2854 | //Any text before found tag. 2855 | if (nPosTagLink > nPosCurr) { 2856 | const auto wsvTextBefore = wsvText.substr(nPosCurr, nPosTagLink - nPosCurr); 2857 | ::GetTextExtentPoint32W(hDC, wsvTextBefore.data(), static_cast(wsvTextBefore.size()), &size); 2858 | if (rcTextCurr.IsRectNull()) { 2859 | rcTextCurr.SetRect(rcTextOrig.left, rcTextOrig.top, rcTextOrig.left + size.cx, rcTextOrig.bottom); 2860 | } 2861 | else { 2862 | rcTextCurr.left = rcTextCurr.right; 2863 | rcTextCurr.right += size.cx; 2864 | } 2865 | vecData.emplace_back(wsvTextBefore, L"", L"", rcTextCurr); 2866 | } 2867 | 2868 | //The clickable/linked text, that between textFromHere tags. 2869 | const auto wsvTextBetweenTags = wsvText.substr(nPosTagFirstClose + wsvTagFirstClose.size(), 2870 | nPosTagLast - (nPosTagFirstClose + wsvTagFirstClose.size())); 2871 | ::GetTextExtentPoint32W(hDC, wsvTextBetweenTags.data(), static_cast(wsvTextBetweenTags.size()), &size); 2872 | 2873 | if (rcTextCurr.IsRectNull()) { 2874 | rcTextCurr.SetRect(rcTextOrig.left, rcTextOrig.top, rcTextOrig.left + size.cx, rcTextOrig.bottom); 2875 | } 2876 | else { 2877 | rcTextCurr.left = rcTextCurr.right; 2878 | rcTextCurr.right += size.cx; 2879 | } 2880 | 2881 | //Link tag text (linkID) between quotes: 2882 | const auto wsvTextLink = wsvText.substr(nPosLinkOpenQuote + wsvQuote.size(), 2883 | nPosLinkCloseQuote - nPosLinkOpenQuote - wsvQuote.size()); 2884 | nPosCurr = nPosLinkCloseQuote + wsvQuote.size(); 2885 | 2886 | //Searching for title "" tag. 2887 | bool fTitle { false }; 2888 | std::wstring_view wsvTextTitle { }; 2889 | 2890 | if (const std::size_t nPosTagTitle { wsvText.find(wsvTagTitle, nPosCurr) }, //Position of the (title=) tag beginning. 2891 | nPosTitleOpenQuote { wsvText.find(wsvQuote, nPosTagTitle) }, //Position of the (title="<-) opening quote. 2892 | nPosTitleCloseQuote { wsvText.find(wsvQuote, nPosTitleOpenQuote + wsvQuote.size()) }; //Position of the (title=""<-) closing quote. 2893 | nPosTagTitle != std::wstring_view::npos && nPosTitleOpenQuote != std::wstring_view::npos 2894 | && nPosTitleCloseQuote != std::wstring_view::npos) { 2895 | //Title tag text between quotes: <...title="textFromHere"> 2896 | wsvTextTitle = wsvText.substr(nPosTitleOpenQuote + wsvQuote.size(), 2897 | nPosTitleCloseQuote - nPosTitleOpenQuote - wsvQuote.size()); 2898 | fTitle = true; 2899 | } 2900 | 2901 | vecData.emplace_back(wsvTextBetweenTags, wsvTextLink, wsvTextTitle, rcTextCurr, true, fTitle); 2902 | nPosCurr = nPosTagLast + wsvTagLast.size(); 2903 | } 2904 | else { 2905 | const auto wsvTextAfter = wsvText.substr(nPosCurr, wsvText.size() - nPosCurr); 2906 | ::SelectObject(hDC, m_hFntList); 2907 | SIZE size; 2908 | ::GetTextExtentPoint32W(hDC, wsvTextAfter.data(), static_cast(wsvTextAfter.size()), &size); 2909 | 2910 | if (rcTextCurr.IsRectNull()) { 2911 | rcTextCurr.SetRect(rcTextOrig.left, rcTextOrig.top, rcTextOrig.left + size.cx, rcTextOrig.bottom); 2912 | } 2913 | else { 2914 | rcTextCurr.left = rcTextCurr.right; 2915 | rcTextCurr.right += size.cx; 2916 | } 2917 | 2918 | vecData.emplace_back(wsvTextAfter, L"", L"", rcTextCurr); 2919 | nPosCurr = std::wstring_view::npos; 2920 | } 2921 | } 2922 | ::ReleaseDC(m_hWnd, hDC); 2923 | 2924 | //Depending on column's data alignment we adjust rects coords to render properly. 2925 | switch (GetHeader().GetColumnDataAlign(iSubitem)) { 2926 | case LVCFMT_LEFT: //Do nothing, by default it's already left aligned. 2927 | break; 2928 | case LVCFMT_CENTER: 2929 | { 2930 | int iWidthTotal { }; 2931 | for (const auto& iter : vecData) { 2932 | iWidthTotal += iter.rc.Width(); 2933 | } 2934 | 2935 | if (iWidthTotal < rcTextOrig.Width()) { 2936 | const auto iRcOffset = (rcTextOrig.Width() - iWidthTotal) / 2; 2937 | for (auto& iter : vecData) { 2938 | if (iter.iIconIndex == -1) { //Offset only rects with text, not icons rects. 2939 | iter.rc.OffsetRect(iRcOffset, 0); 2940 | } 2941 | } 2942 | } 2943 | } 2944 | break; 2945 | case LVCFMT_RIGHT: 2946 | { 2947 | int iWidthTotal { }; 2948 | for (const auto& iter : vecData) { 2949 | iWidthTotal += iter.rc.Width(); 2950 | } 2951 | 2952 | const auto iWidthRect = rcTextOrig.Width() + iImageWidth - iIndentRc; 2953 | if (iWidthTotal < iWidthRect) { 2954 | const auto iRcOffset = iWidthRect - iWidthTotal; 2955 | for (auto& iter : vecData) { 2956 | if (iter.iIconIndex == -1) { //Offset only rects with text, not icons rects. 2957 | iter.rc.OffsetRect(iRcOffset, 0); 2958 | } 2959 | } 2960 | } 2961 | } 2962 | break; 2963 | default: 2964 | break; 2965 | } 2966 | 2967 | return vecData; 2968 | } 2969 | 2970 | void CListEx::RedrawWindow()const 2971 | { 2972 | assert(IsWindow()); 2973 | ::RedrawWindow(m_hWnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); 2974 | } 2975 | 2976 | void CListEx::RecalcMeasure()const 2977 | { 2978 | //To get WM_MEASUREITEM after changing the font. 2979 | wnd::CRect rc; 2980 | ::GetWindowRect(m_hWnd, rc); 2981 | const WINDOWPOS wp { .hwnd { m_hWnd }, .cx { rc.Width() }, .cy { rc.Height() }, 2982 | .flags { SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER } }; 2983 | ::SendMessageW(m_hWnd, WM_WINDOWPOSCHANGED, 0, reinterpret_cast(&wp)); 2984 | } 2985 | 2986 | void CListEx::SetFontSize(long lSize) 2987 | { 2988 | assert(IsCreated()); 2989 | if (!IsCreated()) { 2990 | return; 2991 | } 2992 | 2993 | //Prevent font size from being too small or too big. 2994 | if (lSize < 4 || lSize > 64) { 2995 | return; 2996 | } 2997 | 2998 | LOGFONTW lf { }; 2999 | ::GetObjectW(m_hFntList, sizeof(lf), &lf); 3000 | lf.lfHeight = -::MulDiv(lSize, m_iLOGPIXELSY, 72); 3001 | SetFont(lf); 3002 | } 3003 | 3004 | void CListEx::TTCellShow(bool fShow, bool fTimer) 3005 | { 3006 | TTTOOLINFOW ttiCell { .cbSize { sizeof(TTTOOLINFOW) }, .lpszText { m_wstrTTText.data() } }; 3007 | if (fShow) { 3008 | wnd::CPoint ptCur; 3009 | ::GetCursorPos(&ptCur); 3010 | ptCur.Offset(m_ptTTOffset); 3011 | ::SendMessageW(m_hWndCellTT, TTM_TRACKPOSITION, 0, static_cast(MAKELONG(ptCur.x, ptCur.y))); 3012 | ::SendMessageW(m_hWndCellTT, TTM_SETTITLEW, TTI_NONE, reinterpret_cast(m_wstrTTCaption.data())); 3013 | ::SendMessageW(m_hWndCellTT, TTM_UPDATETIPTEXTW, 0, reinterpret_cast(&ttiCell)); 3014 | ::SendMessageW(m_hWndCellTT, TTM_TRACKACTIVATE, TRUE, reinterpret_cast(&ttiCell)); 3015 | m_tmTT = std::chrono::high_resolution_clock::now(); 3016 | ::SetTimer(m_hWnd, m_uIDTTTCellCheck, 300, nullptr); //Timer to check whether mouse has left subitem's rect. 3017 | } 3018 | else { 3019 | ::KillTimer(m_hWnd, m_uIDTTTCellCheck); 3020 | 3021 | //When hiding tooltip by timer we not nullify the m_htiCurrCell. 3022 | //Otherwise tooltip will be shown again after mouse movement, 3023 | //even if cursor didn't leave current cell area. 3024 | if (!fTimer) { 3025 | m_htiCurrCell.iItem = -1; 3026 | m_htiCurrCell.iSubItem = -1; 3027 | } 3028 | 3029 | m_fCellTTActive = false; 3030 | ::SendMessageW(m_hWndCellTT, TTM_TRACKACTIVATE, FALSE, reinterpret_cast(&ttiCell)); 3031 | } 3032 | } 3033 | 3034 | void CListEx::TTLinkShow(bool fShow, bool fTimer) 3035 | { 3036 | TTTOOLINFOW ttiLink { .cbSize { sizeof(TTTOOLINFOW) }, .lpszText { m_wstrTTText.data() } }; 3037 | if (fShow) { 3038 | wnd::CPoint ptCur; 3039 | ::GetCursorPos(&ptCur); 3040 | ptCur.Offset(m_ptTTOffset); 3041 | ::SendMessageW(m_hWndLinkTT, TTM_TRACKPOSITION, 0, static_cast(MAKELONG(ptCur.x, ptCur.y))); 3042 | ::SendMessageW(m_hWndLinkTT, TTM_UPDATETIPTEXTW, 0, reinterpret_cast(&ttiLink)); 3043 | ::SendMessageW(m_hWndLinkTT, TTM_TRACKACTIVATE, TRUE, reinterpret_cast(&ttiLink)); 3044 | m_tmTT = std::chrono::high_resolution_clock::now(); 3045 | ::SetTimer(m_hWnd, m_uIDTTTLinkCheck, 300, nullptr); //Timer to check whether mouse has left link's rect. 3046 | } 3047 | else { 3048 | ::KillTimer(m_hWnd, m_uIDTTTLinkCheck); 3049 | if (!fTimer) { 3050 | m_htiCurrLink.iItem = -1; 3051 | m_htiCurrLink.iSubItem = -1; 3052 | m_rcLinkCurr.SetRectEmpty(); 3053 | } 3054 | 3055 | m_fLinkTTActive = false; 3056 | ::SendMessageW(m_hWndLinkTT, TTM_TRACKACTIVATE, FALSE, reinterpret_cast(&ttiLink)); 3057 | } 3058 | } 3059 | 3060 | void CListEx::TTHLShow(bool fShow, UINT uRow) 3061 | { 3062 | if (fShow) { 3063 | POINT ptCur; 3064 | ::GetCursorPos(&ptCur); 3065 | wchar_t warrOffset[32]; 3066 | *std::format_to(warrOffset, L"Row: {}", uRow) = L'\0'; 3067 | TTTOOLINFOW ttiHL { .cbSize { sizeof(TTTOOLINFOW) }, .lpszText { warrOffset } }; 3068 | ::SendMessageW(m_hWndRowTT, TTM_TRACKPOSITION, 0, static_cast(MAKELONG(ptCur.x - 5, ptCur.y - 20))); 3069 | ::SendMessageW(m_hWndRowTT, TTM_UPDATETIPTEXTW, 0, reinterpret_cast(&ttiHL)); 3070 | } 3071 | 3072 | TTTOOLINFOW ttiHL { .cbSize { sizeof(TTTOOLINFOW) }, }; 3073 | ::SendMessageW(m_hWndRowTT, TTM_TRACKACTIVATE, fShow, reinterpret_cast(&ttiHL)); 3074 | } 3075 | 3076 | auto CListEx::SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, 3077 | UINT_PTR uIDSubclass, DWORD_PTR /*dwRefData*/) -> LRESULT { 3078 | if (uMsg == WM_NCDESTROY) { 3079 | ::RemoveWindowSubclass(hWnd, SubclassProc, uIDSubclass); 3080 | } 3081 | 3082 | const auto pListEx = reinterpret_cast(uIDSubclass); 3083 | return pListEx->ProcessMsg({ .hwnd { hWnd }, .message { uMsg }, .wParam { wParam }, .lParam { lParam } }); 3084 | } 3085 | 3086 | auto CListEx::EditSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, 3087 | UINT_PTR uIdSubclass, DWORD_PTR /*dwRefData*/)->LRESULT 3088 | { 3089 | switch (uMsg) { 3090 | case WM_GETDLGCODE: 3091 | return DLGC_WANTALLKEYS; 3092 | case WM_KEYDOWN: 3093 | if (wParam == VK_ESCAPE || wParam == VK_RETURN) { 3094 | const NMHDR hdr { .hwndFrom { hWnd }, .idFrom { m_uIDEditInPlace }, .code { static_cast(wParam) } }; 3095 | ::SendMessageW(::GetParent(hWnd), WM_NOTIFY, 0, reinterpret_cast(&hdr)); 3096 | } 3097 | break; 3098 | case WM_NCDESTROY: 3099 | ::RemoveWindowSubclass(hWnd, EditSubclassProc, uIdSubclass); 3100 | break; 3101 | default: 3102 | break; 3103 | } 3104 | 3105 | return ::DefSubclassProc(hWnd, uMsg, wParam, lParam); 3106 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## **Introduction** 2 | **CListEx** is an advanced owner-draw **List Control** class, written in pure Win32 API. 3 | ![](docs/img/listex_mainwnd.jpg) 4 | 5 | ## Table of Contents 6 | * [Features](#features) 7 | * [Installation](#installation) 8 | * [Creating](#creating) 9 | * [Manually](#manually) 10 | * [In Dialog](#in-dialog) 11 | * [Sorting](#sorting) 12 | * [Editing Cells](#editing-cells) 13 | * [Data Alignment](#data-alignment) 14 | * [Public Methods](#public-methods) 15 | * [HideColumn](#hidecolumn) 16 | * [SetColumnEditable](setcolumneditable) 17 | * [SetHdrColumnIcon](#sethdrcolumnicon) 18 | * [SetHdrHeight](#sethdrheight) 19 | * [SetSortable](#setsortable) 20 | * [Notification Messages](#notification-messages)
_Expand_ 21 | * [LISTEX_MSG_EDITBEGIN](#listex_msg_editbegin) 22 | * [LISTEX_MSG_GETCOLOR](#listex_msg_getcolor) 23 | * [LISTEX_MSG_GETICON](#listex_msg_geticon) 24 | * [LISTEX_MSG_GETTOOLTIP](#) 25 | * [LISTEX_MSG_LINKCLICK](#listex_msg_linkclick) 26 | * [LISTEX_MSG_HDRICONCLICK](#listex_msg_hdriconclick) 27 | * [LISTEX_MSG_HDRRBTNDOWN](#) 28 | * [LISTEX_MSG_HDRRBTNUP](#) 29 | * [LISTEX_MSG_SETDATA](#listex_msg_setdata) 30 | * [Example](#example) 31 | 32 | ## [](#)Features 33 | * [Editable cells](#editing-cells), not only the first column 34 | * [Hyperlinks](#listex_msg_linkclick) in a cells' text 35 | * [Tooltips](#setcelltooltip) for individual cells 36 | * [Background and text color](#setcellcolor) for individual cells 37 | * Many options to set individual colors for lots of list aspects with the [LISTEXCOLOR] 38 | * Set header height and font 39 | * Set header color for individual columns 40 | * Individual text alignment for a header and column itself 41 | * [Header icons](#listex_msg_hdriconclick) 42 | * [Hiding individual columns](#hidecolumn) 43 | * Innate ability to sort list columns with no additional effort 44 | * Dynamically changed list's font size with the **Ctrl+MouseWheel** 45 | 46 | ## [](#)Installation 47 | 1. Add `ListEx.ixx` into your project 48 | 2. Import `ListEx` module and declare `CListEx` object somewhere: 49 | ```cpp 50 | import ListEx; 51 | LISTEX::CListEx myList; 52 | ``` 53 | 54 | ## [](#)Creating 55 | 56 | ### [](#)Manually 57 | `Create` is the main method to create **CListEx** control, it takes [`LISTEXCREATE`](#listexcreate) structure as an argument. It's also important to add handlers for two Windows messages, `WM_DRAWITEM` and `WM_MEASUREITEM`. 58 | #### Example: 59 | ```cpp 60 | CListEx myList; 61 | 62 | LISTEXCREATE lcs; 63 | lcs.uID = ID_MY_LIST; 64 | lcs.hWndParent = m_hWnd; 65 | lcs.rect = CRect(0, 0, 500, 300); 66 | //lcs.fDialogCtrl = true; //If it's control in a Dialog. 67 | myList.Create(lcs); 68 | 69 | void CMyDialog::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 70 | { 71 | if (nIDCtl == ID_MY_LIST) { 72 | myList.DrawItem(lpDrawItemStruct); 73 | return; 74 | } 75 | 76 | CDialogEx::OnDrawItem(nIDCtl, lpDrawItemStruct); 77 | } 78 | 79 | void CMyDialog::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 80 | { 81 | if (nIDCtl == ID_MY_LIST) { 82 | myList.MeasureItem(lpMeasureItemStruct); 83 | return; 84 | } 85 | 86 | CDialogEx::OnMeasureItem(nIDCtl, lpMeasureItemStruct); 87 | } 88 | ``` 89 | 90 | ### [](#)In Dialog 91 | To create **CListEx** in a Dialog you can manually do it with the [Create](#manually) method. 92 | But most of the time you prefer to place a standard **List Control** onto a Dialog's template, by dragging it from the **Toolbox** within **Visual studio**. 93 | To use the latter approach follow these steps: 94 | 1. Put a standard **List Control** from the toolbox onto your dialog. 95 | 2. Declare `CListEx` member variable within your dialog class. 96 | 3. In your `OnInitDialog` method call the `m_myList.CreateDialogCtrl(ID_MY_LIST, m_hWnd);` method. 97 | 98 | ## [](#)Sorting 99 | To enable sorting set the [`LISTEXCREATE::fSortable`](#listexcreate) flag to `true`. In this case when you click on the header list will be sorted according to the clicked column. By default **CListEx** performs lexicographical sorting. 100 | To set your own sorting routine use the [`SetSortable`](#setsortable) method. 101 | 102 | ## [](#)Editing Cells 103 | By default **CListEx** works in the read-only mode. To enable cells editing call the [`SetColumnEditable`](#setcolumneditable) method with the column ID which cells you wish to become editable. 104 | 105 | ## [](#)Data Alignment 106 | Classical List Control allows setting an alignment only for a header and column simultaneously. 107 | **CListEx** allows setting alignment separately for the header and the data. The `iDataAlign` argument in the `InsertColumn()` method is responsible exactly for that. 108 | 109 | ## [](#)Public Methods 110 | `CListEx` class also has a set of additional methods to help customize your control in many different aspects. 111 | 112 | ### [](#)HideColumn 113 | ```cpp 114 | void HideColumn(int iIndex, bool fHide); 115 | ``` 116 | Hide or show column by `iIndex`. 117 | 118 | ### [](#)SetColumnEditable 119 | ```cpp 120 | void SetColumnEditable(int iColumn, bool fEditable); 121 | ``` 122 | Enables or disables edit mode for a given column. 123 | 124 | ### [](#)SetHdrColumnIcon 125 | ```cpp 126 | void SetHdrColumnIcon(int iColumn, int iIconIndex, bool fClick = false); 127 | ``` 128 | Sets the icon index in the header's image list for a given `iColumn`. To remove icon from column set the `iIconIndex` to `-1`. 129 | Flag `fClick` means that icon is clickable. See [`LISTEX_MSG_HDRICONCLICK`](#listex_msg_hdriconclick) message for more info. 130 | 131 | ### [](#)SetHdrHeight 132 | ```cpp 133 | void SetHdrHeight(DWORD dwHeight); 134 | ``` 135 | 136 | ### [](#)SetSortable 137 | ```cpp 138 | void SetSortable(bool fSortable, PFNLVCOMPARE pfnCompare = nullptr, EListExSortMode enSortMode = EListExSortMode::SORT_LEX) 139 | ``` 140 | **Parameters:** 141 | `bool fSortable` 142 | Enables or disables sorting 143 | 144 | `PFNLVCOMPARE pfnCompare` 145 | Callback function pointer with the type `int (CALLBACK *PFNLVCOMPARE)(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)` that is used to set your own comparison function. If it's `nullptr` **CListEx** performs default sorting. 146 | The comparison function must be either a static member of a class or a stand-alone function that is not a member of any class. 147 | 148 | `EListExSortMode enSortMode` 149 | Default sorting mode for the list. 150 | 151 | ## [](#)Notification Messages 152 | These messages are sent to the parent window in form of `WM_NOTIFY` Windows messages. 153 | The `lParam` will contain a pointer to the `NMHDR` standard Windows struct. `NMHDR::code` can be one of the `LISTEX_MSG_[XXXXX]` messages described below. 154 | 155 | ### [](#)LISTEX_MSG_EDITBEGIN 156 | Sent to the parent window when edit box for cell's data editing is about to show up. If you don't want it to show up set the `PLISTEXDATAINFO::fAllowEdit` to `false` in response. 157 | The `pwszData` member points to the text string for the edit box to display, you can change this text by setting your own pointer in responce, or you can copy new text straight to the buffer pointed to by the `pwszData`. The text limit is 256 `wchar_t` characters including terminating null. 158 | The `hWndEdit` member is a Windows handle to the edit box. 159 | ```cpp 160 | BOOL CMyDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* /*pResult*/) { 161 | const auto pLDI = reinterpret_cast(pNMHDR); 162 | ... 163 | pLDI->fAllowEdit = false; //Edit-box won't show up. 164 | } 165 | ``` 166 | 167 | ### [](#)LISTEX_MSG_GETCOLOR 168 | Sent to the parent window to retrieve cell's color information. 169 | ```cpp 170 | void CListDlg::OnListExGetColor(NMHDR* pNMHDR, LRESULT* /*pResult*/) { 171 | const auto pLCI = reinterpret_cast(pNMHDR); 172 | if (pLCI->iSubItem == 1) { //Column number 1 (for all rows) colored to RGB(0, 220, 220). 173 | pLCI->stClr = { RGB(0, 220, 220), RGB(0, 0, 0) }; 174 | } 175 | } 176 | ``` 177 | 178 | ### [](#)LISTEX_MSG_GETICON 179 | Sent to the parent window to retrieve cell's icon index in the list internal image list. 180 | ```cpp 181 | void CListDlg::OnListExGetIcon(NMHDR* pNMHDR, LRESULT* /*pResult*/) { 182 | const auto pLII = reinterpret_cast(pNMHDR); 183 | ... 184 | pLII->iIconIndex = 1; //Icon index in the list's image list. 185 | } 186 | ``` 187 | ### [](#)LISTEX_MSG_GETTOOLTIP 188 | Sent to the parent window to retrieve cell's tooltip information. 189 | ```cpp 190 | void CListDlg::OnListGetToolTip(NMHDR* pNMHDR, LRESULT* /*pResult*/) { 191 | const auto pTTI = reinterpret_cast(pNMHDR); 192 | const auto iItem = pTTI->iItem; 193 | 194 | static constexpr const wchar_t* ttData[] { L"Cell tooltip text...", L"Caption of the cell tooltip:" }; 195 | pTTI->stData.pwszText = ttData[0]; 196 | pTTI->stData.pwszCaption = ttData[1]; 197 | } 198 | ``` 199 | 200 | ### [](#)LISTEX_MSG_LINKCLICK 201 | List embedded hyperlink has been clicked. `WM_NOTIFY` `lParam` will point to the `LISTEXLINKINFO` struct. 202 | 203 | Hyperlink syntax is: `L"Text with the embedded link"` 204 | If no optional `title` tag is provided then the link text itself will be used as hyperlink's tool-tip. 205 | The `link` and the `title`'s text must be quoted `""`. 206 | 207 | ### [](#)LISTEX_MSG_HDRICONCLICK 208 | Header icon that previously was set by the [`SetHdrColumnIcon`](#sethdrcolumnicon) method has been clicked. 209 | Example code for handling this message: 210 | ```cpp 211 | BOOL CMyDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* /*pResult*/) { 212 | const auto pNMI = reinterpret_cast(lParam); 213 | 214 | if (pNMI->hdr.code == LISTEX_MSG_HDRICONCLICK && pNMI->hdr.idFrom == IDC_MYLIST) { 215 | const auto pNMI = reinterpret_cast(lParam); 216 | //pNMI->iItem holds clicked column index. 217 | } 218 | ... 219 | } 220 | ``` 221 | 222 | ### [](#)LISTEX_MSG_SETDATA 223 | Sent to the parent window when cell's text has been changed. 224 | ```cpp 225 | BOOL CMyDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* /*pResult*/) { 226 | const auto pLDI = reinterpret_cast(pNMHDR); 227 | 228 | const auto pwszNewText = pLDI->pwszData; 229 | ... 230 | } 231 | ``` 232 | 233 | ## [](#)Example 234 | Let’s imagine that you need a **CListEx** control with the non standard header height, and yellow background color. 235 | Nothing is simpler, see the code below: 236 | ```cpp 237 | LISTEXCREATE lcs; 238 | lcs.rect = CRect(0, 0, 500, 300) 239 | lcs.hWndParent = m_hWnd; 240 | lcs.dwHdrHeight = 50; 241 | lcs.stColor.clrListBkOdd = RGB(255, 255, 0); 242 | lcs.stColor.clrListBkEven = RGB(255, 255, 0); 243 | 244 | CListEx myList; 245 | myList.Create(lcs); 246 | myList.InsertColumn(...); 247 | myList.InsertItem(...); 248 | ``` 249 | Here we set both even and odd rows to the same yellow color. -------------------------------------------------------------------------------- /SampleProject/ListEx.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.156 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ListExSample", "ListExSample.vcxproj", "{9D1474B6-FF8F-4E42-80FA-E4D3580726B9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9}.Debug|x64.ActiveCfg = Debug|x64 17 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9}.Debug|x64.Build.0 = Debug|x64 18 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9}.Debug|x86.ActiveCfg = Debug|Win32 19 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9}.Debug|x86.Build.0 = Debug|Win32 20 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9}.Release|x64.ActiveCfg = Release|x64 21 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9}.Release|x64.Build.0 = Release|x64 22 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9}.Release|x86.ActiveCfg = Release|Win32 23 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {0087C654-EABD-4DB2-B683-542A97073A7D} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SampleProject/ListExSample.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "framework.h" 3 | #include "ListExSample.h" 4 | #include "ListExSampleDlg.h" 5 | 6 | #ifdef _DEBUG 7 | #define new DEBUG_NEW 8 | #endif 9 | 10 | BEGIN_MESSAGE_MAP(CListExSampleApp, CWinApp) 11 | ON_COMMAND(ID_HELP, &CWinApp::OnHelp) 12 | END_MESSAGE_MAP() 13 | 14 | CListExSampleApp theApp; 15 | 16 | BOOL CListExSampleApp::InitInstance() 17 | { 18 | CWinApp::InitInstance(); 19 | 20 | // Activate "Windows Native" visual manager for enabling themes in MFC controls 21 | CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows)); 22 | 23 | // Standard initialization 24 | // If you are not using these features and wish to reduce the size 25 | // of your final executable, you should remove from the following 26 | // the specific initialization routines you do not need 27 | // Change the registry key under which our settings are stored 28 | // TODO: You should modify this string to be something appropriate 29 | // such as the name of your company or organization 30 | SetRegistryKey(_T("ListEx Sample Application")); 31 | 32 | CListExSampleDlg dlg; 33 | m_pMainWnd = &dlg; 34 | const auto nResponse = dlg.DoModal(); 35 | if (nResponse == IDOK) { 36 | // TODO: Place code here to handle when the dialog is 37 | // dismissed with OK 38 | } 39 | else if (nResponse == IDCANCEL) { 40 | // TODO: Place code here to handle when the dialog is 41 | // dismissed with Cancel 42 | } 43 | else if (nResponse == -1) { 44 | TRACE(traceAppMsg, 0, "Warning: dialog creation failed, so application is terminating unexpectedly.\n"); 45 | TRACE(traceAppMsg, 0, "Warning: if you are using MFC controls on the dialog, you cannot #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS.\n"); 46 | } 47 | 48 | #if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS) 49 | ControlBarCleanUp(); 50 | #endif 51 | 52 | // Since the dialog has been closed, return FALSE so that we exit the 53 | // application, rather than start the application's message pump. 54 | return FALSE; 55 | } -------------------------------------------------------------------------------- /SampleProject/ListExSample.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef __AFXWIN_H__ 4 | #error "include 'pch.h' before including this file for PCH" 5 | #endif 6 | 7 | #include "Resource.h" 8 | 9 | class CListExSampleApp : public CWinApp 10 | { 11 | public: 12 | CListExSampleApp() = default; 13 | 14 | public: 15 | BOOL InitInstance()override; 16 | DECLARE_MESSAGE_MAP() 17 | }; -------------------------------------------------------------------------------- /SampleProject/ListExSample.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jovibor/ListEx/0f94e5f383359fb563d866375047c26320769961/SampleProject/ListExSample.rc -------------------------------------------------------------------------------- /SampleProject/ListExSample.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | {9D1474B6-FF8F-4E42-80FA-E4D3580726B9} 24 | MFCProj 25 | ListExSample 26 | 10.0 27 | SampleProject 28 | 29 | 30 | 31 | Application 32 | true 33 | v143 34 | Unicode 35 | Dynamic 36 | 37 | 38 | Application 39 | false 40 | v143 41 | true 42 | Unicode 43 | Dynamic 44 | 45 | 46 | Application 47 | true 48 | v143 49 | Unicode 50 | Dynamic 51 | 52 | 53 | Application 54 | false 55 | v143 56 | true 57 | Unicode 58 | Dynamic 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | true 80 | $(SolutionDir)bin\obj\$(Platform)\$(Configuration)\$(ProjectName)\ 81 | CppCoreCheckRules.ruleset 82 | $(SolutionDir)bin\ 83 | ListExSampled 84 | 85 | 86 | true 87 | $(SolutionDir)bin\obj\$(Platform)\$(Configuration)\$(ProjectName)\ 88 | CppCoreCheckRules.ruleset 89 | $(SolutionDir)bin\ 90 | ListExSample64d 91 | 92 | 93 | false 94 | $(SolutionDir)bin\obj\$(Platform)\$(Configuration)\$(ProjectName)\ 95 | CppCoreCheckRules.ruleset 96 | $(SolutionDir)bin\ 97 | ListExSample 98 | 99 | 100 | false 101 | $(SolutionDir)bin\obj\$(Platform)\$(Configuration)\$(ProjectName)\ 102 | CppCoreCheckRules.ruleset 103 | $(SolutionDir)bin\ 104 | ListExSample64 105 | 106 | 107 | 108 | Use 109 | Level4 110 | Disabled 111 | true 112 | WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) 113 | true 114 | stdcpp20 115 | 116 | 117 | Windows 118 | true 119 | 120 | 121 | false 122 | true 123 | _DEBUG;%(PreprocessorDefinitions) 124 | 125 | 126 | 0x0409 127 | _DEBUG;%(PreprocessorDefinitions) 128 | $(IntDir);%(AdditionalIncludeDirectories) 129 | 130 | 131 | 132 | 133 | Use 134 | Level4 135 | Disabled 136 | true 137 | _WINDOWS;_DEBUG;%(PreprocessorDefinitions) 138 | true 139 | stdcpp20 140 | 141 | 142 | Windows 143 | true 144 | 145 | 146 | false 147 | true 148 | _DEBUG;%(PreprocessorDefinitions) 149 | 150 | 151 | 0x0409 152 | _DEBUG;%(PreprocessorDefinitions) 153 | $(IntDir);%(AdditionalIncludeDirectories) 154 | 155 | 156 | 157 | 158 | Use 159 | Level4 160 | MaxSpeed 161 | true 162 | true 163 | true 164 | WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) 165 | None 166 | true 167 | stdcpp20 168 | 169 | 170 | Windows 171 | true 172 | true 173 | 174 | 175 | false 176 | true 177 | NDEBUG;%(PreprocessorDefinitions) 178 | 179 | 180 | 0x0409 181 | NDEBUG;%(PreprocessorDefinitions) 182 | $(IntDir);%(AdditionalIncludeDirectories) 183 | 184 | 185 | 186 | 187 | Use 188 | Level4 189 | MaxSpeed 190 | true 191 | true 192 | true 193 | _WINDOWS;NDEBUG;%(PreprocessorDefinitions) 194 | true 195 | stdcpp20 196 | 197 | 198 | Windows 199 | true 200 | true 201 | 202 | 203 | false 204 | true 205 | NDEBUG;%(PreprocessorDefinitions) 206 | 207 | 208 | 0x0409 209 | NDEBUG;%(PreprocessorDefinitions) 210 | $(IntDir);%(AdditionalIncludeDirectories) 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | NotUsing 224 | NotUsing 225 | NotUsing 226 | NotUsing 227 | 228 | 229 | 230 | 231 | Create 232 | Create 233 | Create 234 | Create 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /SampleProject/ListExSample.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {e5003000-58d7-4cee-9410-297ca67e9c80} 18 | 19 | 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | Source Files 49 | 50 | 51 | ListEx 52 | 53 | 54 | 55 | 56 | Resource Files 57 | 58 | 59 | 60 | 61 | Resource Files 62 | 63 | 64 | 65 | 66 | Resource Files 67 | 68 | 69 | Resource Files 70 | 71 | 72 | -------------------------------------------------------------------------------- /SampleProject/ListExSampleDlg.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "ListExSample.h" 3 | #include "ListExSampleDlg.h" 4 | #include "afxdialogex.h" 5 | #include "framework.h" 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef _DEBUG 11 | #define new DEBUG_NEW 12 | #endif 13 | 14 | constexpr auto g_iColumns { 3 }; 15 | constexpr auto g_iRows { 30 }; 16 | constexpr auto g_iDataSize { 30 }; 17 | constexpr auto IDC_LIST_MENU_HDR_BEGIN = 0x5; 18 | 19 | BEGIN_MESSAGE_MAP(CListExSampleDlg, CDialogEx) 20 | ON_WM_PAINT() 21 | ON_WM_QUERYDRAGICON() 22 | ON_NOTIFY(LVN_GETDISPINFOW, IDC_LISTEX, &CListExSampleDlg::OnListGetDispInfo) 23 | ON_NOTIFY(LISTEX_MSG_EDITBEGIN, IDC_LISTEX, &CListExSampleDlg::OnListEditBegin) 24 | ON_NOTIFY(LISTEX_MSG_GETCOLOR, IDC_LISTEX, &CListExSampleDlg::OnListGetColor) 25 | ON_NOTIFY(LISTEX_MSG_GETICON, IDC_LISTEX, &CListExSampleDlg::OnListGetIcon) 26 | ON_NOTIFY(LISTEX_MSG_GETTOOLTIP, IDC_LISTEX, &CListExSampleDlg::OnListGetToolTip) 27 | ON_NOTIFY(LISTEX_MSG_HDRICONCLICK, IDC_LISTEX, &CListExSampleDlg::OnListHdrIconClick) 28 | ON_NOTIFY(LISTEX_MSG_HDRRBTNUP, IDC_LISTEX, &CListExSampleDlg::OnListHdrRClick) 29 | ON_NOTIFY(LISTEX_MSG_LINKCLICK, IDC_LISTEX, &CListExSampleDlg::OnListLinkClick) 30 | ON_NOTIFY(LISTEX_MSG_SETDATA, IDC_LISTEX, &CListExSampleDlg::OnListSetData) 31 | ON_WM_DRAWITEM() 32 | ON_WM_MEASUREITEM() 33 | END_MESSAGE_MAP() 34 | 35 | CListExSampleDlg::CListExSampleDlg(CWnd* pParent /*=nullptr*/) 36 | : CDialogEx(IDD_LISTEXSAMPLE_DIALOG, pParent) 37 | { 38 | m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); 39 | } 40 | 41 | void CListExSampleDlg::DoDataExchange(CDataExchange* pDX) 42 | { 43 | CDialogEx::DoDataExchange(pDX); 44 | } 45 | 46 | BOOL CListExSampleDlg::OnInitDialog() 47 | { 48 | CDialogEx::OnInitDialog(); 49 | 50 | SetIcon(m_hIcon, TRUE); 51 | SetIcon(m_hIcon, FALSE); 52 | 53 | LISTEXCREATE lcs; 54 | lcs.uID = IDC_LISTEX; 55 | lcs.hWndParent = m_hWnd; 56 | lcs.fDialogCtrl = true; 57 | lcs.dwHdrHeight = 30; 58 | lcs.fSortable = true; 59 | lcs.dwTTStyleCell = TTS_BALLOON; 60 | lcs.dwTTStyleLink = TTS_NOANIMATE; 61 | lcs.dwTTShowTime = 2000; //Tooltip show up time. 62 | // lcs.dwTTDelayTime = 1000; 63 | lcs.dwSizeFontList = 10; 64 | lcs.dwSizeFontHdr = 10; 65 | lcs.fLinks = true; 66 | //lcs.fEditSingleClick = true; 67 | //lcs.fHighLatency = true; 68 | //lcs.dwTTDelayTime = 500; 69 | 70 | LISTEXCOLORS stColor { .clrHdrText { RGB(250, 250, 250) } }; 71 | lcs.pColors = &stColor; 72 | m_MyList.Create(lcs); 73 | 74 | m_MyList.SetExtendedStyle(LVS_EX_HEADERDRAGDROP); 75 | // m_MyList.SetExtendedStyle(LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES); 76 | m_MyList.InsertColumn(0, L"Test column 0\n Multiline", LVCFMT_LEFT, 170, -1, LVCFMT_LEFT, true); 77 | 78 | //Header menu. 79 | m_menuHdr.CreatePopupMenu(); 80 | m_menuHdr.AppendMenuW(MF_STRING, IDC_LIST_MENU_HDR_BEGIN, L"Test column 0"); 81 | m_menuHdr.CheckMenuItem(IDC_LIST_MENU_HDR_BEGIN, MF_CHECKED | MF_BYCOMMAND); 82 | 83 | for (int i = 1; i < g_iColumns; ++i) { 84 | const auto wstrName = std::wstring(L"Test column ") + std::to_wstring(i); 85 | m_MyList.InsertColumn(i, wstrName.data(), LVCFMT_CENTER, 170, -1, LVCFMT_CENTER, true); 86 | m_MyList.SetHdrColumnColor(i, RGB(200, 200, 200)); 87 | m_menuHdr.AppendMenuW(MF_STRING, IDC_LIST_MENU_HDR_BEGIN + i, wstrName.data()); 88 | m_menuHdr.CheckMenuItem(IDC_LIST_MENU_HDR_BEGIN + i, MF_CHECKED | MF_BYCOMMAND); 89 | } 90 | 91 | m_MyList.SetHdrColumnColor(0, RGB(70, 70, 70)); 92 | // m_MyList.SetHdrColumnColor(1, RGB(125, 125, 125)); 93 | // m_MyList.SetHdrColumnColor(2, RGB(200, 200, 200)); 94 | // m_myList->SetColumnSortMode(0, false); 95 | 96 | //For Virtual list. 97 | //Sample data for Virtual mode (LVS_OWNERDATA). 98 | std::srand(static_cast(std::time(0))); 99 | for (unsigned iItem = 0; iItem < g_iDataSize; ++iItem) { 100 | m_vecData.emplace_back(VIRTLISTDATA { 101 | L"Column:0/" 102 | L"row:" + std::to_wstring(iItem) + L"", 103 | //Some random numbers at the beginning, for checking the sorting. 104 | L"[" + std::to_wstring(std::rand()) + L"] " + L"Column:1/row:" + std::to_wstring(iItem), 105 | L"Column:2/row:" + std::to_wstring(iItem), 106 | iItem == 2, //Icon. 107 | iItem == 7, //Color. 108 | iItem == 1, //Tooltip. 109 | iItem == 7 ? LISTEXCOLOR { RGB(0, 220, 0) } : LISTEXCOLOR { } //Row number 7 (for all columns) colored to RGB(0, 220, 0). 110 | }); 111 | } 112 | m_MyList.SetItemCountEx(g_iRows, LVSICF_NOSCROLL); //Amount of Virtual items. 113 | 114 | //For classical list. 115 | /* m_MyList.InsertItem(0, L"Test item - row:0/column:0"); 116 | m_MyList.InsertItem(1, L"Test item - row:1/column:0."); 117 | m_MyList.InsertItem(2, L"Test item - row:2/column:0.."); 118 | m_MyList.InsertItem(3, L"Test item - row:3/column:0..."); 119 | m_MyList.InsertItem(4, L"Test item - row:4/column:0...."); 120 | m_MyList.SetItemText(0, 1, L"Test item - row:0/column:1...."); 121 | m_MyList.SetItemText(1, 1, L"Test item - row:1/column:1..."); 122 | m_MyList.SetItemText(2, 1, L"Test item - row:2/column:1.."); 123 | m_MyList.SetItemText(3, 1, L"Test item - row:3/column:1."); 124 | m_MyList.SetItemText(4, 1, L"Test item - row:4/column:1"); 125 | m_MyList.SetItemText(0, 2, L"Test item - row:0/column:2..."); 126 | m_MyList.SetItemText(1, 2, L"Test item - row:1/column:2."); 127 | m_MyList.SetItemText(2, 2, L"Test item - row:2/column:2...."); 128 | m_MyList.SetItemText(3, 2, L"Test item - row:3/column:2.."); 129 | m_MyList.SetItemText(4, 2, L"Test item - row:4/column:2....."); 130 | */ 131 | 132 | m_stImgList.Create(16, 16, ILC_COLOR | ILC_MASK, 0, 1); 133 | m_stImgList.Add(static_cast(LoadImageW(AfxGetInstanceHandle(), MAKEINTRESOURCEW(IDI_TEST), 134 | IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR))); 135 | m_MyList.SetImageList(m_stImgList.m_hImageList, LVSIL_NORMAL); 136 | m_MyList.SetHdrImageList(m_stImgList); 137 | 138 | LISTEXHDRICON stHdrIcon { .pt { .x = 4, .y = 4 }, .iIndex = 0 }; 139 | m_MyList.SetHdrColumnIcon(0, stHdrIcon); 140 | 141 | return TRUE; 142 | } 143 | 144 | void CListExSampleDlg::OnPaint() 145 | { 146 | if (IsIconic()) { 147 | CPaintDC dc(this); // device context for painting 148 | 149 | SendMessageW(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0); 150 | 151 | // Center icon in client rectangle 152 | const auto cxIcon = GetSystemMetrics(SM_CXICON); 153 | const auto cyIcon = GetSystemMetrics(SM_CYICON); 154 | CRect rect; 155 | GetClientRect(&rect); 156 | const auto x = (rect.Width() - cxIcon + 1) / 2; 157 | const auto y = (rect.Height() - cyIcon + 1) / 2; 158 | 159 | // Draw the icon 160 | dc.DrawIcon(x, y, m_hIcon); 161 | } 162 | else { 163 | CDialogEx::OnPaint(); 164 | } 165 | } 166 | 167 | HCURSOR CListExSampleDlg::OnQueryDragIcon() 168 | { 169 | return static_cast(m_hIcon); 170 | } 171 | 172 | BOOL CListExSampleDlg::OnCommand(WPARAM wParam, LPARAM lParam) 173 | { 174 | const auto wMenuID = LOWORD(wParam); 175 | if (wMenuID < IDC_LIST_MENU_HDR_BEGIN || wMenuID > IDC_LIST_MENU_HDR_BEGIN + g_iColumns) 176 | return CDialogEx::OnCommand(wParam, lParam); 177 | 178 | const auto uState = m_menuHdr.GetMenuState(wMenuID, MF_BYCOMMAND); 179 | 180 | if (uState & MF_CHECKED) { 181 | m_menuHdr.CheckMenuItem(wMenuID, MF_UNCHECKED | MF_BYCOMMAND); 182 | m_MyList.HideColumn(wMenuID - IDC_LIST_MENU_HDR_BEGIN, true); 183 | } 184 | else { 185 | m_menuHdr.CheckMenuItem(wMenuID, MF_CHECKED | MF_BYCOMMAND); 186 | m_MyList.HideColumn(wMenuID - IDC_LIST_MENU_HDR_BEGIN, false); 187 | } 188 | 189 | return CDialogEx::OnCommand(wParam, lParam); 190 | } 191 | 192 | void CListExSampleDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 193 | { 194 | if (nIDCtl == IDC_LISTEX) { 195 | m_MyList.DrawItem(lpDrawItemStruct); 196 | return; 197 | } 198 | 199 | CDialogEx::OnDrawItem(nIDCtl, lpDrawItemStruct); 200 | } 201 | 202 | BOOL CListExSampleDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 203 | { 204 | const auto pNMI = reinterpret_cast(lParam); 205 | 206 | if (pNMI->hdr.idFrom == IDC_LISTEX) { 207 | switch (pNMI->hdr.code) { 208 | case LVN_COLUMNCLICK: 209 | SortVecData(); 210 | return TRUE; //Disable further message processing. 211 | } 212 | } 213 | 214 | return CDialogEx::OnNotify(wParam, lParam, pResult); 215 | } 216 | 217 | void CListExSampleDlg::OnOK() 218 | { 219 | } 220 | 221 | void CListExSampleDlg::OnListEditBegin(NMHDR* pNMHDR, LRESULT* /*pResult*/) 222 | { 223 | const auto pLDI = reinterpret_cast(pNMHDR); 224 | pLDI->fAllowEdit = true; 225 | //pLDI->fAllowEdit = false; //Edit-box won't show up. 226 | } 227 | 228 | void CListExSampleDlg::OnListGetDispInfo(NMHDR* pNMHDR, LRESULT* /*pResult*/) 229 | { 230 | const auto pDispInfo = reinterpret_cast(pNMHDR); 231 | const auto pItem = &pDispInfo->item; 232 | const auto iItem = pItem->iItem; 233 | if (iItem >= g_iDataSize || (pItem->mask & LVIF_TEXT) == 0) 234 | return; 235 | 236 | switch (pItem->iSubItem) { 237 | case 0: 238 | pItem->pszText = m_vecData[static_cast(iItem)].wstr0.data(); 239 | break; 240 | case 1: 241 | pItem->pszText = m_vecData[static_cast(iItem)].wstr1.data(); 242 | break; 243 | case 2: 244 | pItem->pszText = m_vecData[static_cast(iItem)].wstr2.data(); 245 | break; 246 | } 247 | } 248 | 249 | void CListExSampleDlg::OnListGetColor(NMHDR* pNMHDR, LRESULT* /*pResult*/) 250 | { 251 | //Virtual data colors. 252 | const auto pLCI = reinterpret_cast(pNMHDR); 253 | if (pLCI->iItem < 0 || pLCI->iSubItem < 0) 254 | return; 255 | 256 | if (pLCI->iItem >= g_iDataSize) 257 | return; 258 | 259 | const auto sItem = static_cast(pLCI->iItem); 260 | const auto sSubItem = pLCI->iSubItem; 261 | 262 | if (m_vecData.at(sItem).fToolTip && sSubItem == 0) { 263 | pLCI->stClr = { RGB(240, 240, 0) }; //Yellow Bk for cells with tooltip for 0 column only. 264 | return; 265 | } 266 | 267 | if (sSubItem == 1) { //Column number 1 (for all rows) colored to RGB(0, 220, 220). 268 | pLCI->stClr = { RGB(0, 220, 220), RGB(0, 0, 0) }; 269 | return; 270 | } 271 | 272 | if (m_vecData.at(sItem).fColor) { 273 | pLCI->stClr = m_vecData[sItem].clr; 274 | return; 275 | } 276 | } 277 | 278 | void CListExSampleDlg::OnListGetIcon(NMHDR* pNMHDR, LRESULT* /*pResult*/) 279 | { 280 | //Virtual data icons. 281 | const auto pLII = reinterpret_cast(pNMHDR); 282 | if (pLII->iItem < 0 || pLII->iSubItem < 0) 283 | return; 284 | 285 | const auto index = pLII->iItem < g_iDataSize ? pLII->iItem : 1; 286 | if (m_vecData.at(static_cast(index)).fIcon && (pLII->iSubItem == 0 || pLII->iSubItem == 1)) { 287 | pLII->iIconIndex = 0; //Icon index in the list's image list. 288 | return; 289 | } 290 | } 291 | 292 | void CListExSampleDlg::OnListGetToolTip(NMHDR* pNMHDR, LRESULT* /*pResult*/) 293 | { 294 | //Virtual data tooltips. 295 | const auto pTTI = reinterpret_cast(pNMHDR); 296 | const auto iItem = pTTI->iItem; 297 | if (iItem < 0 || pTTI->iSubItem != 0 || iItem >= g_iDataSize) 298 | return; 299 | 300 | if (m_vecData[static_cast(iItem)].fToolTip) { 301 | static constexpr const wchar_t* ttData[2] { L"Cell tooltip text...", L"Caption of the cell tooltip:" }; 302 | pTTI->stData.pwszText = ttData[0]; 303 | pTTI->stData.pwszCaption = ttData[1]; 304 | return; 305 | } 306 | } 307 | 308 | void CListExSampleDlg::OnListHdrIconClick(NMHDR* pNMHDR, LRESULT* /*pResult*/) 309 | { 310 | const auto pNMI = reinterpret_cast(pNMHDR); 311 | const auto wstr = L"Header icon clicked at column: " + std::to_wstring(pNMI->iItem); 312 | MessageBoxW(wstr.data()); 313 | } 314 | 315 | void CListExSampleDlg::OnListHdrRClick(NMHDR* /*pNMHDR*/, LRESULT* /*pResult*/) 316 | { 317 | CPoint pt; 318 | GetCursorPos(&pt); 319 | m_menuHdr.TrackPopupMenu(TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, pt.x, pt.y, this); 320 | } 321 | 322 | void CListExSampleDlg::OnListLinkClick(NMHDR* pNMHDR, LRESULT* /*pResult*/) 323 | { 324 | const auto pLLI = reinterpret_cast(pNMHDR); 325 | MessageBoxW(pLLI->pwszText); 326 | } 327 | 328 | void CListExSampleDlg::OnListSetData(NMHDR* pNMHDR, LRESULT* /*pResult*/) 329 | { 330 | //Changing virtual data in internal m_vecData. 331 | const auto pLDI = reinterpret_cast(pNMHDR); 332 | auto& ref = m_vecData[pLDI->iItem]; 333 | 334 | switch (pLDI->iSubItem) { 335 | case 0: 336 | ref.wstr0 = pLDI->pwszData; 337 | break; 338 | case 1: 339 | ref.wstr1 = pLDI->pwszData; 340 | break; 341 | case 2: 342 | ref.wstr2 = pLDI->pwszData; 343 | break; 344 | default: 345 | break; 346 | } 347 | } 348 | 349 | void CListExSampleDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 350 | { 351 | if (nIDCtl == IDC_LISTEX) { 352 | m_MyList.MeasureItem(lpMeasureItemStruct); 353 | return; 354 | } 355 | 356 | CDialogEx::OnMeasureItem(nIDCtl, lpMeasureItemStruct); 357 | } 358 | 359 | void CListExSampleDlg::SortVecData() 360 | { 361 | const auto iColumnIndex = m_MyList.GetSortColumn(); 362 | if (iColumnIndex < 0) 363 | return; 364 | 365 | //Sorts the vector of data according to clicked column. 366 | std::sort(m_vecData.begin(), m_vecData.end(), [&](const VIRTLISTDATA& st1, const VIRTLISTDATA& st2) { 367 | int iCompare { }; 368 | switch (iColumnIndex) { 369 | case 0: 370 | iCompare = st1.wstr0.compare(st2.wstr0); 371 | break; 372 | case 1: 373 | iCompare = st1.wstr1.compare(st2.wstr1); 374 | break; 375 | case 2: 376 | iCompare = st1.wstr2.compare(st2.wstr2); 377 | break; 378 | } 379 | 380 | bool result { false }; 381 | if (m_MyList.GetSortAscending()) { 382 | if (iCompare < 0) 383 | result = true; 384 | } 385 | else { 386 | if (iCompare > 0) 387 | result = true; 388 | } 389 | 390 | return result; 391 | }); 392 | 393 | m_MyList.RedrawWindow(); 394 | } -------------------------------------------------------------------------------- /SampleProject/ListExSampleDlg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | import ListEx; 6 | using namespace LISTEX; 7 | 8 | struct VIRTLISTDATA { 9 | std::wstring wstr0; //Arbitrary data... 10 | std::wstring wstr1; 11 | std::wstring wstr2; 12 | bool fIcon { false }; //Does this row have an icon. 13 | bool fColor { false }; //Does this row have color. 14 | bool fToolTip { false }; //Tooltip row. 15 | LISTEXCOLOR clr { }; //Row color. 16 | }; 17 | 18 | class CListExSampleDlg : public CDialogEx { 19 | public: 20 | CListExSampleDlg(CWnd* pParent = nullptr); 21 | protected: 22 | void DoDataExchange(CDataExchange* pDX)override; 23 | BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)override; 24 | BOOL OnInitDialog()override; 25 | afx_msg void OnPaint(); 26 | virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); 27 | afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct); 28 | virtual void OnOK(); 29 | afx_msg HCURSOR OnQueryDragIcon(); 30 | afx_msg void OnListEditBegin(NMHDR* pNMHDR, LRESULT* pResult); 31 | afx_msg void OnListGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult); 32 | afx_msg void OnListGetColor(NMHDR* pNMHDR, LRESULT* pResult); 33 | afx_msg void OnListGetIcon(NMHDR* pNMHDR, LRESULT* pResult); 34 | afx_msg void OnListGetToolTip(NMHDR* pNMHDR, LRESULT* pResult); 35 | afx_msg void OnListHdrIconClick(NMHDR* pNMHDR, LRESULT* pResult); 36 | afx_msg void OnListHdrRClick(NMHDR* pNMHDR, LRESULT* pResult); 37 | afx_msg void OnListLinkClick(NMHDR* pNMHDR, LRESULT* pResult); 38 | afx_msg void OnListSetData(NMHDR* pNMHDR, LRESULT* pResult); 39 | afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct); 40 | void SortVecData(); 41 | DECLARE_MESSAGE_MAP(); 42 | protected: 43 | HICON m_hIcon; 44 | CListEx m_MyList; 45 | CMenu m_menuHdr; 46 | std::vector m_vecData { }; 47 | CImageList m_stImgList; 48 | }; -------------------------------------------------------------------------------- /SampleProject/Resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by ListExSample.rc 4 | // 5 | #define IDD_LISTEXSAMPLE_DIALOG 102 6 | #define IDR_MAINFRAME 128 7 | #define IDI_TEST 131 8 | #define IDC_LISTEX 1000 9 | 10 | // Next default values for new objects 11 | // 12 | #ifdef APSTUDIO_INVOKED 13 | #ifndef APSTUDIO_READONLY_SYMBOLS 14 | #define _APS_NEXT_RESOURCE_VALUE 132 15 | #define _APS_NEXT_COMMAND_VALUE 32771 16 | #define _APS_NEXT_CONTROL_VALUE 1001 17 | #define _APS_NEXT_SYMED_VALUE 101 18 | #endif 19 | #endif 20 | -------------------------------------------------------------------------------- /SampleProject/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef VC_EXTRALEAN 4 | #define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers 5 | #endif 6 | 7 | #include "targetver.h" 8 | 9 | #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit 10 | 11 | // turns off MFC's hiding of some common and often safely ignored warning messages 12 | #define _AFX_ALL_WARNINGS 13 | 14 | #include // MFC extensions 15 | #include // MFC core and standard components 16 | 17 | #ifndef _AFX_NO_OLE_SUPPORT 18 | #include // MFC support for Internet Explorer 4 Common Controls 19 | #endif 20 | #ifndef _AFX_NO_AFXCMN_SUPPORT 21 | #include // MFC support for Windows Common Controls 22 | #endif // _AFX_NO_AFXCMN_SUPPORT 23 | 24 | #include // MFC support for ribbons and control bars -------------------------------------------------------------------------------- /SampleProject/res/ListExSample.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jovibor/ListEx/0f94e5f383359fb563d866375047c26320769961/SampleProject/res/ListExSample.ico -------------------------------------------------------------------------------- /SampleProject/res/ListExSample.rc2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jovibor/ListEx/0f94e5f383359fb563d866375047c26320769961/SampleProject/res/ListExSample.rc2 -------------------------------------------------------------------------------- /SampleProject/res/test.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jovibor/ListEx/0f94e5f383359fb563d866375047c26320769961/SampleProject/res/test.ico -------------------------------------------------------------------------------- /SampleProject/stdafx.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" -------------------------------------------------------------------------------- /SampleProject/stdafx.h: -------------------------------------------------------------------------------- 1 | // pch.h: This is a precompiled header file. 2 | // Files listed below are compiled only once, improving build performance for future builds. 3 | // This also affects IntelliSense performance, including code completion and many code browsing features. 4 | // However, files listed here are ALL re-compiled if any one of them is updated between builds. 5 | // Do not add files here that you will be updating frequently as this negates the performance advantage. 6 | 7 | #ifndef PCH_H 8 | #define PCH_H 9 | 10 | // add headers that you want to pre-compile here 11 | #include "framework.h" 12 | 13 | #endif //PCH_H 14 | -------------------------------------------------------------------------------- /SampleProject/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /docs/img/listex_mainwnd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jovibor/ListEx/0f94e5f383359fb563d866375047c26320769961/docs/img/listex_mainwnd.jpg --------------------------------------------------------------------------------