diff --git a/server/2015Remote/2015RemoteDlg.cpp b/server/2015Remote/2015RemoteDlg.cpp index 01c845f..8cdeb7a 100644 --- a/server/2015Remote/2015RemoteDlg.cpp +++ b/server/2015Remote/2015RemoteDlg.cpp @@ -706,11 +706,12 @@ VOID CMy2015RemoteDlg::InitControl() rect.bottom+=20; MoveWindow(rect); 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 #include -#include"file_server.h" +#include "file_server.h" +#include "CListCtrlEx.h" ////////////////////////////////////////////////////////////////////////// // 以下为特殊需求使用 @@ -151,8 +152,8 @@ public: VOID SendAllCommand(PBYTE szBuffer, ULONG ulLength); // 显示用户上线信息 CWnd* m_pFloatingTip = nullptr; - CListCtrl m_CList_Online; - CListCtrl m_CList_Message; + CListCtrlEx m_CList_Online; + CListCtrl m_CList_Message; std::set m_HostList; std::set m_GroupList; std::string m_selectedGroup; diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj b/server/2015Remote/2015Remote_vs2015.vcxproj index 6e1f5c5..450c59c 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj +++ b/server/2015Remote/2015Remote_vs2015.vcxproj @@ -287,6 +287,7 @@ + @@ -382,6 +383,7 @@ + diff --git a/server/2015Remote/2015Remote_vs2015.vcxproj.filters b/server/2015Remote/2015Remote_vs2015.vcxproj.filters index 2233bf2..6bdd3f8 100644 --- a/server/2015Remote/2015Remote_vs2015.vcxproj.filters +++ b/server/2015Remote/2015Remote_vs2015.vcxproj.filters @@ -66,6 +66,7 @@ + @@ -146,6 +147,7 @@ + diff --git a/server/2015Remote/CClientListDlg.cpp b/server/2015Remote/CClientListDlg.cpp index 0c633f8..f6d1056 100644 --- a/server/2015Remote/CClientListDlg.cpp +++ b/server/2015Remote/CClientListDlg.cpp @@ -144,7 +144,6 @@ void CClientListDlg::DoDataExchange(CDataExchange* pDX) BEGIN_MESSAGE_MAP(CClientListDlg, CDialogEx) ON_WM_SIZE() - ON_WM_CONTEXTMENU() ON_NOTIFY(LVN_COLUMNCLICK, IDC_CLIENT_LIST, &CClientListDlg::OnColumnClick) ON_NOTIFY(NM_CLICK, IDC_CLIENT_LIST, &CClientListDlg::OnListClick) END_MESSAGE_MAP() @@ -172,22 +171,19 @@ BOOL CClientListDlg::OnInitDialog() m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 10000); m_ToolTip.Activate(TRUE); - // 初始化列可见性(从配置加载) - m_ColumnVisible.resize(g_nColumnCount, TRUE); - LoadColumnVisibility(); + // 设置配置键名 + m_ClientList.SetConfigKey(_T("ClientList")); - // 添加列 - int totalWidth = 0; + // 添加列(第一列序号不允许隐藏) for (int i = 0; i < g_nColumnCount; i++) { - totalWidth += g_ColumnInfos[i].Width; - } - 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); + BOOL bCanHide = (i != COL_NO); // 序号列不允许隐藏 + m_ClientList.AddColumn(i, g_ColumnInfos[i].Name, g_ColumnInfos[i].Width, LVCFMT_LEFT, bCanHide); } + // 初始化列(计算百分比、加载配置、应用列宽) + m_ClientList.InitColumns(); + // 首次加载数据 - AdjustColumnWidths(); RefreshClientList(); return TRUE; @@ -394,27 +390,7 @@ void CClientListDlg::SortByColumn(int /*nColumn*/, BOOL /*bAscending*/) void CClientListDlg::AdjustColumnWidths() { - CRect rect; - 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); // 隐藏列 - } - } + m_ClientList.AdjustColumnWidths(); } void CClientListDlg::OnSize(UINT nType, int cx, int cy) @@ -540,103 +516,3 @@ void CClientListDlg::PostNcDestroy() 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 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); -} diff --git a/server/2015Remote/CClientListDlg.h b/server/2015Remote/CClientListDlg.h index dbaf1fe..7de852a 100644 --- a/server/2015Remote/CClientListDlg.h +++ b/server/2015Remote/CClientListDlg.h @@ -6,6 +6,7 @@ #include #include #include "2015RemoteDlg.h" +#include "CListCtrlEx.h" // 分组键:支持任意字段组合 struct GroupKey { @@ -53,19 +54,14 @@ protected: CToolTipCtrl m_ToolTip; int m_nTipItem; // 当前提示的行 int m_nTipSubItem; // 当前提示的列 - std::vector m_ColumnVisible; // 各列的显示状态 void BuildGroups(); // 构建分组数据 - void ShowColumnContextMenu(CPoint pt); // 显示列选择菜单 - void ToggleColumnVisibility(int nColumn); // 切换列的显示/隐藏 - void LoadColumnVisibility(); // 从配置加载列可见性 - void SaveColumnVisibility(); // 保存列可见性到配置 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 DECLARE_MESSAGE_MAP() public: - CListCtrl m_ClientList; + CListCtrlEx m_ClientList; virtual BOOL OnInitDialog(); void RefreshClientList(); void DisplayClients(); @@ -74,7 +70,6 @@ public: afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnColumnClick(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 void OnCancel(); virtual void PostNcDestroy(); diff --git a/server/2015Remote/CListCtrlEx.cpp b/server/2015Remote/CListCtrlEx.cpp new file mode 100644 index 0000000..f06d787 --- /dev/null +++ b/server/2015Remote/CListCtrlEx.cpp @@ -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(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 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); +} diff --git a/server/2015Remote/CListCtrlEx.h b/server/2015Remote/CListCtrlEx.h new file mode 100644 index 0000000..e36efa9 --- /dev/null +++ b/server/2015Remote/CListCtrlEx.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +// 列信息结构 +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 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); +};