2025-11-28 00:35:46 +09:00

290 lines
9.5 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
// 1) define the GROUPID enum
// enum GROUPID = {GROUPID_DEFAULT, GROUPID_1, GROUPID_2, ... };
// 2) define the group mapping structure
// typedef struct
// {
// GROUPID groupid;
// PCWSTR pszGroupName;
// } LOG_GROUP;
//
// 3) declare c_rgGroupInfo[]
// const LOG_GROUP c_rgGroupInfo[] = { };
// 4) declare the member var that represents the log window
// CLogWindow<LOG_GROUP, GROUPID> _logWindow
// 5) in the constructor of your main window construct call CLogWindow's constructor
// _logWindow(c_rgGroupInfo, ARRAYSIZE(c_rgGroupInfo))
// 6) in WM_INITDIALOG call _logWindow.InitListView(GetDlgItem(_hdlg, IDC_LISTVIEW))
// 7) in your .RC file add the definition of the listview window control
// CONTROL "",IDC_LISTVIEW,"SysListView32",WS_CLIPCHILDREN | WS_TABSTOP, 7,7,281,191
// 8) to enable the right click menu in the log window forward the WM_NOTIFY message
// to CLogWindow list this
// case WM_NOTIFY:
// {
// NMHDR *pnm = (NMHDR*)lParam;
// if (pnm->idFrom == IDC_LISTVIEW)
// {
// _logWindow.OnNotify(pnm);
// }
// }
// break;
template <class TGroupIDMap, class TGroupID> class CLogWindow
{
public:
CLogWindow(const TGroupIDMap *pGroupInfo, UINT cGroupInfo) :
_pGroupInfo(pGroupInfo), _cGroupInfo(cGroupInfo), _fDebugOutput(false)
{
}
CLogWindow & operator=(const CLogWindow &)
{
// User-defined assignment operator is necessary for /W4 /WX since a default
// one cannot be created by the compiler because this class contains const members.
}
void InitListView(HWND hwndList)
{
_hwndList = hwndList;
// Enable ListView for Grouping mode.
SetWindowLongPtr(_hwndList, GWL_STYLE, GetWindowLongPtr(_hwndList, GWL_STYLE) |
LVS_REPORT | LVS_NOSORTHEADER | LVS_SHOWSELALWAYS);
ListView_SetExtendedListViewStyle(_hwndList, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER);
ListView_EnableGroupView(_hwndList, TRUE);
// Setup up common values.
LVCOLUMN lvc = {};
lvc.mask = LVCF_TEXT | LVCF_SUBITEM | LVCF_FMT; // Links are not active in this dialog, so just show text without markup
lvc.fmt = LVCFMT_LEFT;
lvc.pszText = L"Name";
// Add Column 0
lvc.iSubItem = 0;
ListView_InsertColumn(_hwndList, 0, &lvc);
// Add Column 1
lvc.iSubItem = 1;
lvc.pszText = L"Value";
ListView_InsertColumn(_hwndList, 1, &lvc);
AutoAdjustListView();
// Init group IDs and display names
for (UINT i = 0; i < _cGroupInfo; i++)
{
LogGroup(_pGroupInfo[i].groupid, _pGroupInfo[i].pszGroupName);
}
}
void AutoAdjustListView()
{
// Auto-adjust the column widths making sure that the first column doesn't
// make itself too big.
ListView_SetColumnWidth(_hwndList, 0, LVSCW_AUTOSIZE_USEHEADER);
RECT rect;
BOOL bRet = GetClientRect(_hwndList, &rect);
if (bRet)
{
LVCOLUMN lvc;
lvc.mask = LVCF_WIDTH;
bRet = ListView_GetColumn(_hwndList, 0, &lvc);
if (bRet)
{
int iSize = rect.right / 2;
int cxScroll = GetSystemMetrics(SM_CXVSCROLL);
if (lvc.cx > iSize)
{
ListView_SetColumnWidth(_hwndList, 0, iSize);
ListView_SetColumnWidth(_hwndList, 1, iSize - cxScroll);
}
else
{
ListView_SetColumnWidth(_hwndList, 1, rect.right - lvc.cx - cxScroll);
}
}
}
if (!bRet)
{
ListView_SetColumnWidth(_hwndList, 1, LVSCW_AUTOSIZE_USEHEADER);
}
}
void LogGroup(TGroupID groupid, PCWSTR pszGroupName)
{
LVGROUP lvg = {};
lvg.cbSize = sizeof(lvg);
lvg.mask = LVGF_HEADER | LVGF_GROUPID | LVGF_STATE;
lvg.state = LVGS_COLLAPSIBLE;
int iGroupID = (int)groupid;
if (groupid == GROUPID_DEFAULT)
{
iGroupID = (int)ListView_GetGroupCount(_hwndList);
}
lvg.iGroupId = iGroupID;
lvg.pszHeader = const_cast<PWSTR>(pszGroupName);
ListView_InsertGroup(_hwndList, -1, &lvg);
}
void LogMessage(TGroupID groupid, PCWSTR pszName, PCWSTR pszBuf)
{
// Add an item name
LVITEM lvi = {};
lvi.mask = LVIF_TEXT | LVIF_GROUPID;
lvi.iItem = MAXLONG;
int iGroupID = (int)groupid;
if (groupid == GROUPID_DEFAULT)
{
iGroupID = (int)ListView_GetGroupCount(_hwndList) - 1; // groups are numbered 0, 1, ... n-1
}
lvi.iGroupId = iGroupID;
lvi.pszText = const_cast<PWSTR>(pszName);
int iItem = ListView_InsertItem(_hwndList, &lvi);
if (-1 != iItem)
{
// Add the formatted value.
ListView_SetItemText(_hwndList, iItem, 1, const_cast<PWSTR>(pszBuf));
if (_fDebugOutput)
{
OutputDebugString(pszName);
OutputDebugString(L"\t");
OutputDebugString(pszBuf);
OutputDebugString(L"\r\n");
}
}
}
void LogMessagePrintf(TGroupID groupid, PCWSTR pszName, PCWSTR pszFormatString, ...)
{
va_list argList;
va_start(argList, pszFormatString);
WCHAR szBuf[512];
HRESULT hr = StringCchVPrintf(szBuf, ARRAYSIZE(szBuf), pszFormatString, argList);
if (SUCCEEDED(hr))
{
LogMessage(groupid, pszName, szBuf);
}
va_end(argList);
}
void ResetContents()
{
ListView_DeleteAllItems(_hwndList);
}
void SetDebugOutput(bool fDebugOutput)
{
_fDebugOutput = fDebugOutput;
}
PWSTR GetText(bool fSelectionOnly)
{
size_t const cchAlloc = 64 * 1024; // fixed size buffer for simplicity of impl
PWSTR pszResult = (PWSTR)GlobalAlloc(GPTR, cchAlloc * sizeof(*pszResult));
if (pszResult)
{
PWSTR psz = pszResult; // accumulate results using this pointer
size_t cch = cchAlloc; // size left in buffer
const int itemCount = ListView_GetItemCount(_hwndList);
for (int i = 0; i < itemCount; i++)
{
if (fSelectionOnly ? (LVIS_SELECTED == ListView_GetItemState(_hwndList, i, LVIS_SELECTED)) : true)
{
LVCOLUMN column = {LVCF_WIDTH}; // query a dummy value so we can probe for columns presence
for (int j = 0; ListView_GetColumn(_hwndList, j, &column); j++)
{
WCHAR szBuffer[512];
LV_ITEM item;
item.iItem = i;
item.iSubItem = j;
item.mask = LVIF_TEXT;
item.pszText = szBuffer;
item.cchTextMax = ARRAYSIZE(szBuffer);
if (ListView_GetItem(_hwndList, &item))
{
if (j)
{
StringCchCatEx(psz, cch, L"\t", &psz, &cch, 0);
}
StringCchCatEx(psz, cch, szBuffer, &psz, &cch, 0);
}
}
StringCchCatEx(psz, cch, L"\r\n", &psz, &cch, 0);
}
}
}
return pszResult;
}
void CopyTextToClipboard(bool fSelectionOnly, HWND hwnd)
{
PWSTR psz = GetText(fSelectionOnly);
if (psz)
{
HRESULT hr = OpenClipboard(hwnd) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
EmptyClipboard();
hr = (SetClipboardData(CF_UNICODETEXT, psz) == psz) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
psz = NULL; // ownership transfered to clipboard
}
CloseClipboard();
}
GlobalFree(psz);
}
}
void OnNotify(NMHDR *pnm)
{
if (pnm->code == NM_RCLICK)
{
NMITEMACTIVATE *pnmItem = (NMITEMACTIVATE *)pnm;
POINT ptMenu = pnmItem->ptAction;
ClientToScreen(pnmItem->hdr.hwndFrom, &ptMenu);
HMENU hMenu = CreatePopupMenu();
if (hMenu)
{
AppendMenu(hMenu, MF_ENABLED, 1, L"Copy");
AppendMenu(hMenu, MF_ENABLED, 2, L"Copy All");
int idCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, ptMenu.x, ptMenu.y, 0, _hwndList, NULL);
if (idCmd == 1)
{
CopyTextToClipboard(true, GetParent(_hwndList));
}
else if (idCmd == 2)
{
CopyTextToClipboard(false, GetParent(_hwndList));
}
DestroyMenu(hMenu);
}
}
}
private:
HWND _hwndList;
const TGroupIDMap *_pGroupInfo;
const UINT _cGroupInfo;
bool _fDebugOutput;
};