Feature: Add log search bar with prev/next/clear for m_CList_Message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
yuanyuanxiang
2026-07-15 19:22:55 +02:00
parent f24e7acc48
commit b97f086c0c
8 changed files with 407 additions and 12 deletions

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 清空结果
}