Quicker 的“脚本动作”中使用 C# 脚本
“点击选窗 → 取标题 → 写入剪贴板”的脚本:
- 运行后会先弹出提示,让你用鼠标左键点击目标窗口(标题栏或内容区都行);
- 自动获取该窗口(沿父链/拥有者链提升到顶层)的标题;若为空会回退到进程主窗口标题;
- 把结果写入剪贴板,并弹窗确认。 兼容旧版 C# 语法:不要加
using Quicker.Public;,入口保持Exec(object context)。 - 一次性返回:标题/路径(如有)、进程名、类名、句柄(十六进制)
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
public class Script
{
[StructLayout(LayoutKind.Sequential)]
public struct POINT { public int X; public int Y; }
[DllImport("user32.dll")] static extern short GetAsyncKeyState(int vKey);
[DllImport("user32.dll")] static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("user32.dll")] static extern IntPtr WindowFromPoint(POINT Point);
[DllImport("user32.dll")] static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")] static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
// 剪贴板 WinAPI
[DllImport("user32.dll")] static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll")] static extern bool EmptyClipboard();
[DllImport("user32.dll")] static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem);
[DllImport("user32.dll")] static extern bool CloseClipboard();
[DllImport("kernel32.dll")] static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);
[DllImport("kernel32.dll")] static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")] static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("kernel32.dll")] static extern IntPtr GlobalFree(IntPtr hMem);
const int VK_LBUTTON = 0x01;
const uint GA_ROOT = 2;
const uint GA_ROOTOWNER = 3;
const uint GMEM_MOVEABLE = 0x0002;
const uint CF_UNICODETEXT = 13;
static string GetClassStr(IntPtr h)
{
StringBuilder cls = new StringBuilder(256);
GetClassName(h, cls, cls.Capacity);
return cls.ToString();
}
static string GetTitleStr(IntPtr h)
{
int len = GetWindowTextLength(h);
StringBuilder sb = new StringBuilder(len > 0 ? len + 8 : 512);
GetWindowText(h, sb, sb.Capacity);
return sb.ToString();
}
static IntPtr PromoteToRootOwner(IntPtr h)
{
IntPtr r = GetAncestor(h, GA_ROOTOWNER);
if (r == IntPtr.Zero) r = GetAncestor(h, GA_ROOT);
return r == IntPtr.Zero ? h : r;
}
static string ProcessNameFromHwnd(IntPtr h)
{
uint pid;
GetWindowThreadProcessId(h, out pid);
try
{
Process p = Process.GetProcessById((int)pid);
return p != null ? p.ProcessName : "";
}
catch { return ""; }
}
static string ProcessMainWindowTitleFromHwnd(IntPtr h)
{
uint pid;
GetWindowThreadProcessId(h, out pid);
try
{
Process p = Process.GetProcessById((int)pid);
return p != null ? p.MainWindowTitle : "";
}
catch { return ""; }
}
// 带重试的剪贴板写入:最多 10 次,每次间隔 100ms
static bool TrySetClipboardUnicodeText(string text, int maxRetry, int delayMs)
{
byte[] data = Encoding.Unicode.GetBytes(text + "\0");
UIntPtr size = (UIntPtr)data.Length;
for (int i = 0; i < maxRetry; i++)
{
IntPtr hGlobal = GlobalAlloc(GMEM_MOVEABLE, size);
if (hGlobal == IntPtr.Zero) return false;
IntPtr ptr = GlobalLock(hGlobal);
if (ptr == IntPtr.Zero)
{
GlobalFree(hGlobal);
return false;
}
try { Marshal.Copy(data, 0, ptr, data.Length); }
finally { GlobalUnlock(hGlobal); }
if (OpenClipboard(IntPtr.Zero))
{
try
{
EmptyClipboard();
IntPtr setRes = SetClipboardData(CF_UNICODETEXT, hGlobal);
if (setRes != IntPtr.Zero)
{
// 成功后,内存归剪贴板所有,不可再 free
return true;
}
else
{
// 失败:我们回收内存
GlobalFree(hGlobal);
}
}
finally { CloseClipboard(); }
}
else
{
// 打不开剪贴板,回收内存,并稍后重试
GlobalFree(hGlobal);
}
Thread.Sleep(delayMs);
}
return false;
}
public static object Exec(object context)
{
// 1) 提示点击
MessageBoxW(IntPtr.Zero, "请用鼠标左键单击要获取标题的窗口(标题栏或内容区)。\n将复制:标题/路径、进程、类名、句柄。", "获取窗口信息 → 复制", 0);
// 2) 等待一次左键按下(最多 5 秒)
int waited = 0;
while (waited < 5000)
{
short state = GetAsyncKeyState(VK_LBUTTON);
if ((state & 0x8000) != 0) break;
Thread.Sleep(10);
waited += 10;
}
Thread.Sleep(120); // 等抬起稳定
// 3) 通过点击点选窗
POINT pt;
if (!GetCursorPos(out pt)) { MessageBoxW(IntPtr.Zero, "无法获取鼠标位置", "获取窗口信息 → 复制", 0); return "无法获取鼠标位置"; }
IntPtr h = WindowFromPoint(pt);
if (h == IntPtr.Zero || !IsWindow(h) || !IsWindowVisible(h))
{ MessageBoxW(IntPtr.Zero, "未找到目标窗口", "获取窗口信息 → 复制", 0); return "未找到目标窗口"; }
// 4) 提升到顶层拥有者窗口
h = PromoteToRootOwner(h);
// 5) 获取标题,必要时回退到进程主窗口标题
string title = GetTitleStr(h);
if (title == null) title = "";
if (title.Length == 0)
{
string t2 = ProcessMainWindowTitleFromHwnd(h);
if (!string.IsNullOrEmpty(t2)) title = t2;
}
if (title.Length == 0) title = "[空标题]";
// 6) 组装你需要的一整行
string proc = ProcessNameFromHwnd(h);
string cls = GetClassStr(h);
string hwndHex = "0x" + h.ToInt64().ToString("X");
string line = "title/path=" + title
+ " | process=" + proc
+ " | class=" + cls
+ " | hwnd=" + hwndHex;
// 7) 写入剪贴板(带重试)
bool ok = TrySetClipboardUnicodeText(line, 10, 100);
// 8) 提示并返回
if (ok)
MessageBoxW(IntPtr.Zero, "已复制到剪贴板:\n" + line, "获取窗口信息 → 复制", 0);
else
MessageBoxW(IntPtr.Zero, "复制失败(剪贴板可能被占用)。\n请重试或关闭占用剪贴板的程序。\n\n内容:\n" + line, "获取窗口信息 → 复制", 0);
return line; // 也作为 Quicker 的“上一步结果”返回
}
}