Files
SimpleRemoter/server/2015Remote/CClientListDlg.cpp

643 lines
20 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.
// CClientListDlg.cpp: 实现文件
//
#include "stdafx.h"
#include "afxdialogex.h"
#include "CClientListDlg.h"
#include "2015Remote.h"
// CClientListDlg 对话框
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;
}
}
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)
, m_nTipItem(-1)
, m_nTipSubItem(-1)
{
}
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()
ON_WM_CONTEXTMENU()
ON_NOTIFY(LVN_COLUMNCLICK, IDC_CLIENT_LIST, &CClientListDlg::OnColumnClick)
ON_NOTIFY(NM_CLICK, IDC_CLIENT_LIST, &CClientListDlg::OnListClick)
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 // 显示网格线
);
// 初始化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();
// 添加列
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);
}
// 首次加载数据
AdjustColumnWidths();
RefreshClientList();
return TRUE;
}
void CClientListDlg::RefreshClientList()
{
m_clients = g_ClientList->GetAll(); // 保存到成员变量
BuildGroups(); // 构建分组
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;
}
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);
}
}
void CClientListDlg::DisplayClients()
{
m_ClientList.DeleteAllItems();
// 创建分组指针列表用于排序
std::vector<std::pair<const GroupKey, GroupInfo>*> sortedGroups;
for (auto& pair : m_groups) {
sortedGroups.push_back(&pair);
}
// 如果有排序列,按第一个设备的该列值排序
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);
});
}
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++;
}
}
}
}
}
void CClientListDlg::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
int nColumn = pNMLV->iSubItem;
// 序号列不排序
if (nColumn == COL_NO) {
*pResult = 0;
return;
}
// 点击同一列切换排序方向
if (nColumn == m_nSortColumn) {
m_bSortAscending = !m_bSortAscending;
}
else {
m_nSortColumn = nColumn;
m_bSortAscending = TRUE;
}
SortByColumn(nColumn, m_bSortAscending);
*pResult = 0;
}
void CClientListDlg::SortByColumn(int /*nColumn*/, BOOL /*bAscending*/)
{
// 排序在 DisplayClients 中进行(使用成员变量 m_nSortColumn, m_bSortAscending
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;
// 计算可见列的总百分比
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)
{
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();
}
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);
}
void CClientListDlg::OnCancel()
{
DestroyWindow();
}
void CClientListDlg::PostNcDestroy()
{
if (g_pParent) {
g_pParent->m_pClientListDlg = nullptr;
}
CDialogEx::PostNcDestroy();
delete this;
}
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);
}