У меня есть приложение, которое использует отложенный рендеринг буфера обмена для вставки текущего времени. Но: когда время было визуализировано один раз, оно всегда будет вставлять одно и то же время, а не обновленное время.
Думаю, мне придется снова вызвать SetClipboardData(CF_TEXT, nullptr);
, чтобы восстановить еще один случай отложенного рендеринга, но я не знаю, когда и где мне это сделать. Могу ли я определить, когда целевое приложение взяло данные из буфера обмена?
Как я могу вставлять текущее время каждый раз, когда пользователь нажимает Ctrl+V?
#include <windows.h>
#include <thread>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
// Get current time with milliseconds (HH:MM:SS.xxx)
void GetTime(std::string& time_str)
{
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
std::tm bt{};
localtime_s(&bt, &in_time_t);
std::ostringstream oss;
oss << std::put_time(&bt, "%H:%M:%S") << '.' << std::setfill('0') << std::setw(3) << milliseconds.count();
time_str = oss.str();
}
void RenderClipboardData(HWND hwnd) {
std::string time_str;
GetTime(time_str);
EmptyClipboard();
// Allocate global memory for the clipboard data
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, time_str.size() + 1);
if (hGlobal) {
// Lock the global memory and copy the string into it
void* pGlobal = GlobalLock(hGlobal);
if (pGlobal) {
memcpy(pGlobal, time_str.c_str(), time_str.size() + 1);
GlobalUnlock(hGlobal);
SetClipboardData(CF_TEXT, hGlobal);
}
else
{
// Free the global memory if it wasn't successfully set
GlobalFree(hGlobal);
}
}
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_RENDERFORMAT:
if (wParam == CF_TEXT) {
RenderClipboardData(hwnd);
}
break;
case WM_CREATE:
CreateWindow(
L"STATIC",
L"This application pastes the current time on Ctrl+V. ",
WS_VISIBLE | WS_CHILD,
10, 10, 600, 100,
hwnd,
nullptr,
reinterpret_cast<LPCREATESTRUCT>(lParam)->hInstance,
nullptr);
if (OpenClipboard(hwnd)) {
EmptyClipboard();
SetClipboardData(CF_TEXT, nullptr); // Delayed rendering to get the current time
CloseClipboard();
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {
constexpr wchar_t CLASS_NAME[] = L"SampleWindowClass";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Delayed Clipboard Rendering", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, 640, 120,
nullptr, // Parent window
nullptr, // Menu
hInstance, // Instance handle
nullptr // Additional application data
);
if (hwnd == nullptr) {
return 0;
}
ShowWindow(hwnd, nCmdShow);
MSG msg = {};
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Я пытался:
WM_DESTROYCLIPBOARD
и сброс настроек буфера обмена для отложенного рендеринга.@BitTickler: возможно, это должно объяснять это, но, ИМХО, нет. Я пробовал реализовать WM_DESTROYCLIPBOARD, но это не имеет значения.
Во-первых, НЕ следует вызывать EmptyClipboard()
при рендеринге текста. Другое приложение, которому нужен текст, уже открыло буфер обмена, поэтому вам нужно просто поместить текст в буфер обмена и больше ничего не делать.
Теперь, чтобы выполнить то, что вы хотите, вы можете сделать так, чтобы ваш обработчик WM_RENDERFORMAT
отправлял специальное сообщение в ваше окно после рендеринга данных, а затем запускал новую задержку рендеринга из этого обработчика сообщения, например:
bool SetClipboardDataDelayRendered(HWND hwnd) {
if (!OpenClipboard(hwnd)) return false;
EmptyClipboard();
SetClipboardData(CF_TEXT, nullptr); // Delayed rendering to get the current time
CloseClipboard();
return true;
}
void RenderClipboardData(HWND hwnd) {
...
// DO NOT DO THIS HERE!
// EmptyClipboard();
...
}
static const UINT WM_SET_CB_DELAY_RENDERED = WM_USER + 100;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE:
...
SendMessage(hwnd, WM_SET_CB_DELAY_RENDERED, 0, 0);
break;
case WM_SET_CB_DELAY_RENDERED:
// keep trying until the clipboard can be opened...
if (!SetClipboardDataDelayRendered(hwnd))
PostMessage(hwnd, WM_SET_CB_DELAY_RENDERED, 0, 0);
break;
case WM_RENDERFORMAT:
if (wParam == CF_TEXT) {
RenderClipboardData(hwnd);
// the other app has the clipboard open,
// wait for it to close before resetting it...
PostMessage(hwnd, WM_SET_CB_DELAY_RENDERED, 0, 0);
}
break;
...
}
return 0;
}
При этом имейте в виду, что после того, как вы запросите отложенный рендеринг ваших данных, другое приложение может прийти и заменить содержимое буфера обмена своими собственными данными, поэтому ваш отложенный рендеринг перестанет работать. Вы можете обнаружить это состояние, обработав WM_DESTROYCLIPBOARD
и/или используя GetClipboardOwner()
, чтобы убедиться, что ваш HWND
по-прежнему является владельцем. В этом случае вам может потребоваться использовать таймер или другой механизм для перезапуска отложенного рендеринга.
Потрясающий. Большое спасибо. Работает так, как я хотел.
Скорее грубая сила. Я не уверен, должно ли это быть решением по умолчанию. Голосовать не буду, но сомнения полны ;)
@BitTickler, очевидно, это не исчерпывающее решение. Возможно, потребуется дополнительная работа. Это необычное использование буфера обмена. Я не рассмотрел все возможные сценарии.