简体   繁体   English

在线程中更改GUI

[英]Change GUI in thread

I have an operation which ends in about 20 seconds. 我有一个操作,大约20秒结束。 To avoid freezing, I want to create a thread and update a label text in it every second. 为了避免冻结,我想创建一个线程并每秒更新一个标签文本。 I searched a lot, since everyone has different opinion, I couldn't decide which method to use. 我搜索了很多,因为每个人都有不同的意见,我无法决定使用哪种方法。

I tried SendMessage and it works but some people believe that using SendMessage is not safe and I should use PostMessage instead. 我尝试使用SendMessage并且它有效,但有些人认为使用SendMessage并不安全,我应该使用PostMessage。 But PostMessage fails with ERROR_MESSAGE_SYNC_ONLY (1159). 但是PostMessage因ERROR_MESSAGE_SYNC_ONLY而失败(1159)。

char text[20] = "test text";
SendMessage(label_hwnd, WM_SETTEXT, NULL, text);

I searched about this and I think it's because of using pointers in PostMessage which is not allowed. 我搜索了这个,我认为这是因为在PostMessage中使用指针是不允许的。 That's why it fails. 这就是它失败的原因。

So, what should I do? 所以我该怎么做? I'm confused. 我糊涂了。 What do you suggest? 你有什么建议? Is this method is good for change UI elements in other thread? 这种方法是否适合更改其他线程中的UI元素?

Thanks 谢谢

Well, the error says it all. 好吧,错误说明了一切。 The message cannot be sent asynchronously. 消息无法异步发送。 The thing about PostMessage is that it posts the message to the listening thread's queue and returns immediately, without waiting for the result of message processing. 关于PostMessage的事情是它将消息发布到侦听线程的队列并立即返回,而不等待消息处理的结果。 SendMessage on the other hand, waits until the window procedure finishes processing the message and only then it returns. 另一方面, SendMessage等待窗口过程完成处理消息,然后才返回。

The risk of using PostMessage in your case is that before window procedure processes the message you may have deallocated the string buffer. 在您的情况下使用PostMessage的风险是在窗口过程处理之前您可能已释放字符串缓冲区的消息。 So it is safer to use SendMessage in this instance and that's what MS developers probably thought about when they decided not to allow asynchronous posting of this particular message. 因此,在这个实例中使用SendMessage更安全,这就是MS开发人员在决定不允许异步发布此特定消息时可能会想到的内容。

EDIT: Just to be clear, of course this doesn't eliminate the risk of passing a naked pointer totally. 编辑:要明确,当然这并不能消除完全传递裸指针的风险。

The documentation for ERROR_MESSAGE_SYNC_ONLY says: ERROR_MESSAGE_SYNC_ONLY的文档说:

The message can be used only with synchronous operations. 该消息只能用于同步操作。

This means that you can use synchronous message delivery, ie SendMessage and similar, but you cannot use asynchronous message delivery, ie PostMessage . 这意味着您可以使用同步消息传递,即SendMessage等,但您不能使用异步消息传递,即PostMessage

The reason is that WM_SETTEXT is a message whose parameters include a reference. 原因是WM_SETTEXT是一个参数包含引用的消息。 The parameters cannot be copied by value. 参数不能按值复制。 If you could deliver WM_SETTEXT asynchronously then how would the system guarantee that the pointer that the recipient window received was still valid? 如果您可以异步传送WM_SETTEXT那么系统如何保证收件人窗口收到的指针仍然有效?

So the system simply rejects your attempt to send this message, and indeed any other message that has parameters that are references. 因此,系统只是拒绝您发送此消息的尝试,实际上拒绝任何其他具有参考参数的消息。

It is reasonable for you to use SendMessage here. 在这里使用SendMessage是合理的。 That will certainly work. 这肯定会奏效。

However, you are forcing your worker thread to block on the UI. 但是,您强制您的工作线程在UI上阻止。 It may take the UI some time to update the caption's text. UI可能需要一些时间来更新标题的文本。 The alternative is to post a custom message to the UI thread that instructs the UI thread to update the UI. 另一种方法是将自定义消息发布到UI线程,指示UI线程更新UI。 Then your worker thread thread can continue its tasks and let the UI thread update in parallel, without blocking the worker thread. 然后,您的工作线程线程可以继续其任务并让UI线程并行更新,而不会阻止工作线程。

In order for that to work you need a way for the UI thread to get the progress information from the worker thread. 为了使其工作,您需要一种方法让UI线程从工作线程获取进度信息。 If the progress is as simple as a percentage then all you need to do is have the worker thread write to, and the UI thread read from, a shared variable. 如果进度像百分比一样简单,那么您需要做的就是让工作线程写入,并从共享变量读取UI线程。

