我想可靠地模拟用户对其他窗口的输入。我为此使用了 SendInput,但是我需要等到目标应用程序处理完输入后再发送更多。据我所知,SendInput,尽管它的名字,实际上是将消息发布到队列并且不会等到它们被处理。
我的尝试是基于等待消息队列至少一次为空的想法。由于我无法直接检查其他线程的消息队列(至少我不知道这样做的方法),我正在使用 AttachThreadInput 将目标线程的队列附加到该线程的队列,然后 PeekMessage 进行检查。
为了检查功能,我使用了带有一个窗口和一个按钮的小应用程序。单击按钮时,我调用 Thread.Sleep(15000) 有效地停止消息处理,从而确保接下来的 15 秒消息队列不会为空。
代码在这里:
public static void WaitForWindowInputIdle(IntPtr hwnd)
{
var currentThreadId = GetCurrentThreadId();
var targetThreadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero);
Func<bool> checkIfMessageQueueIsEmpty = () =>
{
bool queueEmpty;
bool threadsAttached = false;
try
{
threadsAttached = AttachThreadInput(targetThreadId, currentThreadId, true);
if (threadsAttached)
{
NativeMessage nm;
queueEmpty = !PeekMessage(out nm, hwnd, 0, 0, RemoveMsg.PM_NOREMOVE | RemoveMsg.PM_NOYIELD);
}
else
throw new ThreadStateException("AttachThreadInput failed.");
}
finally
{
if (threadsAttached)
AttachThreadInput(targetThreadId, currentThreadId, false);
}
return queueEmpty;
};
var timeout = TimeSpan.FromMilliseconds(15000);
var retryInterval = TimeSpan.FromMilliseconds(500);
var start = DateTime.Now;
while (DateTime.Now - start < timeout)
{
if (checkIfMessageQueueIsEmpty()) return;
Thread.Sleep(retryInterval);
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PeekMessage(out NativeMessage lpMsg,
IntPtr hWnd,
uint wMsgFilterMin,
uint wMsgFilterMax,
RemoveMsg wRemoveMsg);
[StructLayout(LayoutKind.Sequential)]
public struct NativeMessage
{
public IntPtr handle;
public uint msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}
[Flags]
private enum RemoveMsg : uint
{
PM_NOREMOVE = 0x0000,
PM_REMOVE = 0x0001,
PM_NOYIELD = 0x0002,
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();
现在,由于某种原因它无法正常工作。它总是返回消息队列为空。有人知道我做错了什么,或者可能有其他方法可以实现我的需要吗?
编辑:关于为什么我首先需要等待。 如果不间断地立即模拟其他 Action ,我会遇到只输入部分文本的情况。例如。当焦点位于某个文本框时,我通过 SendInput 模拟了“abcdefgh”,紧接着 - 一些鼠标点击。我得到的是输入“abcde”然后点击。如果我将 Thread.Sleep(100) 放在 SendInput 之后 - 该问题在我的机器上不可重现,但在硬件较低的 VM 上很少重现。所以我需要更可靠的方法来等待正确的时间。
我对可能发生的事情的猜测与 TranslateMessage function 有关:
Translates virtual-key messages into character messages. The character messages are posted to the calling thread's message queue, to be read the next time the thread calls the GetMessage or PeekMessage function.
因此,我为“abcdefgh”调用 SendInput - 发布到线程队列的一堆输入消息。然后它开始以 FIFO 顺序处理这些消息,翻译“abcde”,并将每个字符的消息发布到队列的尾部。然后模拟鼠标点击并在“abcde”的字符消息后发布。然后翻译完成,但是“fgh”的翻译消息发生在鼠标单击之后。最后应用程序看到“abcde”,然后点击,然后是“fgh”——显然去错了地方……
最佳答案
这是 UI 自动化中的常见需求。它实际上是由 WindowPattern.WaitForInputIdle() method 在 .NET 中实现的.
如果使用 System.Windows.Automation 命名空间来实现它,您会过得很好。然而,该方法很容易自己实现。您可以从引用源或反编译器中查看。他们是如何做到的让我有点惊讶,但它看起来很坚固。它不会尝试猜测消息队列是否为空,而只是查看拥有该窗口的 UI 线程的状态。如果它被阻塞并且它没有等待内部系统操作,那么你有一个非常强烈的信号表明线程正在等待 Windows 传递下一条消息。我是这样写的:
using namespace System.Diagnostics;
...
public static bool WaitForInputIdle(IntPtr hWnd, int timeout = 0) {
int pid;
int tid = GetWindowThreadProcessId(hWnd, out pid);
if (tid == 0) throw new ArgumentException("Window not found");
var tick = Environment.TickCount;
do {
if (IsThreadIdle(pid, tid)) return true;
System.Threading.Thread.Sleep(15);
} while (timeout > 0 && Environment.TickCount - tick < timeout);
return false;
}
private static bool IsThreadIdle(int pid, int tid) {
Process prc = System.Diagnostics.Process.GetProcessById(pid);
var thr = prc.Threads.Cast<ProcessThread>().First((t) => tid == t.Id);
return thr.ThreadState == ThreadState.Wait &&
thr.WaitReason == ThreadWaitReason.UserRequest;
}
[System.Runtime.InteropServices.DllImport("User32.dll")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int pid);
在调用 SendInput() 之前在您的代码中调用 WaitForInputIdle()。您必须传递的窗口句柄非常灵活,任何 窗口句柄都可以,只要它属于进程的 UI 线程。 Process.MainWindowHandle 已经是一个很好的候选者。请注意,如果进程终止,该方法将抛出异常。
关于c# - 是否可以等到其他线程处理输入消息发布到它?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21028326/
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html
这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife
我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("