Feature: Add CListCtrlEx with column show/hide support

- Create CListCtrlEx class derived from CListCtrl
- Support right-click header menu to toggle column visibility
- Save column visibility settings to registry (list\{ConfigKey})
- Settings persist by column name, not index (order-independent)
- Skip empty column titles in context menu
- Update CClientListDlg to use CListCtrlEx
- Update m_CList_Online in 2015RemoteDlg to use CListCtrlEx
This commit is contained in:
yuanyuanxiang
2026-01-26 17:57:03 +01:00
parent d7789e04ca
commit d49c541ea2
8 changed files with 367 additions and 154 deletions

View File

@@ -706,11 +706,12 @@ VOID CMy2015RemoteDlg::InitControl()
rect.bottom+=20; rect.bottom+=20;
MoveWindow(rect); MoveWindow(rect);
auto style = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_LABELTIP; auto style = LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER | LVS_EX_HEADERDRAGDROP | LVS_EX_LABELTIP;
m_CList_Online.SetConfigKey(_T("OnlineList"));
for (int i = 0; i<g_Column_Count_Online; ++i) { for (int i = 0; i<g_Column_Count_Online; ++i) {
m_CList_Online.InsertColumn(i, g_Column_Data_Online[i].szTitle,LVCFMT_CENTER,g_Column_Data_Online[i].nWidth); m_CList_Online.AddColumn(i, g_Column_Data_Online[i].szTitle, g_Column_Data_Online[i].nWidth, LVCFMT_CENTER);
g_Column_Online_Width+=g_Column_Data_Online[i].nWidth; g_Column_Online_Width+=g_Column_Data_Online[i].nWidth;
} }
m_CList_Online.InitColumns();
m_CList_Online.ModifyStyle(0, LVS_SHOWSELALWAYS); m_CList_Online.ModifyStyle(0, LVS_SHOWSELALWAYS);
m_CList_Online.SetExtendedStyle(style); m_CList_Online.SetExtendedStyle(style);
m_CList_Online.SetParent(&m_GroupTab); m_CList_Online.SetParent(&m_GroupTab);
@@ -1469,15 +1470,7 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
m_GroupTab.AdjustRect(FALSE, &rcInside); m_GroupTab.AdjustRect(FALSE, &rcInside);
rcInside.bottom -= 1; rcInside.bottom -= 1;
m_CList_Online.MoveWindow(&rcInside); m_CList_Online.MoveWindow(&rcInside);
m_CList_Online.AdjustColumnWidths();
auto total = rcInside.Width() - 24;
for(int i=0; i<g_Column_Count_Online; ++i) { //遍历每一个列
double Temp=g_Column_Data_Online[i].nWidth; //得到当前列的宽度 138
Temp/=g_Column_Online_Width; //看一看当前宽度占总长度的几分之几
Temp*=total; //用原来的长度乘以所占的几分之几得到当前的宽度
int lenth = Temp; //转换为int 类型
m_CList_Online.SetColumnWidth(i,(lenth)); //设置当前的宽度
}
} }
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);

View File

@@ -7,7 +7,8 @@
#include "IOCPServer.h" #include "IOCPServer.h"
#include <common/location.h> #include <common/location.h>
#include <map> #include <map>
#include"file_server.h" #include "file_server.h"
#include "CListCtrlEx.h"
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// 以下为特殊需求使用 // 以下为特殊需求使用
@@ -151,7 +152,7 @@ public:
VOID SendAllCommand(PBYTE szBuffer, ULONG ulLength); VOID SendAllCommand(PBYTE szBuffer, ULONG ulLength);
// 显示用户上线信息 // 显示用户上线信息
CWnd* m_pFloatingTip = nullptr; CWnd* m_pFloatingTip = nullptr;
CListCtrl m_CList_Online; CListCtrlEx m_CList_Online;
CListCtrl m_CList_Message; CListCtrl m_CList_Message;
std::set<context*> m_HostList; std::set<context*> m_HostList;
std::set<std::string> m_GroupList; std::set<std::string> m_GroupList;

View File