The asynch PostMessage() alternative requires that the lifetime of the data passed in the parameters is extended beyond the message originator function. asynch PostMessage()替代方案要求参数中传递的数据的生命周期超出消息发起方函数。 The 'classic' way of doing that is to heap-allocate the data, PostMessage a pointer to it, handle the data in the message-handler in the usual way and then delete it, (or handle it in some other way such that it does not leak). 这样做的“经典”方法是堆分配数据,PostMessage是指向它的指针,以通常的方式处理消息处理程序中的数据,然后将其删除,(或以其他方式处理它)不泄漏)。 In other words, 'fire and forget' - you must not touch the data in the originating thread after the PostMessage has been issued. 换句话说,“发射并忘记” - 在发布PostMessage后,您不得触摸原始线程中的数据。

The upside is that PostMessage() allows the originating thread to run on 'immediately' and so do further work, (maybe posting more messages). 好处是PostMessage()允许原始线程“立即”运行,因此可以进一步工作(可能发布更多消息)。 SendMessage() and such synchronous comms can get held up if the GUI is busy, imacting overall throughput. 如果GUI忙,则会发送SendMessage()和此类同步通信,从而影响整体吞吐量。

The downside is that a thread may generate mesages faster than the GUI can process them. 缺点是线程可能比GUI可以处理它们更快地生成消息。 This usually manifests to the by laggy GUI responses, especially when performing GUI-intenisve work like moving/resizing windows and updating TreeViews. 这通常表现为滞后的GUI响应,特别是在执行GUI-intenisve工作时,如移动/调整窗口大小和更新TreeViews。 Eventually, the PostMessage call will fail when 10,000+ messages are queued up. 最终,当10,000多条消息排队时,PostMessage调用将失败。 If this is found to be a problem, additional flow-control may have to be added, so further complicating the comms, ( I usually do that by using a fixed-size object pool to block/throttle the originating thread if all available objects are stuck 'in transit' in posted, but unhandled, messages. 如果发现这是一个问题,可能必须添加额外的流量控制,因此进一步使通信复杂化(我通常通过使用固定大小的对象池来阻止/限制原始线程,如果所有可用对象都是在已过帐但未处理的邮件中卡住了“过境”。

From MSDN 来自MSDN

If you send a message in the range below WM_USER to the asynchronous message functions (PostMessage, SendNotifyMessage, and SendMessageCallback), its message parameters cannot include pointers. 如果将WM_USER下面的消息发送到异步消息函数(PostMessage,SendNotifyMessage和SendMessageCallback),则其消息参数不能包含指针。 Otherwise, the operation will fail. 否则,操作将失败。

I think you can use SendMessage safely here. 我想你可以在这里安全地使用SendMessage。 Then you don't need to worry about memory persistence for your string and other issues. 然后,您不必担心字符串和其他问题的内存持久性。 SendMessage is not safe when you send messages from another message handler or send message to blocked GUI thread, but if in your case you know it is safe - just use it 当您从另一个消息处理程序发送消息或向已阻止的GUI线程发送消息时,SendMessage不安全,但如果在您的情况下您知道它是安全的 - 只需使用它

This is not a problem with the PostMessage but a problem with the message you are sending - WM_SETTEXT . 这不是PostMessage的问题,但是您发送的消息存在问题 - WM_SETTEXT First a common misconception is that if you SendMessage() to a control from a thread, it is different from calling GUI API, it is in fact NOT . 首先,一个常见的误解是,如果SendMessage()来自一个线程的控件,它与调用GUI API不同,它实际上不是 When you call a GUI API (from anywhere) for example to set text, windows implement this in the form of SendMessage() call. 当你调用GUI API(从任何地方)例如设置文本时,windows以SendMessage()调用的形式实现它。 So when you are sending the same message, it is essentially same as calling the API. 因此,当您发送相同的消息时,它与调用API基本相同。 Although directly GUI access like this works in many ways it is not recommended. 虽然像这样的GUI直接访问在很多方面都有用,但不建议这样做。 For this reason, I would beg to disagree with the accepted answer by @David. 出于这个原因,我不同意@David接受的回答。

The correct way is (code on the fly) 正确的方法是(代码即时)

char* text = new char[20] 
strcpy_s(text, "test text");
PostMessage(label_hwnd, IDM_MY_MSG_UPDATE_TEXT, NULL, text); 

you will updated the text in your own message IDM_MY_MSG_UPDATE_TEXT handler function and delete the memory. 您将更新自己的消息IDM_MY_MSG_UPDATE_TEXT处理函数中的文本并删除内存。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM