2026-01-18 22:39:36 +01:00
|
|
|
|
// CClientListDlg.cpp: 实现文件
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
|
|
|
#include "afxdialogex.h"
|
|
|
|
|
|
#include "CClientListDlg.h"
|
2026-01-25 12:14:15 +01:00
|
|
|
|
#include "2015Remote.h"
|
2026-01-18 22:39:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// CClientListDlg 对话框
|
|
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
typedef struct {
|
|
|
|
|
|
LPCTSTR Name;
|
|
|
|
|
|
int Width;
|
|
|
|
|
|
float Percent;
|
|
|
|
|
|
} ColumnInfo;
|
|
|
|
|
|
|
|
|
|
|
|
static ColumnInfo g_ColumnInfos[] = {
|
|
|
|
|
|
{ _T("序号"), 40, 0.0f },
|
|
|
|
|
|
{ _T("ID"), 130, 0.0f },
|
|
|
|
|
|
{ _T("备注"), 60, 0.0f },
|
|
|
|
|
|
{ _T("计算机名称"), 105, 0.0f },
|
|
|
|
|
|
{ _T("位置"), 115, 0.0f },
|
|
|
|
|
|
{ _T("IP"), 95, 0.0f },
|
|
|
|
|
|
{ _T("系统"), 100, 0.0f },
|
|
|
|
|
|
{ _T("安装时间"), 115, 0.0f },
|
|
|
|
|
|
{ _T("最后登录"), 115, 0.0f },
|
|
|
|
|
|
{ _T("程序路径"), 150, 0.0f },
|
|
|
|
|
|
{ _T("关注"), 40, 0.0f },
|
|
|
|
|
|
{ _T("授权"), 40, 0.0f },
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static const int g_nColumnCount = _countof(g_ColumnInfos);
|
|
|
|
|
|
|
|
|
|
|
|
// 列索引枚举(与 g_ColumnInfos 顺序一致)
|
|
|
|
|
|
enum ColumnIndex {
|
|
|
|
|
|
COL_NO = 0, // 序号
|
|
|
|
|
|
COL_ID, // ID
|
|
|
|
|
|
COL_NOTE, // 备注
|
|
|
|
|
|
COL_COMPUTER_NAME, // 计算机名称
|
|
|
|
|
|
COL_LOCATION, // 位置
|
|
|
|
|
|
COL_IP, // IP
|
|
|
|
|
|
COL_OS, // 系统
|
|
|
|
|
|
COL_INSTALL_TIME, // 安装时间
|
|
|
|
|
|
COL_LAST_LOGIN, // 最后登录
|
|
|
|
|
|
COL_PROGRAM_PATH, // 程序路径
|
|
|
|
|
|
COL_LEVEL, // 关注
|
|
|
|
|
|
COL_AUTH, // 授权
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 分组字段配置 ==========
|
|
|
|
|
|
// 可用的分组字段枚举
|
|
|
|
|
|
enum GroupField {
|
|
|
|
|
|
GF_IP,
|
|
|
|
|
|
GF_ComputerName,
|
|
|
|
|
|
GF_OsName,
|
|
|
|
|
|
GF_Location,
|
|
|
|
|
|
GF_ProgramPath,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 分组字段配置:字段枚举 + 对应的列索引
|
|
|
|
|
|
struct GroupFieldConfig {
|
|
|
|
|
|
GroupField Field;
|
|
|
|
|
|
int ColumnIndex;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ★★★ 修改这里即可改变分组方式 ★★★
|
|
|
|
|
|
static GroupFieldConfig g_GroupFieldConfigs[] = {
|
|
|
|
|
|
{ GF_IP, COL_IP }, // 按 IP 分组
|
|
|
|
|
|
{ GF_ComputerName, COL_COMPUTER_NAME }, // 按 计算机名称 分组
|
|
|
|
|
|
// { GF_OsName, COL_OS }, // 取消注释:增加按操作系统分组
|
|
|
|
|
|
// { GF_Location, COL_LOCATION }, // 取消注释:增加按位置分组
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static const int g_nGroupFieldCount = _countof(g_GroupFieldConfigs);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据字段枚举获取 ClientValue 中的值
|
|
|
|
|
|
static CString GetFieldValue(const ClientValue& val, GroupField field)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (field) {
|
|
|
|
|
|
case GF_IP: return CString(val.IP);
|
|
|
|
|
|
case GF_ComputerName: return CString(val.ComputerName);
|
|
|
|
|
|
case GF_OsName: return CString(val.OsName);
|
|
|
|
|
|
case GF_Location: return CString(val.Location);
|
|
|
|
|
|
case GF_ProgramPath: return CString(val.ProgramPath);
|
|
|
|
|
|
default: return _T("");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 比较两个客户端的指定列,返回 <0, 0, >0
|
|
|
|
|
|
static int CompareClientByColumn(const std::pair<ClientKey, ClientValue>& a,
|
|
|
|
|
|
const std::pair<ClientKey, ClientValue>& b,
|
|
|
|
|
|
int nColumn)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (nColumn) {
|
|
|
|
|
|
case COL_ID:
|
|
|
|
|
|
return (a.first < b.first) ? -1 : ((a.first > b.first) ? 1 : 0);
|
|
|
|
|
|
case COL_NOTE:
|
|
|
|
|
|
return strcmp(a.second.Note, b.second.Note);
|
|
|
|
|
|
case COL_COMPUTER_NAME:
|
|
|
|
|
|
return strcmp(a.second.ComputerName, b.second.ComputerName);
|
|
|
|
|
|
case COL_LOCATION:
|
|
|
|
|
|
return strcmp(a.second.Location, b.second.Location);
|
|
|
|
|
|
case COL_IP:
|
|
|
|
|
|
return strcmp(a.second.IP, b.second.IP);
|
|
|
|
|
|
case COL_OS:
|
|
|
|
|
|
return strcmp(a.second.OsName, b.second.OsName);
|
|
|
|
|
|
case COL_INSTALL_TIME:
|
|
|
|
|
|
return strcmp(a.second.InstallTime, b.second.InstallTime);
|
|
|
|
|
|
case COL_LAST_LOGIN:
|
|
|
|
|
|
return strcmp(a.second.LastLoginTime, b.second.LastLoginTime);
|
|
|
|
|
|
case COL_PROGRAM_PATH:
|
|
|
|
|
|
return strcmp(a.second.ProgramPath, b.second.ProgramPath);
|
|
|
|
|
|
case COL_LEVEL:
|
|
|
|
|
|
return a.second.Level - b.second.Level;
|
|
|
|
|
|
case COL_AUTH:
|
|
|
|
|
|
return a.second.Authorized - b.second.Authorized;
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 22:39:36 +01:00
|
|
|
|
IMPLEMENT_DYNAMIC(CClientListDlg, CDialogEx)
|
|
|
|
|
|
|
|
|
|
|
|
CClientListDlg::CClientListDlg(_ClientList* clients, CMy2015RemoteDlg* pParent)
|
|
|
|
|
|
: g_ClientList(clients), g_pParent(pParent), CDialogEx(IDD_DIALOG_CLIENTLIST, pParent)
|
|
|
|
|
|
, m_nSortColumn(-1)
|
|
|
|
|
|
, m_bSortAscending(TRUE)
|
2026-01-25 12:14:15 +01:00
|
|
|
|
, m_nTipItem(-1)
|
|
|
|
|
|
, m_nTipSubItem(-1)
|
2026-01-18 22:39:36 +01:00
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CClientListDlg::~CClientListDlg()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::DoDataExchange(CDataExchange* pDX)
|
|
|
|
|
|
{
|
|
|
|
|
|
CDialogEx::DoDataExchange(pDX);
|
|
|
|
|
|
DDX_Control(pDX, IDC_CLIENT_LIST, m_ClientList);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CClientListDlg, CDialogEx)
|
|
|
|
|
|
ON_WM_SIZE()
|
2026-01-25 12:14:15 +01:00
|
|
|
|
ON_WM_CONTEXTMENU()
|
2026-01-18 22:39:36 +01:00
|
|
|
|
ON_NOTIFY(LVN_COLUMNCLICK, IDC_CLIENT_LIST, &CClientListDlg::OnColumnClick)
|
2026-01-25 12:14:15 +01:00
|
|
|
|
ON_NOTIFY(NM_CLICK, IDC_CLIENT_LIST, &CClientListDlg::OnListClick)
|
2026-01-18 22:39:36 +01:00
|
|
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// CClientListDlg 消息处理程序
|
|
|
|
|
|
|
|
|
|
|
|
BOOL CClientListDlg::OnInitDialog()
|
|
|
|
|
|
{
|
|
|
|
|
|
CDialogEx::OnInitDialog();
|
|
|
|
|
|
|
|
|
|
|
|
HICON hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MACHINE));
|
|
|
|
|
|
SetIcon(hIcon, FALSE);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置扩展样式
|
|
|
|
|
|
m_ClientList.SetExtendedStyle(
|
|
|
|
|
|
LVS_EX_FULLROWSELECT | // 整行选中
|
|
|
|
|
|
LVS_EX_GRIDLINES // 显示网格线
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
// 初始化ToolTip
|
|
|
|
|
|
m_ToolTip.Create(this, TTS_ALWAYSTIP | TTS_NOPREFIX);
|
|
|
|
|
|
m_ToolTip.AddTool(&m_ClientList);
|
|
|
|
|
|
m_ToolTip.SetMaxTipWidth(500);
|
|
|
|
|
|
m_ToolTip.SetDelayTime(TTDT_AUTOPOP, 10000);
|
|
|
|
|
|
m_ToolTip.Activate(TRUE);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化列可见性(从配置加载)
|
|
|
|
|
|
m_ColumnVisible.resize(g_nColumnCount, TRUE);
|
|
|
|
|
|
LoadColumnVisibility();
|
|
|
|
|
|
|
2026-01-18 22:39:36 +01:00
|
|
|
|
// 添加列
|
2026-01-25 12:14:15 +01:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-01-18 22:39:36 +01:00
|
|
|
|
|
|
|
|
|
|
// 首次加载数据
|
|
|
|
|
|
AdjustColumnWidths();
|
|
|
|
|
|
RefreshClientList();
|
|
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::RefreshClientList()
|
|
|
|
|
|
{
|
|
|
|
|
|
m_clients = g_ClientList->GetAll(); // 保存到成员变量
|
2026-01-25 12:14:15 +01:00
|
|
|
|
BuildGroups(); // 构建分组
|
2026-01-18 22:39:36 +01:00
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
m_ClientList.SetRedraw(FALSE);
|
|
|
|
|
|
DisplayClients();
|
|
|
|
|
|
m_ClientList.SetRedraw(TRUE);
|
|
|
|
|
|
m_ClientList.Invalidate();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::BuildGroups()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 保留已有分组的展开状态
|
|
|
|
|
|
std::map<GroupKey, BOOL> expandedStates;
|
|
|
|
|
|
for (const auto& pair : m_groups) {
|
|
|
|
|
|
expandedStates[pair.first] = pair.second.bExpanded;
|
2026-01-18 22:39:36 +01:00
|
|
|
|
}
|
2026-01-25 12:14:15 +01:00
|
|
|
|
|
|
|
|
|
|
m_groups.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// 根据配置的字段进行分组
|
|
|
|
|
|
for (const auto& client : m_clients) {
|
|
|
|
|
|
GroupKey key;
|
|
|
|
|
|
for (int i = 0; i < g_nGroupFieldCount; i++) {
|
|
|
|
|
|
key.Values.push_back(GetFieldValue(client.second, g_GroupFieldConfigs[i].Field));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (m_groups.find(key) == m_groups.end()) {
|
|
|
|
|
|
GroupInfo info;
|
|
|
|
|
|
info.GroupId = 0; // 显示时重新编号
|
|
|
|
|
|
// 恢复展开状态,新分组默认收起
|
|
|
|
|
|
auto it = expandedStates.find(key);
|
|
|
|
|
|
info.bExpanded = (it != expandedStates.end()) ? it->second : FALSE;
|
|
|
|
|
|
m_groups[key] = info;
|
|
|
|
|
|
}
|
|
|
|
|
|
m_groups[key].Clients.push_back(client);
|
2026-01-18 22:39:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::DisplayClients()
|
|
|
|
|
|
{
|
|
|
|
|
|
m_ClientList.DeleteAllItems();
|
|
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
// 创建分组指针列表用于排序
|
|
|
|
|
|
std::vector<std::pair<const GroupKey, GroupInfo>*> sortedGroups;
|
|
|
|
|
|
for (auto& pair : m_groups) {
|
|
|
|
|
|
sortedGroups.push_back(&pair);
|
|
|
|
|
|
}
|
2026-01-18 22:39:36 +01:00
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
// 如果有排序列,按第一个设备的该列值排序
|
|
|
|
|
|
if (m_nSortColumn >= 0) {
|
|
|
|
|
|
int sortCol = m_nSortColumn;
|
|
|
|
|
|
BOOL ascending = m_bSortAscending;
|
|
|
|
|
|
std::sort(sortedGroups.begin(), sortedGroups.end(),
|
|
|
|
|
|
[sortCol, ascending](const std::pair<const GroupKey, GroupInfo>* a,
|
|
|
|
|
|
const std::pair<const GroupKey, GroupInfo>* b) {
|
|
|
|
|
|
// 取每个分组的第一个设备进行比较
|
|
|
|
|
|
const auto& clientA = a->second.Clients[0];
|
|
|
|
|
|
const auto& clientB = b->second.Clients[0];
|
|
|
|
|
|
int result = CompareClientByColumn(clientA, clientB, sortCol);
|
|
|
|
|
|
return ascending ? (result < 0) : (result > 0);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-01-18 22:39:36 +01:00
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
int nRow = 0;
|
|
|
|
|
|
int nGroupIndex = 0;
|
|
|
|
|
|
for (auto* pPair : sortedGroups) {
|
|
|
|
|
|
const GroupKey& groupKey = pPair->first;
|
|
|
|
|
|
GroupInfo& groupInfo = pPair->second;
|
|
|
|
|
|
nGroupIndex++;
|
|
|
|
|
|
groupInfo.GroupId = nGroupIndex; // 按显示顺序重新编号
|
|
|
|
|
|
|
|
|
|
|
|
int nItem;
|
|
|
|
|
|
size_t clientCount = groupInfo.Clients.size();
|
|
|
|
|
|
|
|
|
|
|
|
// 只有一个设备时,直接显示设备详情
|
|
|
|
|
|
if (clientCount == 1) {
|
|
|
|
|
|
const ClientKey& key = groupInfo.Clients[0].first;
|
|
|
|
|
|
const ClientValue& val = groupInfo.Clients[0].second;
|
|
|
|
|
|
|
|
|
|
|
|
CString strNo;
|
|
|
|
|
|
strNo.Format(_T("%d"), groupInfo.GroupId);
|
|
|
|
|
|
|
|
|
|
|
|
CString strID;
|
|
|
|
|
|
strID.Format(_T("%llu"), key);
|
|
|
|
|
|
|
|
|
|
|
|
CString strLevel;
|
|
|
|
|
|
strLevel.Format(_T("%d"), val.Level);
|
|
|
|
|
|
|
|
|
|
|
|
CString strAuth = val.Authorized ? _T("Y") : _T("N");
|
|
|
|
|
|
|
|
|
|
|
|
nItem = m_ClientList.InsertItem(nRow, strNo);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_ID, strID);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_LOCATION, val.Location);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_IP, val.IP);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_PROGRAM_PATH, CString(val.ProgramPath));
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
|
|
|
|
|
|
m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
|
|
|
|
|
|
nRow++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
// 多个设备时,显示可展开的分组行
|
|
|
|
|
|
CString strNo;
|
|
|
|
|
|
strNo.Format(_T("%d"), groupInfo.GroupId);
|
|
|
|
|
|
|
|
|
|
|
|
CString strCount;
|
|
|
|
|
|
strCount.Format(_T("%s (%d台设备)"), groupInfo.bExpanded ? _T("-") : _T("+"), (int)clientCount);
|
|
|
|
|
|
|
|
|
|
|
|
nItem = m_ClientList.InsertItem(nRow, strNo);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_ID, strCount);
|
|
|
|
|
|
|
|
|
|
|
|
// 清空所有列
|
|
|
|
|
|
for (int col = COL_NOTE; col < g_nColumnCount; col++) {
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, col, _T(""));
|
|
|
|
|
|
}
|
|
|
|
|
|
// 根据配置填充分组字段到对应列
|
|
|
|
|
|
for (int i = 0; i < g_nGroupFieldCount; i++) {
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, g_GroupFieldConfigs[i].ColumnIndex, groupKey.Values[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 分组行的 ItemData 使用高位标记: 0x8000000000000000 | groupId
|
|
|
|
|
|
m_ClientList.SetItemData(nItem, 0x8000000000000000ULL | groupInfo.GroupId);
|
|
|
|
|
|
nRow++;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果展开,显示组内设备
|
|
|
|
|
|
if (groupInfo.bExpanded) {
|
|
|
|
|
|
for (const auto& client : groupInfo.Clients) {
|
|
|
|
|
|
const ClientKey& key = client.first;
|
|
|
|
|
|
const ClientValue& val = client.second;
|
|
|
|
|
|
|
|
|
|
|
|
CString strSubNo, strID;
|
|
|
|
|
|
strID.Format(_T("%llu"), key);
|
|
|
|
|
|
|
|
|
|
|
|
CString strLevel;
|
|
|
|
|
|
strLevel.Format(_T("%d"), val.Level);
|
|
|
|
|
|
|
|
|
|
|
|
CString strAuth = val.Authorized ? _T("Y") : _T("N");
|
|
|
|
|
|
|
|
|
|
|
|
nItem = m_ClientList.InsertItem(nRow, strSubNo);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_ID, strID);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_NOTE, val.Note);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_COMPUTER_NAME, CString(val.ComputerName));
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_LOCATION, val.Location);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_IP, val.IP);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_OS, val.OsName);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_INSTALL_TIME, val.InstallTime);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_LAST_LOGIN, val.LastLoginTime);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_PROGRAM_PATH, CString(val.ProgramPath));
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_LEVEL, strLevel);
|
|
|
|
|
|
m_ClientList.SetItemText(nItem, COL_AUTH, strAuth);
|
|
|
|
|
|
m_ClientList.SetItemData(nItem, (DWORD_PTR)key);
|
|
|
|
|
|
nRow++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-18 22:39:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
|
|
|
|
|
|
int nColumn = pNMLV->iSubItem;
|
|
|
|
|
|
|
|
|
|
|
|
// 序号列不排序
|
2026-01-25 12:14:15 +01:00
|
|
|
|
if (nColumn == COL_NO) {
|
2026-01-18 22:39:36 +01:00
|
|
|
|
*pResult = 0;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 点击同一列切换排序方向
|
|
|
|
|
|
if (nColumn == m_nSortColumn) {
|
|
|
|
|
|
m_bSortAscending = !m_bSortAscending;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
m_nSortColumn = nColumn;
|
|
|
|
|
|
m_bSortAscending = TRUE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SortByColumn(nColumn, m_bSortAscending);
|
|
|
|
|
|
|
|
|
|
|
|
*pResult = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
void CClientListDlg::SortByColumn(int /*nColumn*/, BOOL /*bAscending*/)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 排序在 DisplayClients 中进行(使用成员变量 m_nSortColumn, m_bSortAscending)
|
2026-01-18 22:39:36 +01:00
|
|
|
|
m_ClientList.SetRedraw(FALSE);
|
|
|
|
|
|
DisplayClients();
|
|
|
|
|
|
m_ClientList.SetRedraw(TRUE);
|
|
|
|
|
|
m_ClientList.Invalidate();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::AdjustColumnWidths()
|
|
|
|
|
|
{
|
|
|
|
|
|
CRect rect;
|
|
|
|
|
|
m_ClientList.GetClientRect(&rect);
|
|
|
|
|
|
int totalWidth = rect.Width() - 20;
|
|
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
// 计算可见列的总百分比
|
|
|
|
|
|
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); // 隐藏列
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-18 22:39:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::OnSize(UINT nType, int cx, int cy)
|
|
|
|
|
|
{
|
|
|
|
|
|
CDialogEx::OnSize(nType, cx, cy);
|
|
|
|
|
|
|
|
|
|
|
|
if (m_ClientList.GetSafeHwnd() == NULL) {
|
|
|
|
|
|
return; // 控件还没创建
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 留点边距
|
|
|
|
|
|
int margin = 10;
|
|
|
|
|
|
|
|
|
|
|
|
// 列表控件填满整个对话框(留边距)
|
|
|
|
|
|
m_ClientList.MoveWindow(margin, margin, cx - margin * 2, cy - margin * 2);
|
|
|
|
|
|
|
|
|
|
|
|
AdjustColumnWidths();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-25 12:14:15 +01:00
|
|
|
|
void CClientListDlg::OnListClick(NMHDR* pNMHDR, LRESULT* pResult)
|
|
|
|
|
|
{
|
|
|
|
|
|
LPNMITEMACTIVATE pNMIA = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
|
|
|
|
|
|
int nItem = pNMIA->iItem;
|
|
|
|
|
|
|
|
|
|
|
|
if (nItem < 0) {
|
|
|
|
|
|
*pResult = 0;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DWORD_PTR itemData = m_ClientList.GetItemData(nItem);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为分组行 (高位被设置)
|
|
|
|
|
|
if (itemData & 0x8000000000000000ULL) {
|
|
|
|
|
|
int groupId = (int)(itemData & 0x7FFFFFFFFFFFFFFFULL);
|
|
|
|
|
|
|
|
|
|
|
|
// 查找对应的分组并切换展开状态
|
|
|
|
|
|
for (auto& pair : m_groups) {
|
|
|
|
|
|
if (pair.second.GroupId == groupId) {
|
|
|
|
|
|
pair.second.bExpanded = !pair.second.bExpanded;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新显示
|
|
|
|
|
|
m_ClientList.SetRedraw(FALSE);
|
|
|
|
|
|
DisplayClients();
|
|
|
|
|
|
m_ClientList.SetRedraw(TRUE);
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复选中状态:找到刷新后的分组行并选中
|
|
|
|
|
|
for (int i = 0; i < m_ClientList.GetItemCount(); i++) {
|
|
|
|
|
|
DWORD_PTR data = m_ClientList.GetItemData(i);
|
|
|
|
|
|
if ((data & 0x8000000000000000ULL) &&
|
|
|
|
|
|
(int)(data & 0x7FFFFFFFFFFFFFFFULL) == groupId) {
|
|
|
|
|
|
m_ClientList.SetItemState(i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
|
|
|
|
|
|
m_ClientList.EnsureVisible(i, FALSE);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_ClientList.Invalidate();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
*pResult = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BOOL CClientListDlg::PreTranslateMessage(MSG* pMsg)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (pMsg->message == WM_MOUSEMOVE && pMsg->hwnd == m_ClientList.GetSafeHwnd()) {
|
|
|
|
|
|
m_ToolTip.RelayEvent(pMsg);
|
|
|
|
|
|
|
|
|
|
|
|
CPoint pt(pMsg->lParam);
|
|
|
|
|
|
LVHITTESTINFO hitInfo = {};
|
|
|
|
|
|
hitInfo.pt = pt;
|
|
|
|
|
|
m_ClientList.SubItemHitTest(&hitInfo);
|
|
|
|
|
|
|
|
|
|
|
|
int nItem = hitInfo.iItem;
|
|
|
|
|
|
int nSubItem = hitInfo.iSubItem;
|
|
|
|
|
|
|
|
|
|
|
|
if (nItem != m_nTipItem || nSubItem != m_nTipSubItem) {
|
|
|
|
|
|
m_nTipItem = nItem;
|
|
|
|
|
|
m_nTipSubItem = nSubItem;
|
|
|
|
|
|
|
|
|
|
|
|
if (nItem >= 0) {
|
|
|
|
|
|
CString strText = m_ClientList.GetItemText(nItem, nSubItem);
|
|
|
|
|
|
|
|
|
|
|
|
// 判断文本是否被截断
|
|
|
|
|
|
CClientDC dc(&m_ClientList);
|
|
|
|
|
|
CFont* pOldFont = dc.SelectObject(m_ClientList.GetFont());
|
|
|
|
|
|
CSize textSize = dc.GetTextExtent(strText);
|
|
|
|
|
|
dc.SelectObject(pOldFont);
|
|
|
|
|
|
|
|
|
|
|
|
int colWidth = m_ClientList.GetColumnWidth(nSubItem);
|
|
|
|
|
|
if (textSize.cx + 12 > colWidth && !strText.IsEmpty()) {
|
|
|
|
|
|
m_ToolTip.UpdateTipText(strText, &m_ClientList);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m_ToolTip.UpdateTipText(_T(""), &m_ClientList);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m_ToolTip.UpdateTipText(_T(""), &m_ClientList);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return CDialogEx::PreTranslateMessage(pMsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 22:39:36 +01:00
|
|
|
|
void CClientListDlg::OnCancel()
|
|
|
|
|
|
{
|
|
|
|
|
|
DestroyWindow();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::PostNcDestroy()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (g_pParent) {
|
|
|
|
|
|
g_pParent->m_pClientListDlg = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CDialogEx::PostNcDestroy();
|
|
|
|
|
|
|
|
|
|
|
|
delete this;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CClientListDlg::OnOK()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
2026-01-25 12:14:15 +01:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|