1 Commits

Author SHA1 Message Date
yuanyuanxiang
b97f086c0c Feature: Add log search bar with prev/next/clear for m_CList_Message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 00:10:12 +02:00
8 changed files with 407 additions and 12 deletions

View File

@@ -62,6 +62,7 @@
#include "common/file_upload.h" #include "common/file_upload.h"
#include "SplashDlg.h" #include "SplashDlg.h"
#include "SearchBarDlg.h" #include "SearchBarDlg.h"
#include "LogSearchBar.h"
#include <ServerServiceWrapper.h> #include <ServerServiceWrapper.h>
#include "CDlgFileSend.h" #include "CDlgFileSend.h"
#include "CClientListDlg.h" #include "CClientListDlg.h"
@@ -689,6 +690,10 @@ CMy2015RemoteDlg::~CMy2015RemoteDlg()
m_pSearchBar->DestroyWindow(); m_pSearchBar->DestroyWindow();
SAFE_DELETE(m_pSearchBar); SAFE_DELETE(m_pSearchBar);
} }
if (m_pLogSearchBar) {
m_pLogSearchBar->DestroyWindow();
SAFE_DELETE(m_pLogSearchBar);
}
} }
// DLL 请求限流成员函数实现 (根据配置限制请求频率) // DLL 请求限流成员函数实现 (根据配置限制请求频率)
@@ -932,6 +937,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete) ON_COMMAND(ID_MSGLOG_DELETE, &CMy2015RemoteDlg::OnMsglogDelete)
ON_COMMAND(ID_MSGLOG_COPY, &CMy2015RemoteDlg::OnMsglogCopy) ON_COMMAND(ID_MSGLOG_COPY, &CMy2015RemoteDlg::OnMsglogCopy)
ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear) ON_COMMAND(ID_MSGLOG_CLEAR, &CMy2015RemoteDlg::OnMsglogClear)
ON_COMMAND(ID_MSGLOG_SEARCH, &CMy2015RemoteDlg::OnMsglogSearch)
ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch) ON_COMMAND(ID_ONLINE_ADD_WATCH, &CMy2015RemoteDlg::OnOnlineAddWatch)
ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify) ON_COMMAND(ID_ONLINE_LOGIN_NOTIFY, &CMy2015RemoteDlg::OnOnlineLoginNotify)
ON_NOTIFY(NM_CUSTOMDRAW, IDC_ONLINE, &CMy2015RemoteDlg::OnNMCustomdrawOnline) ON_NOTIFY(NM_CUSTOMDRAW, IDC_ONLINE, &CMy2015RemoteDlg::OnNMCustomdrawOnline)
@@ -1429,6 +1435,9 @@ VOID CMy2015RemoteDlg::InitControl()
m_nSplitPos = max(60, min(m_nSplitPos, 600)); m_nSplitPos = max(60, min(m_nSplitPos, 600));
m_SplitterBar.Create(this); m_SplitterBar.Create(this);
m_pLogSearchBar = new CLogSearchBar();
m_pLogSearchBar->Create(this, &m_CList_Message);
// 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg // 不在这里调 ApplyThumbnailSettings —— 调用方在 LoadThumbnailSettingsFromCfg
// 之后统一 Apply避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。 // 之后统一 Apply避免"先用默认值 Apply 一次,再读 INI 后再 Apply 一次"的双绘)。
} }
@@ -1670,6 +1679,23 @@ LRESULT CMy2015RemoteDlg::OnShowMessage(WPARAM wParam, LPARAM lParam)
// 消息日志最大条数,达到上限时删除最旧的记录 // 消息日志最大条数,达到上限时删除最旧的记录
#define MAX_MESSAGE_COUNT 1000 #define MAX_MESSAGE_COUNT 1000
namespace {
bool MsgHasKW(const CString& s, const CString* kw, int n) {
for (int i = 0; i < n; i++)
if (s.Find(kw[i]) >= 0) return true;
return false;
}
int MsgColorLevel(const CString& type, const CString& msg) {
const CString redKW[] = { _L("严禁"), _L("禁止") };
const CString orangeKW[] = { _L("失败"), _L("警告") };
const CString greenKW[] = { _L("成功"), _L("") };
if (MsgHasKW(type, redKW, _countof(redKW)) || MsgHasKW(msg, redKW, _countof(redKW))) return 1;
if (MsgHasKW(type, orangeKW, _countof(orangeKW)) || MsgHasKW(msg, orangeKW, _countof(orangeKW))) return 2;
if (MsgHasKW(msg, greenKW, _countof(greenKW))) return 3;
return 0;
}
}
VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg) VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
{ {
AUTO_TICK(200, ""); AUTO_TICK(200, "");
@@ -1682,9 +1708,10 @@ VOID CMy2015RemoteDlg::ShowMessage(CString strType, CString strMsg)
m_CList_Message.DeleteItem(count - 1); m_CList_Message.DeleteItem(count - 1);
} }
m_CList_Message.InsertItem(0, strType); //向控件中设置数据 m_CList_Message.InsertItem(0, strType);
m_CList_Message.SetItemText(0,1,strTime); m_CList_Message.SetItemText(0, 1, strTime);
m_CList_Message.SetItemText(0,2,strMsg); m_CList_Message.SetItemText(0, 2, strMsg);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strType, strMsg));
CString strStatusMsg; CString strStatusMsg;
@@ -1759,9 +1786,12 @@ LRESULT CMy2015RemoteDlg::OnShowErrMessage(WPARAM wParam, LPARAM lParam)
m_CList_Message.DeleteItem(count - 1); m_CList_Message.DeleteItem(count - 1);
} }
m_CList_Message.InsertItem(0, title ? _L(*title) : _TR("操作错误")); CString strTitle = title ? _L(*title) : _TR("操作错误");
CString strText = text ? _L(*text) : _TR("内部错误");
m_CList_Message.InsertItem(0, strTitle);
m_CList_Message.SetItemText(0, 1, strTime); m_CList_Message.SetItemText(0, 1, strTime);
m_CList_Message.SetItemText(0, 2, text ? _L(*text) : _TR("内部错误")); m_CList_Message.SetItemText(0, 2, strText);
m_CList_Message.SetItemData(0, (DWORD_PTR)MsgColorLevel(strTitle, strText));
if(title)delete title; if(title)delete title;
if(text)delete text; if(text)delete text;
@@ -3225,7 +3255,8 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
lastType = nType; lastType = nType;
BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1; BOOL hideLog = THIS_CFG.GetInt("settings", "HideMsg", 0) == 1;
const int SPLITTER_H = 6; const int SPLITTER_H = 6;
const int LOG_SEARCH_H = 26; // 日志搜索条高度
// 日志区有效高度 = m_nSplitPos不含分割条分割条紧贴日志区上方 // 日志区有效高度 = m_nSplitPos不含分割条分割条紧贴日志区上方
int splitPos = hideLog ? 0 : m_nSplitPos; int splitPos = hideLog ? 0 : m_nSplitPos;
@@ -3252,6 +3283,22 @@ void CMy2015RemoteDlg::OnSize(UINT nType, int cx, int cy)
} }
LeaveCriticalSection(&m_cs); LeaveCriticalSection(&m_cs);
if (m_pLogSearchBar && m_pLogSearchBar->GetSafeHwnd()) {
if (hideLog || !m_bLogSearchVisible) {
m_pLogSearchBar->ShowWindow(SW_HIDE);
} else {
m_pLogSearchBar->ShowWindow(SW_SHOW);
// 右对齐,最多 380px左侧留出 180px 给分组信息
int barW = min(260, max(60, cx - 182));
int barX = cx - 2 - barW;
// 叠在在线列表底部右角:紧贴分割条上方,向上偏移一个条高
int barY = cy - 20 - splitPos - SPLITTER_H - LOG_SEARCH_H + 5;
m_pLogSearchBar->MoveWindow(barX, barY, barW, LOG_SEARCH_H);
// 保证搜索条在列表控件上层BringWindowToTop 仅影响 Z-order不影响焦点
m_pLogSearchBar->BringWindowToTop();
}
}
if (m_SplitterBar.m_hWnd != NULL) { if (m_SplitterBar.m_hWnd != NULL) {
if (hideLog) { if (hideLog) {
m_SplitterBar.ShowWindow(SW_HIDE); m_SplitterBar.ShowWindow(SW_HIDE);
@@ -9196,10 +9243,40 @@ void CMy2015RemoteDlg::OnNMCustomdrawMessage(NMHDR* pNMHDR, LRESULT* pResult)
case CDDS_ITEMPREPAINT: { case CDDS_ITEMPREPAINT: {
int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec); int nRow = static_cast<int>(pLVCD->nmcd.dwItemSpec);
int nLastRow = m_CList_Message.GetItemCount() - 1;
if (nRow == nLastRow && nLastRow >= 0) { // 搜索命中行深蓝高亮(不依赖 CDIS_SELECTED直接读搜索栏当前命中行号
pLVCD->clrText = RGB(255, 0, 0); if (m_pLogSearchBar) {
int nHit = m_pLogSearchBar->GetHighlightedRow();
if (nHit >= 0 && nRow == nHit) {
pLVCD->clrTextBk = RGB(0, 80, 180);
pLVCD->clrText = RGB(255, 255, 255);
*pResult = CDRF_NEWFONT;
return;
}
} }
// 关键字着色:必须同时设 clrText + clrTextBk否则系统预填的蓝色会泄漏
int colorLevel = (int)m_CList_Message.GetItemData(nRow);
COLORREF bkDefault = ::GetSysColor(COLOR_WINDOW);
if (colorLevel == 1) {
pLVCD->clrText = RGB(220, 0, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
if (colorLevel == 2) {
pLVCD->clrText = RGB(200, 100, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
if (colorLevel == 3) {
pLVCD->clrText = RGB(0, 140, 0);
pLVCD->clrTextBk = bkDefault;
*pResult = CDRF_NEWFONT;
return;
}
break;
} }
} }
} }
@@ -9213,6 +9290,9 @@ void CMy2015RemoteDlg::OnRClickMessage(NMHDR* pNMHDR, LRESULT* pResult)
menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中")); menu.AppendMenu(MF_STRING, ID_MSGLOG_DELETE, _TR("删除选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_COPY, _TR("复制选中")); menu.AppendMenu(MF_STRING, ID_MSGLOG_COPY, _TR("复制选中"));
menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志")); menu.AppendMenu(MF_STRING, ID_MSGLOG_CLEAR, _TR("清空日志"));
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_STRING, ID_MSGLOG_SEARCH,
m_bLogSearchVisible ? _TR("隐藏搜索") : _TR("搜索日志"));
// 没有选中项时禁用"删除选中" // 没有选中项时禁用"删除选中"
if (m_CList_Message.GetSelectedCount() == 0) { if (m_CList_Message.GetSelectedCount() == 0) {
@@ -9237,7 +9317,7 @@ void CMy2015RemoteDlg::OnMsglogDelete()
while (pos) { while (pos) {
selected.push_back(m_CList_Message.GetNextSelectedItem(pos)); selected.push_back(m_CList_Message.GetNextSelectedItem(pos));
} }
// 倒序删除 // 倒序删除,避免索引变化
for (auto it = selected.rbegin(); it != selected.rend(); ++it) { for (auto it = selected.rbegin(); it != selected.rend(); ++it) {
m_CList_Message.DeleteItem(*it); m_CList_Message.DeleteItem(*it);
} }
@@ -9280,6 +9360,14 @@ void CMy2015RemoteDlg::OnMsglogClear()
m_CList_Message.DeleteAllItems(); m_CList_Message.DeleteAllItems();
} }
void CMy2015RemoteDlg::OnMsglogSearch()
{
m_bLogSearchVisible = !m_bLogSearchVisible;
// 复用 OnSize 里的布局逻辑
CRect rc;
GetClientRect(&rc);
OnSize(SIZE_RESTORED, rc.Width(), rc.Height());
}
void CMy2015RemoteDlg::OnOnlineAddWatch() void CMy2015RemoteDlg::OnOnlineAddWatch()
{ {

View File

@@ -75,6 +75,7 @@ class CSplashDlg; // 前向声明
class CClientListDlg; class CClientListDlg;
class CLicenseDlg; class CLicenseDlg;
class CSearchBarDlg; class CSearchBarDlg;
class CLogSearchBar;
#include "pwd_gen.h" #include "pwd_gen.h"
@@ -140,7 +141,9 @@ public:
_ClientList *m_ClientMap = nullptr; _ClientList *m_ClientMap = nullptr;
CClientListDlg* m_pClientListDlg = nullptr; CClientListDlg* m_pClientListDlg = nullptr;
CLicenseDlg* m_pLicenseDlg = nullptr; CLicenseDlg* m_pLicenseDlg = nullptr;
CSearchBarDlg* m_pSearchBar = nullptr; // 搜索工具栏 CSearchBarDlg* m_pSearchBar = nullptr;
CLogSearchBar* m_pLogSearchBar = nullptr;
bool m_bLogSearchVisible = false; // 日志搜索条是否显示(右键菜单切换)
BOOL m_bEnableFileV2 = FALSE; // V2 文件传输开关 BOOL m_bEnableFileV2 = FALSE; // V2 文件传输开关
// 构造 // 构造
@@ -560,6 +563,7 @@ public:
afx_msg void OnMsglogDelete(); afx_msg void OnMsglogDelete();
afx_msg void OnMsglogCopy(); afx_msg void OnMsglogCopy();
afx_msg void OnMsglogClear(); afx_msg void OnMsglogClear();
afx_msg void OnMsglogSearch();
afx_msg void OnOnlineAddWatch(); afx_msg void OnOnlineAddWatch();
afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnNMCustomdrawOnline(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnOnlineRunAsAdmin(); afx_msg void OnOnlineRunAsAdmin();

View File

@@ -291,6 +291,7 @@
<ClInclude Include="BuildDlg.h" /> <ClInclude Include="BuildDlg.h" />
<ClInclude Include="CClientListDlg.h" /> <ClInclude Include="CClientListDlg.h" />
<ClInclude Include="CClientLog.h" /> <ClInclude Include="CClientLog.h" />
<ClInclude Include="LogSearchBar.h" />
<ClInclude Include="CDlgFileSend.h" /> <ClInclude Include="CDlgFileSend.h" />
<ClInclude Include="CDrawingBoard.h" /> <ClInclude Include="CDrawingBoard.h" />
<ClInclude Include="CGridDialog.h" /> <ClInclude Include="CGridDialog.h" />
@@ -373,6 +374,7 @@
<ItemGroup> <ItemGroup>
<ClCompile Include="..\..\client\Audio.cpp" /> <ClCompile Include="..\..\client\Audio.cpp" />
<ClCompile Include="CClientLog.cpp" /> <ClCompile Include="CClientLog.cpp" />
<ClCompile Include="LogSearchBar.cpp" />
<ClCompile Include="msvc_compat.c"> <ClCompile Include="msvc_compat.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>

View File

@@ -0,0 +1,229 @@
// LogSearchBar.cpp - 日志列表搜索条实现
#include "stdafx.h"
#include "LogSearchBar.h"
#include "LangManager.h"
BEGIN_MESSAGE_MAP(CLogSearchBar, CWnd)
ON_WM_CREATE()
ON_WM_SIZE()
ON_WM_ERASEBKGND()
ON_WM_TIMER()
ON_EN_CHANGE(IDC_LOG_SEARCH_EDIT, OnEnChangeSearch)
ON_BN_CLICKED(IDC_LOG_SEARCH_PREV, OnBnClickedPrev)
ON_BN_CLICKED(IDC_LOG_SEARCH_NEXT, OnBnClickedNext)
ON_BN_CLICKED(IDC_LOG_SEARCH_CLEAR, OnBnClickedClear)
END_MESSAGE_MAP()
CLogSearchBar::CLogSearchBar()
{
}
CLogSearchBar::~CLogSearchBar()
{
}
BOOL CLogSearchBar::Create(CWnd* pParent, CListCtrl* pList)
{
m_pList = pList;
LPCTSTR cls = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW,
::LoadCursor(NULL, IDC_ARROW));
return CWnd::Create(cls, _T(""), WS_CHILD | WS_CLIPCHILDREN,
CRect(0, 0, 1, 1), pParent, 0);
}
int CLogSearchBar::OnCreate(LPCREATESTRUCT lpcs)
{
if (CWnd::OnCreate(lpcs) == -1) return -1;
// 搜索输入框
m_editSearch.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_EDIT);
m_editSearch.SetCueBanner(L"搜索日志...", TRUE);
// 清除按钮
m_btnClear.Create(_T("X"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_CLEAR);
// 上一个 / 下一个按钮(系统样式,与浅色背景融合)
m_btnPrev.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_PREV);
m_btnNext.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_FLAT,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_NEXT);
// 结果计数
m_staticCount.Create(_T(""), WS_CHILD | WS_VISIBLE | SS_CENTER | SS_CENTERIMAGE,
CRect(0, 0, 1, 1), this, IDC_LOG_SEARCH_COUNT);
// 统一字体:跟父窗口(主对话框)保持一致
CFont* pFont = GetParent()->GetFont();
if (pFont) {
m_staticCount.SetFont(pFont);
m_editSearch.SetFont(pFont);
m_btnClear.SetFont(pFont);
m_btnPrev.SetFont(pFont);
m_btnNext.SetFont(pFont);
}
return 0;
}
void CLogSearchBar::LayoutControls(int cx, int cy)
{
if (!m_editSearch.GetSafeHwnd()) return;
const int m = 2;
const int h = cy - 2 * m;
if (h <= 0) return;
const int btnW = h;
const int cntW = 62;
// 搜索框填满剩余空间barW 由外部控制总宽,这里保证控件铺满)
const int editW = max(20, cx - 2 * m - 3 * btnW - 2 * m - cntW);
int x = m;
m_staticCount.MoveWindow(x, m, cntW, h);
x += cntW + m;
m_editSearch.MoveWindow(x, m, editW, h);
x += editW + m;
m_btnClear.MoveWindow(x, m, btnW, h);
x += btnW;
m_btnPrev.MoveWindow(x, m, btnW, h);
x += btnW;
m_btnNext.MoveWindow(x, m, btnW, h);
}
void CLogSearchBar::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
LayoutControls(cx, cy);
}
BOOL CLogSearchBar::OnEraseBkgnd(CDC* pDC)
{
CRect rc;
GetClientRect(&rc);
pDC->FillSolidRect(rc, ::GetSysColor(COLOR_3DFACE));
return TRUE;
}
void CLogSearchBar::OnEnChangeSearch()
{
KillTimer(TIMER_SEARCH);
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
}
void CLogSearchBar::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == TIMER_SEARCH) {
KillTimer(TIMER_SEARCH);
DoSearch();
}
CWnd::OnTimer(nIDEvent);
}
void CLogSearchBar::DoSearch()
{
if (!m_pList || !m_pList->GetSafeHwnd()) return;
CString kw;
m_editSearch.GetWindowText(kw);
m_Results.clear();
m_nCurrentIndex = -1;
if (kw.IsEmpty()) {
UpdateCountText();
return;
}
CString kwLow = kw;
kwLow.MakeLower();
int nCols = m_pList->GetHeaderCtrl() ? m_pList->GetHeaderCtrl()->GetItemCount() : 1;
int nItems = m_pList->GetItemCount();
for (int i = 0; i < nItems; i++) {
for (int c = 0; c < nCols; c++) {
CString cell = m_pList->GetItemText(i, c);
cell.MakeLower();
if (cell.Find(kwLow) >= 0) {
m_Results.push_back(i);
break;
}
}
}
if (!m_Results.empty()) {
m_nCurrentIndex = 0;
GotoMatch(m_nCurrentIndex);
}
UpdateCountText();
}
void CLogSearchBar::GotoMatch(int idx)
{
if (!m_pList || idx < 0 || idx >= (int)m_Results.size()) return;
int item = m_Results[idx];
// 取消所有选中
int n = -1;
while ((n = m_pList->GetNextItem(-1, LVNI_SELECTED)) != -1)
m_pList->SetItemState(n, 0, LVIS_SELECTED | LVIS_FOCUSED);
m_pList->SetItemState(item, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
m_pList->EnsureVisible(item, FALSE);
}
void CLogSearchBar::UpdateCountText()
{
CString text;
if (m_Results.empty()) {
CString kw;
m_editSearch.GetWindowText(kw);
text = kw.IsEmpty() ? _TR("") : _TR("无结果");
} else {
text.Format(_T("%d/%d"), m_nCurrentIndex + 1, (int)m_Results.size());
}
m_staticCount.SetWindowText(text);
}
void CLogSearchBar::InvalidateSearch()
{
KillTimer(TIMER_SEARCH);
m_Results.clear();
m_nCurrentIndex = -1;
UpdateCountText();
if (m_editSearch.GetSafeHwnd()) {
CString kw;
m_editSearch.GetWindowText(kw);
if (!kw.IsEmpty())
SetTimer(TIMER_SEARCH, SEARCH_DELAY_MS, NULL);
}
}
void CLogSearchBar::OnBnClickedPrev()
{
if (m_Results.empty()) return;
m_nCurrentIndex--;
if (m_nCurrentIndex < 0)
m_nCurrentIndex = (int)m_Results.size() - 1;
GotoMatch(m_nCurrentIndex);
UpdateCountText();
}
void CLogSearchBar::OnBnClickedNext()
{
if (m_Results.empty()) return;
m_nCurrentIndex++;
if (m_nCurrentIndex >= (int)m_Results.size())
m_nCurrentIndex = 0;
GotoMatch(m_nCurrentIndex);
UpdateCountText();
}
void CLogSearchBar::OnBnClickedClear()
{
m_editSearch.SetWindowText(_T(""));
m_editSearch.SetFocus();
// 触发 EN_CHANGE → 定时器 → DoSearch 清空结果
}

View File

@@ -0,0 +1,57 @@
// LogSearchBar.h - 日志列表搜索条(嵌入式,固定在右侧)
#pragma once
#include <afxwin.h>
#include <afxcmn.h>
#include <vector>
#define IDC_LOG_SEARCH_EDIT 5001
#define IDC_LOG_SEARCH_PREV 5002
#define IDC_LOG_SEARCH_NEXT 5003
#define IDC_LOG_SEARCH_COUNT 5004
#define IDC_LOG_SEARCH_CLEAR 5005
class CLogSearchBar : public CWnd
{
public:
CLogSearchBar();
virtual ~CLogSearchBar();
// pParent: 主对话框; pList: 要搜索的 CListCtrlm_CList_Message
BOOL Create(CWnd* pParent, CListCtrl* pList);
void InvalidateSearch();
// 当前命中行的列表索引,无命中返回 -1纯内存读
int GetHighlightedRow() const {
if (m_nCurrentIndex < 0 || m_nCurrentIndex >= (int)m_Results.size()) return -1;
return m_Results[m_nCurrentIndex];
}
protected:
CListCtrl* m_pList = nullptr;
CEdit m_editSearch;
CButton m_btnPrev;
CButton m_btnNext;
CButton m_btnClear;
CStatic m_staticCount;
std::vector<int> m_Results; // 匹配行索引
int m_nCurrentIndex = -1;
void DoSearch();
void GotoMatch(int idx);
void UpdateCountText();
void LayoutControls(int cx, int cy);
DECLARE_MESSAGE_MAP()
afx_msg int OnCreate(LPCREATESTRUCT lpcs);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnEnChangeSearch();
afx_msg void OnBnClickedPrev();
afx_msg void OnBnClickedNext();
afx_msg void OnBnClickedClear();
static const UINT TIMER_SEARCH = 1;
static const UINT SEARCH_DELAY_MS = 400;
};

View File

@@ -1958,3 +1958,10 @@ FRPC Զ
客户端日志=Client Log 客户端日志=Client Log
封禁使用=Forbidden Client 封禁使用=Forbidden Client
确定封禁选定的被控程序吗?=Are you sure to forbidden the selected clients? 确定封禁选定的被控程序吗?=Are you sure to forbidden the selected clients?
无结果=Not found
严禁=Prohibited
禁止=Forbidden
失败=Failed
搜索日志=Search Logs
隐藏搜索=Hide Search
=Please

View File

@@ -1949,3 +1949,10 @@ FRPC Զ
客户端日志=客户端日志 客户端日志=客户端日志
封禁使用=封禁使用 封禁使用=封禁使用
确定封禁选定的被控程序吗?=确定封禁选定的被控程序吗? 确定封禁选定的被控程序吗?=确定封禁选定的被控程序吗?
无结果=无结果
严禁=严禁
禁止=禁止
失败=失败
搜索日志=搜索日志
隐藏搜索=隐藏搜索
=

View File

@@ -990,6 +990,7 @@
#define ID_MSGLOG_CLEAR 33041 #define ID_MSGLOG_CLEAR 33041
#define ID_CANCEL_SHARE 33042 #define ID_CANCEL_SHARE 33042
#define ID_MSGLOG_COPY 33043 #define ID_MSGLOG_COPY 33043
#define ID_MSGLOG_SEARCH 33078
#define ID_WEB_REMOTE_CONTROL 33044 #define ID_WEB_REMOTE_CONTROL 33044
#define ID_TOOL_PLUGIN_SETTINGS 33045 #define ID_TOOL_PLUGIN_SETTINGS 33045
#define ID_33046 33046 #define ID_33046 33046
@@ -1031,7 +1032,7 @@
#ifdef APSTUDIO_INVOKED #ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 398 #define _APS_NEXT_RESOURCE_VALUE 398
#define _APS_NEXT_COMMAND_VALUE 33077 #define _APS_NEXT_COMMAND_VALUE 33079
#define _APS_NEXT_CONTROL_VALUE 2542 #define _APS_NEXT_CONTROL_VALUE 2542
#define _APS_NEXT_SYMED_VALUE 105 #define _APS_NEXT_SYMED_VALUE 105
#endif #endif