将 Ctrl+C 发送到上一个活动 window
Send Ctrl+C to previous active window
我正在尝试编写一个位于系统托盘中的小应用程序。我已经注册了一个热键。当触发热键并激活应用程序时,我想将 Ctrl+C 发送到最后一个活动 window,这样我就可以将突出显示的文本放入剪贴板。
这是我目前得到的:
//
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
static extern IntPtr GetLastActivePopup(IntPtr hWnd);
[DllImport("user32.dll", ExactSpelling = true)]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
const uint GA_PARENT = 1;
const uint GA_ROOT = 2;
const uint GA_ROOTOWNER = 3;
public static IntPtr GetPreviousWindow()
{
IntPtr activeAppWindow = GetForegroundWindow();
if (activeAppWindow == IntPtr.Zero)
return IntPtr.Zero;
IntPtr prevAppWindow = GetLastActivePopup(activeAppWindow);
return IsWindowVisible(prevAppWindow) ? prevAppWindow : IntPtr.Zero;
}
public static void FocusToPreviousWindow()
{
IntPtr prevWindow = GetPreviousWindow();
if (prevWindow != IntPtr.Zero)
SetForegroundWindow(prevWindow);
}
...
private static void OnHotKeyFired()
{
FocusToPreviousWindow();
SendKeys.SendWait("^(c)");
_viewModel.Input = Clipboard.GetText();
new UIWindow(_viewModel).ShowDialog();
}
但是我无法让 SendKeys 工作。在大多数应用程序中,什么都没有发生,这意味着 ctrl-c 不会被触发。在 Ms Word 中,当执行 SendWait 时,我的文档中插入了一个版权符号 (c)。
更新:
我试过 WM_COPY 和 SendMessage:
private static void OnHotKeyFired()
{
IntPtr handle = GetPreviousWindow();
SendMessage(handle, WM_COPY, IntPtr.Zero, IntPtr.Zero);
_viewModel.Input = Clipboard.GetText();
...
它在 Word 中有效,但在 Excel、记事本、Visual Studio
中无效
SendKeys.SendWait("^(c)");
那不送Ctrl+C,括号也送。您的意思可能是 "^{c}"
,花括号而不是圆括号。否则,由于 Word 插入版权符号的原因,它会自动将 (c) 更正为 ©。修正:
SendKeys.SendWait("^c");
如果您仍然遇到问题,您可能会遇到问题,然后阅读有关 SendKeys 的 MSDN 文章。它建议使用 .config 文件,以便使用不同的方式来注入击键。 SendInput() 在以后的 Windows 版本中比日志挂钩更好用。
我知道您正在编写一些程序,但如果您这样做是因为找不到更简单的解决方案...
您可以尝试 AutoHotKey,它是一个小程序,位于您的托盘中,可以执行各种 hotkeys/macros 的操作...
您需要为您的宏编写一个小脚本,类似于:
#c::Send !{ESC}, ^c
这个简单的脚本意味着:
- #c::(定义热键Windows+c)
- 发送(发送一些击键)
- !{ESC}(Alt+Esc 切换到最后一个程序)
- ^c (Control+c)
您可以使用此软件执行各种奇特的脚本编写,例如启动程序、正则表达式、线程、鼠标事件等等...
我知道它不能回答您的问题,但这是一个简单的替代方法。
我认为您的问题与时间有关。
要设置 windows 焦点并实际复制到剪贴板,您需要等待 window 获得焦点,并等待剪贴板更新。
有几种方法可以处理这些丑陋的 win32 问题。
用于剪贴板内容。我将原始内容与当前内容进行比较。如果它是图像或其他一些 none 文本数据,我将原始内容设置为 string.empty。然后我等待检查剪贴板更改的功能。
对于 SetForegroundWindow,我目前在异步函数中添加了延迟。您也可能会找到一个 win32 api 调用来等待此 window 正确地放在前台。
我在异步函数中执行了这两个操作并等待它以免阻塞。
SendKeys 应该与 SendWait("^c") 一起使用。 SendWait("^(c)") 并不总是像其他答案中指出的那样工作。然而,ctrl+c 复制到剪贴板不会立即发生。
Point p;
if (GetCursorPos(out p))
{
IntPtr ptr = WindowFromPoint(p);
if (ptr != IntPtr.Zero)
{
SetForegroundWindow(ptr);
//wait for window to get focus quick and ugly
// probably a cleaner way to wait for windows to send a message
// that it has updated the foreground window
await Task.Delay(300);
//try to copy text in the current window
SendKeys.Send("^c");
await WaitForClipboardToUpdate(originalClipboardText);
}
}
}
private static async Task WaitForClipboardToUpdate(string originalClipboardText)
{
Stopwatch sw = Stopwatch.StartNew();
while (true)
{
if (await DoClipboardCheck(originalClipboardText)) return;
if (sw.ElapsedMilliseconds >= 1500) throw new Exception("TIMED OUT WAITING FOR CLIPBOARD TO UPDATE.");
}
}
private static async Task<bool> DoClipboardCheck(string originalClipboardText)
{
await Task.Delay(10);
if (!Clipboard.ContainsText()) return false;
var currentText = Clipboard.GetText();
Debug.WriteLine("current text: " + currentText + " original text: " + originalClipboardText);
return currentText != originalClipboardText;
}
[DllImport("user32.dll")]
private static extern bool GetCursorPos(out Point pt);
[DllImport("user32.dll", EntryPoint = "WindowFromPoint", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr WindowFromPoint(Point pt);
// Activate an application window.
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImportAttribute("user32.dll", EntryPoint = "GetForegroundWindow")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
此外 - 如果时间问题是您的问题,那么快速 validate/check 的真正快速而肮脏的方法是您可以放置 Thread.Sleep(1000);在您的 SetForegroundWindow 和 SendKeys.SendWait 调用之后。
我正在尝试编写一个位于系统托盘中的小应用程序。我已经注册了一个热键。当触发热键并激活应用程序时,我想将 Ctrl+C 发送到最后一个活动 window,这样我就可以将突出显示的文本放入剪贴板。
这是我目前得到的:
//
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
static extern IntPtr GetLastActivePopup(IntPtr hWnd);
[DllImport("user32.dll", ExactSpelling = true)]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
const uint GA_PARENT = 1;
const uint GA_ROOT = 2;
const uint GA_ROOTOWNER = 3;
public static IntPtr GetPreviousWindow()
{
IntPtr activeAppWindow = GetForegroundWindow();
if (activeAppWindow == IntPtr.Zero)
return IntPtr.Zero;
IntPtr prevAppWindow = GetLastActivePopup(activeAppWindow);
return IsWindowVisible(prevAppWindow) ? prevAppWindow : IntPtr.Zero;
}
public static void FocusToPreviousWindow()
{
IntPtr prevWindow = GetPreviousWindow();
if (prevWindow != IntPtr.Zero)
SetForegroundWindow(prevWindow);
}
...
private static void OnHotKeyFired()
{
FocusToPreviousWindow();
SendKeys.SendWait("^(c)");
_viewModel.Input = Clipboard.GetText();
new UIWindow(_viewModel).ShowDialog();
}
但是我无法让 SendKeys 工作。在大多数应用程序中,什么都没有发生,这意味着 ctrl-c 不会被触发。在 Ms Word 中,当执行 SendWait 时,我的文档中插入了一个版权符号 (c)。
更新:
我试过 WM_COPY 和 SendMessage:
private static void OnHotKeyFired()
{
IntPtr handle = GetPreviousWindow();
SendMessage(handle, WM_COPY, IntPtr.Zero, IntPtr.Zero);
_viewModel.Input = Clipboard.GetText();
...
它在 Word 中有效,但在 Excel、记事本、Visual Studio
中无效 SendKeys.SendWait("^(c)");
那不送Ctrl+C,括号也送。您的意思可能是 "^{c}"
,花括号而不是圆括号。否则,由于 Word 插入版权符号的原因,它会自动将 (c) 更正为 ©。修正:
SendKeys.SendWait("^c");
如果您仍然遇到问题,您可能会遇到问题,然后阅读有关 SendKeys 的 MSDN 文章。它建议使用 .config 文件,以便使用不同的方式来注入击键。 SendInput() 在以后的 Windows 版本中比日志挂钩更好用。
我知道您正在编写一些程序,但如果您这样做是因为找不到更简单的解决方案... 您可以尝试 AutoHotKey,它是一个小程序,位于您的托盘中,可以执行各种 hotkeys/macros 的操作... 您需要为您的宏编写一个小脚本,类似于:
#c::Send !{ESC}, ^c
这个简单的脚本意味着:
- #c::(定义热键Windows+c)
- 发送(发送一些击键)
- !{ESC}(Alt+Esc 切换到最后一个程序)
- ^c (Control+c)
您可以使用此软件执行各种奇特的脚本编写,例如启动程序、正则表达式、线程、鼠标事件等等...
我知道它不能回答您的问题,但这是一个简单的替代方法。
我认为您的问题与时间有关。
要设置 windows 焦点并实际复制到剪贴板,您需要等待 window 获得焦点,并等待剪贴板更新。
有几种方法可以处理这些丑陋的 win32 问题。
用于剪贴板内容。我将原始内容与当前内容进行比较。如果它是图像或其他一些 none 文本数据,我将原始内容设置为 string.empty。然后我等待检查剪贴板更改的功能。
对于 SetForegroundWindow,我目前在异步函数中添加了延迟。您也可能会找到一个 win32 api 调用来等待此 window 正确地放在前台。
我在异步函数中执行了这两个操作并等待它以免阻塞。
SendKeys 应该与 SendWait("^c") 一起使用。 SendWait("^(c)") 并不总是像其他答案中指出的那样工作。然而,ctrl+c 复制到剪贴板不会立即发生。
Point p;
if (GetCursorPos(out p))
{
IntPtr ptr = WindowFromPoint(p);
if (ptr != IntPtr.Zero)
{
SetForegroundWindow(ptr);
//wait for window to get focus quick and ugly
// probably a cleaner way to wait for windows to send a message
// that it has updated the foreground window
await Task.Delay(300);
//try to copy text in the current window
SendKeys.Send("^c");
await WaitForClipboardToUpdate(originalClipboardText);
}
}
}
private static async Task WaitForClipboardToUpdate(string originalClipboardText)
{
Stopwatch sw = Stopwatch.StartNew();
while (true)
{
if (await DoClipboardCheck(originalClipboardText)) return;
if (sw.ElapsedMilliseconds >= 1500) throw new Exception("TIMED OUT WAITING FOR CLIPBOARD TO UPDATE.");
}
}
private static async Task<bool> DoClipboardCheck(string originalClipboardText)
{
await Task.Delay(10);
if (!Clipboard.ContainsText()) return false;
var currentText = Clipboard.GetText();
Debug.WriteLine("current text: " + currentText + " original text: " + originalClipboardText);
return currentText != originalClipboardText;
}
[DllImport("user32.dll")]
private static extern bool GetCursorPos(out Point pt);
[DllImport("user32.dll", EntryPoint = "WindowFromPoint", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr WindowFromPoint(Point pt);
// Activate an application window.
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImportAttribute("user32.dll", EntryPoint = "GetForegroundWindow")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
此外 - 如果时间问题是您的问题,那么快速 validate/check 的真正快速而肮脏的方法是您可以放置 Thread.Sleep(1000);在您的 SetForegroundWindow 和 SendKeys.SendWait 调用之后。