mirror of
https://github.com/yuanyuanxiang/SimpleRemoter.git
synced 2026-01-29 02:33:09 +08:00
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:
270
server/2015Remote/CListCtrlEx.cpp
Normal file
270
server/2015Remote/CListCtrlEx.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user