@@ -287,6 +287,7 @@
<ClInclude Include="CDrawingBoard.h" /> <ClInclude Include="CDrawingBoard.h" />
<ClInclude Include="CGridDialog.h" /> <ClInclude Include="CGridDialog.h" />
<ClInclude Include="Chat.h" /> <ClInclude Include="Chat.h" />
<ClInclude Include="CListCtrlEx.h" />
<ClInclude Include="context.h" /> <ClInclude Include="context.h" />
<ClInclude Include="CPasswordDlg.h" /> <ClInclude Include="CPasswordDlg.h" />
<ClInclude Include="CRcEditDlg.h" /> <ClInclude Include="CRcEditDlg.h" />
@@ -382,6 +383,7 @@
<ClCompile Include="CDrawingBoard.cpp" /> <ClCompile Include="CDrawingBoard.cpp" />
<ClCompile Include="CGridDialog.cpp" /> <ClCompile Include="CGridDialog.cpp" />
<ClCompile Include="Chat.cpp" /> <ClCompile Include="Chat.cpp" />
<ClCompile Include="CListCtrlEx.cpp" />
<ClCompile Include="CPasswordDlg.cpp" /> <ClCompile Include="CPasswordDlg.cpp" />
<ClCompile Include="CRcEditDlg.cpp" /> <ClCompile Include="CRcEditDlg.cpp" />
<ClCompile Include="CTextDlg.cpp" /> <ClCompile Include="CTextDlg.cpp" />

View File

@@ -66,6 +66,7 @@
<ClCompile Include="..\..\client\reg_startup.c" /> <ClCompile Include="..\..\client\reg_startup.c" />
<ClCompile Include="CClientListDlg.cpp" /> <ClCompile Include="CClientListDlg.cpp" />
<ClCompile Include="CUpdateDlg.cpp" /> <ClCompile Include="CUpdateDlg.cpp" />
<ClCompile Include="CListCtrlEx.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\..\client\Audio.h" /> <ClInclude Include="..\..\client\Audio.h" />
@@ -146,6 +147,7 @@
<ClInclude Include="CClientListDlg.h" /> <ClInclude Include="CClientListDlg.h" />
<ClInclude Include="context.h" /> <ClInclude Include="context.h" />
<ClInclude Include="CUpdateDlg.h" /> <ClInclude Include="CUpdateDlg.h" />
<ClInclude Include="CListCtrlEx.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="2015Remote.rc" /> <ResourceCompile Include="2015Remote.rc" />

View File

