C# - 在单独的线程中安全地拥有一个拥有窗体,是否可行?

5
我想编写一个特殊的屏幕键盘(OSK),用于我正在使用C#编写的应用程序。为了实现这一点,我创建了一个窗体,在上面放置了几个按钮表示键,并且单击它们会调用SendKeys并发送相应的按键。
这个窗体是由在应用程序首次启动时显示的主窗口拥有的,使用Owner属性。这样,每当用户聚焦于应用程序时,OSK就会弹出来,并且如果该主窗口被拖到其上方,则OSK保持在其上方。
这都很好,但因为我还想将模态对话框与OSK一起使用,所以我尝试在单独的线程中创建它,完全具备自己的消息循环(通过Application.Run),以便可以与主线程中的任何模态对话框一起使用。
问题在于,显然,位于单独线程中会导致交叉线程调用引发InvalidOperationException异常。其中一个具体的例子是从新线程调用Application.Run(osk)时,会出现跨线程错误,因为它正在尝试使用所有者(即主窗口)更新窗口的句柄。
我的问题是,是否可能以安全的方式在单独的线程上拥有一个拥有窗体?如果无法实现,是否可能模拟所拥有窗体的特征(即仅对主窗口始终保持在最上方,并在聚焦于主窗口时弹出)?
谢谢,如果这让您感到困惑,我很抱歉。

为什么要在单独的UI线程中使用模态对话框?如果有多个UI线程,每个线程应该有自己的OSK。我不明白为什么需要启动其他线程。 - YWE
我创建的唯一单独线程是带有 OSK 的线程,以便它可以与需要文本输入的模态对话框一起使用。其他所有内容都在原始的主线程上。 - user153498
你使用的是哪个操作系统(以防你正在使用Mono)?这可能会产生影响。 - YWE
4个回答

2

我认为这实际上是Windows Forms中的一个bug。由于它检查Handle属性的访问权限是错误的线程,所以这种情况有些不可避免。SetParent的SDK文档并没有明确说明,它只说明要求两个窗口属于同一应用程序,没有提到必须属于同一线程。我知道,“同一应用程序”的要求并不是硬性的,Windows中有应用兼容代码可以使来自不同进程的窗口正常工作。Adobe Acrobat长期以来一直在滥用这个功能。这绝对可以免除“同一线程”的要求。

好吧,暂时搁置这个问题,试试看。在设置所有者之前将Control.CheckForIllegalCrossThreadCalls设置为false,在之后设置为true。然后进行大量测试。如果遇到问题,请尝试直接调用SetParent()而不是设置Owner。Windows Forms实际上使用了SetWindowLongPtr,这不被SDK推荐。


1
尝试使用ShowDialog替代Application.Run来打开OSK - ShowDialog会创建一个消息循环,并在窗口关闭时结束它,也许可以解决你的问题。
new Thread(() => new OSK().ShowDialog());

1

我想尝试一下这个。尝试将OSK作为一个独立的进程运行。


问题在于我无法理解所拥有的工具窗口的行为,主要是仅对窗体所有者始终置顶,当主窗体获得焦点时,所拥有的窗口也会随之弹出。 - user153498

0

为什么不使用 Control.Invoke 进行跨线程调用,以避免 InvalidOperationException


因为主要错误发生在调用Application.Run时,这是由于OSK的所有者是主窗口引起的。它试图执行一些内部操作,将句柄与所有者关联起来,这会导致跨线程异常。我无法控制这个问题,因为它在System.Windows.Forms中。 - user153498
@nasufara:你在主窗体中使用 Application.Run(new OSK()) 吗? - Cheng Chen
我在主窗口的构造函数中基本上执行new Thread(() => Application.Run(new OSK())).Start();(有一些更多的初始化内容,但这不是相关的,为了简洁起见被省略了)。 - user153498
但是你为什么要执行Application.Run呢?为什么需要将窗体设置为模态? - Jaco Pretorius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接