Files
SimpleRemoter/server/2015Remote/CListCtrlEx.cpp
yuanyuanxiang d49c541ea2 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
2026-01-26 21:11:43 +01:00

271 lines
6.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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);
}