@@ -144,7 +144,6 @@ void CClientListDlg::DoDataExchange(CDataExchange* pDX)
BEGIN_MESSAGE_MAP(CClientListDlg, CDialogEx) BEGIN_MESSAGE_MAP(CClientListDlg, CDialogEx)
ON_WM_SIZE() ON_WM_SIZE()
ON_WM_CONTEXTMENU()
ON_NOTIFY(LVN_COLUMNCLICK, IDC_CLIENT_LIST, &CClientListDlg::OnColumnClick) ON_NOTIFY(LVN_COLUMNCLICK, IDC_CLIENT_LIST, &CClientListDlg::OnColumnClick)
ON_NOTIFY(NM_CLICK, IDC_CLIENT_LIST, &CClientListDlg::OnListClick) ON_NOTIFY(NM_CLICK, IDC_CLIENT_LIST, &CClientListDlg::OnListClick)
END_MESSAGE_MAP() END_MESSAGE_MAP()
@@ -172,22 +171,19 @@ BOOL CClientListDlg::OnInitDialog()
m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 10000); m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 10000);
m_ToolTip.Activate(TRUE); m_ToolTip.Activate(TRUE);
// 初始化列可见性(从配置加载) // 设置配置键名
m_ColumnVisible.resize(g_nColumnCount, TRUE); m_ClientList.SetConfigKey(_T("ClientList"));
LoadColumnVisibility();
// 添加列 // 添加列(第一列序号不允许隐藏)
int totalWidth = 0;
for (int i = 0; i < g_nColumnCount; i++) { for (int i = 0; i < g_nColumnCount; i++) {
totalWidth += g_ColumnInfos[i].Width; BOOL bCanHide = (i != COL_NO); // 序号列不允许隐藏
} m_ClientList.AddColumn(i, g_ColumnInfos[i].Name, g_ColumnInfos[i].Width, LVCFMT_LEFT, bCanHide);
for (int i = 0; i < g_nColumnCount; i++) {
g_ColumnInfos[i].Percent = (float)g_ColumnInfos[i].Width / totalWidth;
m_ClientList.InsertColumn(i, g_ColumnInfos[i].Name, LVCFMT_LEFT, g_ColumnInfos[i].Width);
} }
// 初始化列(计算百分比、加载配置、应用列宽)
m_ClientList.InitColumns();
// 首次加载数据 // 首次加载数据
AdjustColumnWidths();
RefreshClientList(); RefreshClientList();
return TRUE; return TRUE;
@@ -394,27 +390,7 @@ void CClientListDlg::SortByColumn(int /*nColumn*/, BOOL /*bAscending*/)
void CClientListDlg::AdjustColumnWidths() void CClientListDlg::AdjustColumnWidths()
{ {
CRect rect; m_ClientList.AdjustColumnWidths();
m_ClientList.GetClientRect(&rect);
int totalWidth = rect.Width() - 20;
// 计算可见列的总百分比
float visiblePercent = 0.0f;
for (int i = 0; i < g_nColumnCount; i++) {
if (m_ColumnVisible[i]) {
visiblePercent += g_ColumnInfos[i].Percent;
}
}
// 按比例分配宽度给可见列
for (int i = 0; i < g_nColumnCount; i++) {
if (m_ColumnVisible[i]) {
int width = (visiblePercent > 0) ? (int)(totalWidth * g_ColumnInfos[i].Percent / visiblePercent) : 0;
m_ClientList.SetColumnWidth(i, width);
} else {
m_ClientList.SetColumnWidth(i, 0); // 隐藏列
}
}
} }
void CClientListDlg::OnSize(UINT nType, int cx, int cy) void CClientListDlg::OnSize(UINT nType, int cx, int cy)
@@ -540,103 +516,3 @@ void CClientListDlg::PostNcDestroy()
void CClientListDlg::OnOK() void CClientListDlg::OnOK()
{ {
} }
void CClientListDlg::OnContextMenu(CWnd* pWnd, CPoint pt)
{
// 检查是否点击在表头区域
CHeaderCtrl* pHeader = m_ClientList.GetHeaderCtrl();
if (pHeader) {
CRect headerRect;
pHeader->GetWindowRect(&headerRect);
if (headerRect.PtInRect(pt)) {
ShowColumnContextMenu(pt);
return;
}
}
// 非表头区域,调用默认处理
CDialogEx::OnContextMenu(pWnd, pt);
}
void CClientListDlg::ShowColumnContextMenu(CPoint pt)
{
CMenu menu;
menu.CreatePopupMenu();
// 添加所有列到菜单
for (int i = 0; i < g_nColumnCount; i++) {
UINT flags = MF_STRING;
if (m_ColumnVisible[i]) {
flags |= MF_CHECKED;
}
// 序号列始终显示,不允许隐藏
if (i == COL_NO) {
flags |= MF_GRAYED;
}
menu.AppendMenu(flags, 1000 + i, g_ColumnInfos[i].Name);
}
// 显示菜单并获取选择
int nCmd = menu.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
if (nCmd >= 1000 && nCmd < 1000 + g_nColumnCount) {
ToggleColumnVisibility(nCmd - 1000);
}
}
void CClientListDlg::ToggleColumnVisibility(int nColumn)
{
// 序号列不允许隐藏
if (nColumn == COL_NO) {
return;
}
// 切换可见性
m_ColumnVisible[nColumn] = !m_ColumnVisible[nColumn];
// 保存到配置
SaveColumnVisibility();
// 重新调整列宽
AdjustColumnWidths();
}
void CClientListDlg::LoadColumnVisibility()
{
// 格式:逗号分隔的隐藏列名,如 "备注,程序路径"
std::string strHidden = THIS_CFG.GetStr("ClientList", "HiddenColumns", "");
if (strHidden.empty()) {
return; // 使用默认值(全部显示)
}
// 解析隐藏的列名
std::vector<std::string> hiddenNames = StringToVector(strHidden, ',');
for (const auto& name : hiddenNames) {
// 查找列名对应的索引
for (int i = 0; i < g_nColumnCount; i++) {
CString colName = g_ColumnInfos[i].Name;
CT2A colNameA(colName);
if (name == std::string(colNameA)) {
m_ColumnVisible[i] = FALSE;
break;
}
}
}
// 序号列始终显示
m_ColumnVisible[COL_NO] = TRUE;
}
void CClientListDlg::SaveColumnVisibility()
{
// 只保存隐藏的列名
std::string strHidden;
for (int i = 0; i < g_nColumnCount; i++) {
if (!m_ColumnVisible[i]) {
if (!strHidden.empty()) strHidden += ",";
CString colName = g_ColumnInfos[i].Name;
CT2A colNameA(colName);
strHidden += std::string(colNameA);
}
}
THIS_CFG.SetStr("ClientList", "HiddenColumns", strHidden);
}

View File

@@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include <map> #include <map>
#include "2015RemoteDlg.h" #include "2015RemoteDlg.h"
#include "CListCtrlEx.h"
// 分组键:支持任意字段组合 // 分组键:支持任意字段组合
struct GroupKey { struct GroupKey {
@@ -53,19 +54,14 @@ protected:
CToolTipCtrl m_ToolTip; CToolTipCtrl m_ToolTip;
int m_nTipItem; // 当前提示的行 int m_nTipItem; // 当前提示的行
int m_nTipSubItem; // 当前提示的列 int m_nTipSubItem; // 当前提示的列
std::vector<BOOL> m_ColumnVisible; // 各列的显示状态
void BuildGroups(); // 构建分组数据 void BuildGroups(); // 构建分组数据
void ShowColumnContextMenu(CPoint pt); // 显示列选择菜单
void ToggleColumnVisibility(int nColumn); // 切换列的显示/隐藏
void LoadColumnVisibility(); // 从配置加载列可见性
void SaveColumnVisibility(); // 保存列可见性到配置
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
DECLARE_MESSAGE_MAP() DECLARE_MESSAGE_MAP()
public: public:
CListCtrl m_ClientList; CListCtrlEx m_ClientList;
virtual BOOL OnInitDialog(); virtual BOOL OnInitDialog();
void RefreshClientList(); void RefreshClientList();
void DisplayClients(); void DisplayClients();
@@ -74,7 +70,6 @@ public:
afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnListClick(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnListClick(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnContextMenu(CWnd* pWnd, CPoint pt);
virtual BOOL PreTranslateMessage(MSG* pMsg); virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual void OnCancel(); virtual void OnCancel();
virtual void PostNcDestroy(); virtual void PostNcDestroy();

View File

@@ -0,0 +1,270 @@
#include "stdafx.h"
#include "CListCtrlEx.h"
#include "2015Remote.h"
// CHeaderCtrlEx 实现
BEGIN_MESSAGE_MAP(CHeaderCtrlEx, CHeaderCtrl)
ON_NOTIFY_REFLECT(HDN_BEGINTRACKA, &CHeaderCtrlEx::OnBeginTrack)
ON_NOTIFY_REFLECT(HDN_BEGINTRACKW, &CHeaderCtrlEx::OnBeginTrack)
END_MESSAGE_MAP()
void CHeaderCtrlEx::OnBeginTrack(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMHEADER pNMHeader = reinterpret_cast<LPNMHEADER>(pNMHDR);
int nCol = pNMHeader->iItem;
if (m_pListCtrl && nCol >= 0 && nCol < (int)m_pListCtrl->m_Columns.size()) {
if (!m_pListCtrl->m_Columns[nCol].Visible) {
*pResult = TRUE; // 阻止拖拽
return;
}
}
*pResult = FALSE;
}
// CListCtrlEx 实现
IMPLEMENT_DYNAMIC(CListCtrlEx, CListCtrl)
CListCtrlEx::CListCtrlEx()
{
}
CListCtrlEx::~CListCtrlEx()
{
}
BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
ON_WM_CONTEXTMENU()
END_MESSAGE_MAP()
void CListCtrlEx::SetConfigKey(const CString& strKey)
{
m_strConfigKey = strKey;
}
int CListCtrlEx::AddColumn(int nCol, LPCTSTR lpszColumnHeading, int nWidth, int nFormat, BOOL bCanHide)
{
// 添加到列表控件
int nResult = InsertColumn(nCol, lpszColumnHeading, nFormat, nWidth);
if (nResult != -1) {
// 保存列信息
ColumnInfoEx info;
info.Name = lpszColumnHeading;
info.Width = nWidth;
info.Percent = 0.0f; // 稍后在 InitColumns 中计算
info.Visible = TRUE;
info.CanHide = bCanHide;
// 确保 vector 大小足够
if (nCol >= (int)m_Columns.size()) {
m_Columns.resize(nCol + 1);
}
m_Columns[nCol] = info;
}
return nResult;
}
void CListCtrlEx::InitColumns()
{
if (m_Columns.empty()) {
return;
}
// 子类化 Header 控件
SubclassHeader();
// 计算总宽度和百分比
int totalWidth = 0;
for (const auto& col : m_Columns) {
totalWidth += col.Width;
}
if (totalWidth > 0) {
for (auto& col : m_Columns) {
col.Percent = (float)col.Width / totalWidth;
}
}
// 从配置加载列可见性
LoadColumnVisibility();
// 应用列宽
AdjustColumnWidths();
}
void CListCtrlEx::SubclassHeader()
{
CHeaderCtrl* pHeader = GetHeaderCtrl();
if (pHeader && !m_HeaderCtrl.GetSafeHwnd()) {
m_HeaderCtrl.SubclassWindow(pHeader->GetSafeHwnd());
m_HeaderCtrl.m_pListCtrl = this;
}
}
void CListCtrlEx::AdjustColumnWidths()
{
if (m_Columns.empty() || GetSafeHwnd() == NULL) {
return;
}
CRect rect;
GetClientRect(&rect);
int totalWidth = rect.Width() - 20; // 减去滚动条宽度
// 计算可见列的总百分比
float visiblePercent = 0.0f;
for (const auto& col : m_Columns) {
if (col.Visible) {
visiblePercent += col.Percent;
}
}
// 按比例分配宽度给可见列
for (int i = 0; i < (int)m_Columns.size(); i++) {
if (m_Columns[i].Visible) {
int width = (visiblePercent > 0) ? (int)(totalWidth * m_Columns[i].Percent / visiblePercent) : 0;
SetColumnWidth(i, width);
} else {
SetColumnWidth(i, 0); // 隐藏列
}
}
}
BOOL CListCtrlEx::IsColumnVisible(int nCol) const
{
if (nCol >= 0 && nCol < (int)m_Columns.size()) {
return m_Columns[nCol].Visible;
}
return TRUE;
}
void CListCtrlEx::SetColumnVisible(int nCol, BOOL bVisible)
{
if (nCol >= 0 && nCol < (int)m_Columns.size()) {
if (m_Columns[nCol].CanHide || bVisible) { // 不允许隐藏的列只能设为可见
m_Columns[nCol].Visible = bVisible;
AdjustColumnWidths();
}
}
}
void CListCtrlEx::OnContextMenu(CWnd* pWnd, CPoint pt)
{
// 检查是否点击在表头区域
CHeaderCtrl* pHeader = GetHeaderCtrl();
if (pHeader) {
CRect headerRect;
pHeader->GetWindowRect(&headerRect);
if (headerRect.PtInRect(pt)) {
ShowColumnContextMenu(pt);
return;
}
}
// 非表头区域,调用父类处理
CListCtrl::OnContextMenu(pWnd, pt);
}
void CListCtrlEx::ShowColumnContextMenu(CPoint pt)
{
CMenu menu;
menu.CreatePopupMenu();
// 添加所有列到菜单(跳过空标题的列)
for (int i = 0; i < (int)m_Columns.size(); i++) {
// 跳过空标题的列
if (m_Columns[i].Name.IsEmpty()) {
continue;
}
UINT flags = MF_STRING;
if (m_Columns[i].Visible) {
flags |= MF_CHECKED;
}
// 不允许隐藏的列显示为灰色
if (!m_Columns[i].CanHide) {
flags |= MF_GRAYED;
}
menu.AppendMenu(flags, 1000 + i, m_Columns[i].Name);
}
// 显示菜单并获取选择
int nCmd = menu.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
if (nCmd >= 1000 && nCmd < 1000 + (int)m_Columns.size()) {
ToggleColumnVisibility(nCmd - 1000);
}
}
void CListCtrlEx::ToggleColumnVisibility(int nColumn)
{
if (nColumn < 0 || nColumn >= (int)m_Columns.size()) {
return;
}
// 不允许隐藏的列不处理
if (!m_Columns[nColumn].CanHide) {
return;
}
// 切换可见性
m_Columns[nColumn].Visible = !m_Columns[nColumn].Visible;
// 保存到配置
SaveColumnVisibility();
// 重新调整列宽
AdjustColumnWidths();
}
void CListCtrlEx::LoadColumnVisibility()
{
if (m_strConfigKey.IsEmpty()) {
return; // 没有设置配置键,不加载
}
// 配置结构list\{ConfigKey},如 list\ClientList
// 格式:逗号分隔的隐藏列名,如 "备注,程序路径"
CT2A configKeyA(m_strConfigKey);
std::string strHidden = THIS_CFG.GetStr("list", std::string(configKeyA), "");
if (strHidden.empty()) {
return; // 使用默认值(全部显示)
}
// 解析隐藏的列名
std::vector<std::string> hiddenNames = StringToVector(strHidden, ',');
for (const auto& name : hiddenNames) {
// 查找列名对应的索引
for (int i = 0; i < (int)m_Columns.size(); i++) {
CT2A colNameA(m_Columns[i].Name);
if (name == std::string(colNameA)) {
if (m_Columns[i].CanHide) {
m_Columns[i].Visible = FALSE;
}
break;
}
}
}
}
void CListCtrlEx::SaveColumnVisibility()
{
if (m_strConfigKey.IsEmpty()) {
return; // 没有设置配置键,不保存
}
// 只保存隐藏的列名
std::string strHidden;
for (int i = 0; i < (int)m_Columns.size(); i++) {
if (!m_Columns[i].Visible) {
if (!strHidden.empty()) strHidden += ",";
CT2A colNameA(m_Columns[i].Name);
strHidden += std::string(colNameA);
}
}
// 配置结构list\{ConfigKey},如 list\ClientList
CT2A configKeyA(m_strConfigKey);
THIS_CFG.SetStr("list", std::string(configKeyA), strHidden);
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <vector>
#include <string>
// 列信息结构
struct ColumnInfoEx {
CString Name; // 列名
int Width; // 初始宽度
float Percent; // 占比
BOOL Visible; // 是否可见
BOOL CanHide; // 是否允许隐藏(如序号列不允许)
};
class CListCtrlEx;
// 自定义 Header 控件,用于阻止隐藏列被拖拽
class CHeaderCtrlEx : public CHeaderCtrl
{
public:
CListCtrlEx* m_pListCtrl = nullptr;
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnBeginTrack(NMHDR* pNMHDR, LRESULT* pResult);
};
// CListCtrlEx - 支持列显示/隐藏功能的列表控件
class CListCtrlEx : public CListCtrl
{
DECLARE_DYNAMIC(CListCtrlEx)
friend class CHeaderCtrlEx; // 允许 CHeaderCtrlEx 访问 protected 成员
public:
CListCtrlEx();
virtual ~CListCtrlEx();
// 设置配置键名(用于区分不同列表的配置,如 "ClientList", "FileList"
void SetConfigKey(const CString& strKey);
// 添加列(替代 InsertColumn
// nCol: 列索引
// lpszColumnHeading: 列标题
// nFormat: 对齐方式,默认左对齐
// nWidth: 列宽
// bCanHide: 是否允许隐藏,默认允许
int AddColumn(int nCol, LPCTSTR lpszColumnHeading, int nWidth, int nFormat = LVCFMT_LEFT, BOOL bCanHide = TRUE);
// 初始化完成后调用,计算百分比并加载配置
void InitColumns();
// 调整列宽(窗口大小改变时调用)
void AdjustColumnWidths();
// 获取列是否可见
BOOL IsColumnVisible(int nCol) const;
// 设置列可见性
void SetColumnVisible(int nCol, BOOL bVisible);
protected:
std::vector<ColumnInfoEx> m_Columns; // 列信息
CString m_strConfigKey; // 配置键名
CHeaderCtrlEx m_HeaderCtrl; // 子类化的 Header 控件
void ShowColumnContextMenu(CPoint pt);
void ToggleColumnVisibility(int nColumn);
void LoadColumnVisibility();
void SaveColumnVisibility();
void SubclassHeader(); // 子类化 Header 控件
DECLARE_MESSAGE_MAP()
afx_msg void OnContextMenu(CWnd* pWnd, CPoint pt);
};