When using ATL, what is the best way to display the progress of a background thread (e.g. when it's searching for a file) without blocking the UI?
I still want to be able to process messages, to allow for a Cancel button and to possibly allow the user to continue working with the program while the search happens.
There is no ATL specific here. One of the ways to do is to update progress details into member variable and post a message to GUI window, then handle the message by pulling the data from member variable and updating GUI, such as updating static and/or progress bar.
Worker thread pseudo-code:
m_DataCriticalSection.Lock();
m_nProgress = (INT) (nCurrent * 100 / nTotal);
m_DataCriticalSection.Unlock();
PostMessage(WM_MYUPDATEPROGRESS);
Window:
OnMyUpdateProgress()
{
m_DataCriticalSection.Lock();
INT nProgress = m_nProgress;
m_DataCriticalSection.Unlock();
m_ProgressBar.SetPos(nProgress);
}
UPD. A real code snippet, AddText is called on background thread, :
VOID AddText(const CString& sText)
{
_A(sText.Find(_T('\n')) < 0);
BOOL bIsTextEmpty;
{
CRoCriticalSectionLock TextLock(m_TextCriticalSection);
bIsTextEmpty = m_sText.IsEmpty();
m_sText.Append(sText);
m_sText.Append(_T("\r\n"));
}
if(bIsTextEmpty)
PostPrivateMessage(WM_UPDATETEXT);
}
And the code handler:
BEGIN_MSG_MAP_EX(CMainDialog)
// ...
MESSAGE_HANDLER_EX(WM_UPDATETEXT, OnUpdateText)
LRESULT OnUpdateText(UINT, WPARAM, LPARAM)
{
CString sText;
{
CRoCriticalSectionLock TextLock(m_TextCriticalSection);
sText = m_sText;
m_sText.Empty();
}
if(!sText.IsEmpty())
{
m_TextEdit.SetValue(m_TextEdit.GetValue() + sText);
const INT nTextLength = m_TextEdit.GetWindowTextLength();
m_TextEdit.SetSel(nTextLength, nTextLength);
}
return 0;
}
This uses custom classes (not 'pure' ATL), but I hope you get the idea.
Related
I’ve run into a performance obstacle and I’m uncertain of the cause, all of this is running under VS2022 & Net6.0. As this is my 1st time using this combination of a modal windows form, and progress bar, with the work running on a background thread and two Progress objects updating the UI, the progress bar, and a text label, I don’t know where to attack the problem. Prior to placing the workload on a background thread, everything was snappy, searching a thousand files with about 600 lines of text in each, in about a minute. Naturally, the windows form was frozen during this, which is why the workload was placed on a background thread.
After doing so, the workload will be 25-50% complete before the UI starts displaying the values from the Progress objects, and overall, the entire process now takes 10x as long to complete. Progress objects aren’t skipping over any values sent to them, the UI thread just seems slow in getting the information. Likewise, if I try to drag the modal form to a new spot on the desktop it’s unresponsive for 20—30 seconds before it finally moves. One more thing, I can step through the code on the background thread and see it calling the Progress updaters, but the UI thread is just very slow in responding to them.
I could use some suggestions on how to uncover the problem or if clearly evident, point out where the likely problem could be. Here are the essential controls and methods used.
public class SearchProgressForm : Form
{
private System.Windows.Forms.Button btnSearch = new Button();
private System.Windows.Forms.TextBox txtTextSearch = new TextBox();
private System.Windows.Forms.Label lblSearchFile = new Label();
private System.Windows.Forms.ProgressBar SearchProgressBar = new ProgressBar();
public event LogSearchEventHandler SearchSucceededEvent;
protected void OnSearchSucceeded(LogSearchEventArguments p_eventArguments)
{
LogSearchEventHandler handler = SearchSucceededEvent;
if (handler != null)
{
handler(this, p_eventArguments);
}
}
private void InitializeComponent()
{
this.btnSearch.Name = "btnSearch";
this.btnSearch.Text = "Search";
this.btnSearch.Click += new System.EventHandler(this.btnSearch_Click);
this.lblSearchFile.Text = "Searching File: ";
this.txtTextSearch.Text = "search string";
}
public SearchProgressForm() { }
private void btnSearch_Click(object sender, EventArgs e)
{
this.SearchByText(this.txtTextSearch.Text);
}
private void SearchByText(string p_searchParameter)
{
// Setup a progress report for thr ProgressBar
var _progressBarUpdate = new Progress<int>(value =>
{
this.SearchProgressBar.Value = value;
this.SearchProgressBar.Refresh();
});
var _progressFileNameUpdate = new Progress<string>(value =>
{
this.lblSearchFile.Text = "Searching File For : " + value;
this.lblSearchFile.Refresh();
});
// Start search on a backgroud thread and report progress as it occurs
Task.Run(async () => await this.SearchByStringAsync(p_searchParameter, _progressBarUpdate, _progressFileNameUpdate));
}
private async Task SearchByStringAsync(string p_searchParameter, IProgress<int> p_progressBar, IProgress<string> p_progressFileName)
{
await Task.Delay(1);
TextFileReader textFileReader = new TextFileReader();
LogSearchEventArguments logSearchEventArguments = null;
long _sessionloopCount = 0;
long _totalTextLinesCount = this.GetTotalSearchCount(p_searchParameter, SearchType.TextString);
// Get file names from SQL table
var _logFiles = DataOperations.LogFileSortableList(null);
foreach (var log in _logFiles)
{
// Format a file name to be read from the file system
string _fileName = log.Directory + "\\" + log.FileName;
p_progressFileName.Report(log.FileName);
// If we've raised an event for this file, then stop iterating over remaning text
if (logSearchEventArguments != null)
{
logSearchEventArguments = null;
break;
}
// Read in file contents from file system
List<string> _fileContents = textFileReader.ReadAndReturnStringList(_fileName);
long _fileTotalRecordCount = _fileContents.Count;
long _fileRecordCount = 0;
foreach (var _line in _fileContents)
{
if (_line.ToUpper().Contains(p_searchParameter.ToUpper()))
{
// Raise an event so search parameter and file name can be captured in another form
logSearchEventArguments =
new LogSearchEventArguments
(
"TextSearch", p_searchParameter, SearchType.TextString, true, log,
new DateTime(
Convert.ToInt32("20" + log.FileName.Substring(14, 2)),
Convert.ToInt32(log.FileName.Substring(16, 2)),
Convert.ToInt32(log.FileName.Substring(18, 2)))
);
// We found a match, so no further searching is needed in this log file,
// and it's been flagged in the DB, so raise the event to save search parameter and file name
// then break out of this loop to get the next file to search in.
this.OnSearchSucceeded(logSearchEventArguments);
break;
}
// These calcs are based on actual searches performed
_fileRecordCount++;
_sessionloopCount++;
p_progressBar.Report(Convert.ToInt32((_sessionloopCount * 100) / _totalTextLinesCount));
}
// Because we exit a search as soon as the 1st match is made, need to resynch all counts
// and update the progress bar accordingly
if (_fileRecordCount < _fileTotalRecordCount)
{
long _countDifference = _fileTotalRecordCount - _fileRecordCount;
// Add count difference to sessionLoopCount and update progress bar
_sessionloopCount += _countDifference;
p_progressBar.Report(Convert.ToInt32((_sessionloopCount * 100) / _totalTextLinesCount));
}
}
//Search is complete set Progress to 100% and report before exiting
p_progressBar.Report(100);
// Close the modal SearchForm and exit
this.Close();
}
}
I solved this problem but I'm still not certain of what caused it. I eliminated the method "private void SearchByText(string p_searchParameter)" and moved the code there into the btnSearch_Click event handler so I could call my background worker "SearchByStringAsync" directly from the button click event handler.
I also updated the EFCore NuGet Packages, which were version Net6.0 to version 6.0.4, because of single line of code in my Async background method, "var _logFiles = DataOperations.LogFileSortableList(null)".
That call returned a Sortable BindingList, using BindingList <T>. Between the NuGet updates and a minor change on a custom comparer method in my BindingList <T> class, the windows modal form now updates the ProgressBar and Label text as expected, and the form now responds immediately to user interaction.
I'm developing an app for Windows in Visual C++ which would get inputs from a web API. Some of the API calls require me to request response in some specified time delay and during this time I would like to display a spinner.
I'm all good with the displaying spinner, disabling the buttons etc for the time I need to wait before proceeding to the request, but don't know how to pause the process within the app. Obviously, if I use the _sleep function, the app becomes unresposive.
Here's more or less what I need to achieve (pseudo-code)
void doSomething()
{
ui->button1->setEnable(false);
SendAPIReuqest1();
while (APIRequest1_success)
{
requestAPIRequest1_response();
//wait 10s if false and retry, this can take up to 5mins
}
SendAPIRequest2();
//wait 30s - here I know I can start the job on my end exactly after 30s
doSometing2();
ui->button1->setEnable(true);
}
What would be the correct approach to achieve what I need?
You'll most likely just want to poll for a response from the web API, alternatively you can start a secondary thread to check for a response from the web API;
After seeing some minimal code, something similar to this may work.
//takes an initial start time, calculates elapsed time, compares elapsed time to count
bool ready(std::chrono::time_point<std::chrono::system_clock>&start, const double& count) {
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end-start;
if (diff.count() >= count) {
start = end;
return true;
}
return false;
}
void doSomething()
{
static std::chrono::time_point<std::chrono::system_clock> start;
static int state = 0;
switch(state) {
case (0):
ui->button1->setEnable(false);
SendAPIRequest1();
if (APIRequest1_success) {
start = std::chrono::system_clock::now();
++state;
}
break;
case (1):
if (ready(start, 10.0) && requestAPIRequest1_response()) ++state;
break;
case(2):
SendAPIRequest2();
start = std::chrono::system_clock::now();
++state;
break;
case(3):
if (ready(start, 30.0)) {
doSomething2();
ui->button1->setEnable(true);
state = 0;
}
break;
}
}
This way you can call the function and it will either attempt one of the requests or return to do other tasks.
or with threads it could be as simple as
void worker_func(std::promise<bool>&& result) {
using namespace std::chrono_literals;
SendAPIRequest1();
while (!requestAPIRequest1_response()) {
std::this_thread::sleep_for(10s);
}
SendAPIRequest2();
std::this_thread::sleep_for(30s);
doSomething2();
result.set_value(true);
}
void doSomething() {
static std::future<bool> finished;
static bool flag = true;
if (flag) {
std::promise<bool> _finished;
finished = _finished.get_future();
ui.button1.setEnable(false);
std::thread worker(worker_func, std::move(_finished));
flag = false;
} else if (finished.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
//finished.get();
worker.join();
ui.button1.setEnable(true);
flag = true;
}
}
This way your main thread can keep running the ui, while the worker thread waits for the web API response, as long as your requests aren't handling any QT ui components I believe this should work.
Edit:
Since I have never used QT and it was never mentioned originally that QT was being used the above answers may or may not be usefull, however it looks like QT has some functions to handle things like this. maybe you can just start and stop a Qtimer
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &foo::update);
timer->start(1000);
would run the function foo::update() every second, the time interval can be changed accordingly. Here is a reference to QTimer and a reference to another class which may be of interest QTimerEvent not to mention, QT has an entire event handling system The Event System
We're using a parser program (which I have no access to) that parses a bunch of computer generated mails but needs some help to decide on what it has to do in particular. Because of that, an employee kann use the subject line for additional commands. Since there are more than 500 mails per day that we feed to the program and the commands do all look similar to that: Ba,Vi;#TD*; x0003, it's impossible to write them manually. So I wrote a small C# script that creates an Autohotkey script which does 90% of the work. In theory. It works but only as long as I don't use any special characters, like , : & % etc.
I tried:
clipboard := Ba{,}Vi{;}{#}TD{*}{;} x0003
clipboard := "Ba,Vi;#TD*; x0003"
clipboard := Ba',Vi';'#TD'*'; x0003
clipboard := {raw} Ba,Vi;#TD*; x0003
(plus some others that I probably forgot here)
Here's the entire AHK script with annotations. You start it while having an email selected in Outlook:
;Win+z -> start script
#z::
;Currently only one iteration
loop,1
{
;CTRL+F to forward selected mail,
;which then automatically selects the "To:" line
Send, {CTRLDOWN}f{CTRUP}
Sleep, 500
Send, someemail#adress
Sleep, 500
;Initialize GUI
Gui, +AlwaysOnTop
Gui, Font, S5, Verdana, bold
Gui, Add, Text,, SCANNING-BOOSTER:
Gui, Color, F4A460
;Example for the C# generated buttons below (they all do the same thing):
;Clicking the Button "Google" will run the following script
;Start:
;clipboard := www.Google.com
;return
;This is the part where AHK fails because instead
;of www.Google.com I have codes like "Ba,Vi;#TD*; x0003" which crash AHK
Gui,add,Button,gLabel,Google
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
Gui,add,Button, ......
..... (around 60 more auto-generated buttons)
Gui,show
Return
Label:
;run the script that has the same name as the Button
;in this case it would be Google.ahk
Run, % A_GuiControl ".ahk"
GuiClose:
Gui, Hide
Sleep, 1000
;after the user has pressed a button and the according code
;has been copied to the clipboard, the GUI closes, the
;mail window becomes active again and we can continue to paste
;the code into the subject line
;Go to subject line
Send, {ALTDOWN}r{ALTUP}
Sleep, 500
;CTRL+a
Send, {CTRLDOWN}a{CTRUP}
Sleep, 500
;Write text from your clipboard to the subject line
Send, %clipboard%
Sleep, 500
return
}
Apparently it's currently not possible to copy a (more or less) random string to your clipboard in Autohotkey and then paste it somewhere else without receiving so many errors that it's worth pursuing it. I was left with no choice other than programming everything myself in C#. Here's how I did it:
First, start a new Console Application and then change it to a Windows Application How to change a console application to a windows form application?
This is the easiest way to make a program completely invisible to the user.
Next, we need a key listener. Here's the one I used: https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-keyboard-hook-in-c/
I would advise you to put it in a new class. This code needs to be slightly altered anyway because it prints every key press to the console. We don't want that, we want to call a method from it. Change HookCallback to:
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyPressed == vkCode)
DoWork();
}
return CallNextHookEx(_HookID, nCode, wParam, lParam);
}
For the code above to work, we need to add a new delegate and a new int variable to our class:
private static int KeyPressed;
public delegate void SomethingToDo();
private static SomethingToDo DoWork;
SetHook also needs some minor alterations:
private static IntPtr SetHook(KeyboardProc _proc, int KeyCode, SomethingToDo GiveMeWork)
{
DoWork = GiveMeWork;
KeyPressed = KeyCode;
...(leave the rest as it is)...
}
The program is now completely invisible and can react to a key press. Let's do the opposite, simulating keys!
Ctrl key kept down after simulating a ctrl key down event and ctrl key up event
This is a lot simpler. I made three methods, PressKey, ReleaseKey and TapKey. Keep in mind that ALT and F10 are special system keys which might not work.
[DllImport("user32.dll", SetLastError = true)]
static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
private const int KeyPressCode = 0x0001;
private const int KeyReleaseCode = 0x0002;
public static void PressKey(System.Windows.Forms.Keys Key)
{
keybd_event((byte)Key, 0, KeyPressCode | 0, 0);
Thread.Sleep(10);
}
public static void ReleaseKey(System.Windows.Forms.Keys Key)
{
keybd_event((byte)Key, 0, KeyPressCode | KeyReleaseCode, 0);
Thread.Sleep(10);
}
public static void TapKey(System.Windows.Forms.Keys Key)
{
PressKey(Key);
ReleaseKey(Key);
}
That's it, a basic Autohokey-Clone that can cope with strings. If you want to go even further and make a try icon for it:
https://translate.google.de/translate?sl=de&tl=en&js=y&prev=_t&hl=de&ie=UTF-8&u=https%3A%2F%2Fdotnet-snippets.de%2Fsnippet%2Fvorlage-fuer-tray-notifyicon-anwendung%2F541&edit-text=&act=url
Showing a GUI with buttons is also surprisingly simple:
using (Form _form = new Form())
{
_form.size = ...
//....
Button MyButton = new Button();
//....
//closing is also pretty simple, just like your everyday Windows Forms Application:
MyButton.Click += new EventHandler((sender, e) => { Application.Exit(); });
_form.ShowDialog();
}
Putting everything together in the main method:
private static NotifyIcon TrayIcon = new NotifyIcon();
[STAThread]
static void Main(string[] args)
{
bool WindowOpen = true;
try
{
//Make a new method above or below Main() and replace DoSomething with it.
//It will be executed everytime the F2 key is pressed.
KeyListener._HookID = KeyListener.SetHook(proc, System.Windows.Forms.Keys.F2, DoSomething);
System.Windows.Forms.ContextMenu SmallMenu = new System.Windows.Forms.ContextMenu();
System.Windows.Forms.MenuItem MenuElement;
int MenuIndex = 0;
MenuElement = new System.Windows.Forms.MenuItem();
MenuElement.Index = ++MenuIndex;
MenuElement.Text = "Close";
MenuElement.Click += new EventHandler((sender, e) => { WindowOpen = false; System.Windows.Forms.Application.Exit(); });
SmallMenu.MenuItems.Add(MenuElement);
TrayIcon.Icon = new System.Drawing.Icon("Ghost.ico");
TrayIcon.Text = "String Compatible AHK";
TrayIcon.Visible = true;
TrayIcon.ContextMenu = SmallMenu;
while (WindowOpen)
System.Windows.Forms.Application.Run();
}
finally
{
TrayIcon.Dispose();
KeyListener.UnhookWindowsHookEx(KeyListener._HookID);
}
}
Let me setup this question with some background information, we have a long running process which will be generating data in a Windows Form. So, obviously some form of multi-threading is going to be needed to keep the form responsive. But, we also have the requirement that the form updates as many times per second while still remaining responsive.
Here is a simple test example using background worker thread:
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int reportValue = (int)e.UserState;
label1.Text = reportValue;
//We can put this.Refresh() here to force repaint which gives us high repaints but we lose
//all other responsiveness with the control
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int x = 0; x < 100000; x++)
{
//We could put Thread.Sleep here but we won't get highest performance updates
bw.ReportProgress(0, x);
}
}
Please see the comments in the code. Also, please don't question why I want this. The question is simple, how do we achieve the highest fidelity (most repaints) in updating the form while maintaining responsiveness? Forcing the repaint does give us updates but we don't process windows messages.
I have also try placing DoEvents but that produces stack overflow. What I need is some way to say, "process any windows messages if you haven't lately". I can see also that maybe a slightly different pattern is needed to achieve this.
It seems we need to handle a few issues:
Updating the Form through the non UI thread. There are quite a few solution to this problem such as invoke, synchronization context, background worker pattern.
The second problem is flooding the Form with too many updates which blocks the message processing and this is the issue around which my question really concerns. In most examples, this is handles trivially by slowing down the requests with an arbitrary wait or only updating every X%. Neither of these solutions are approriate for real-world applications nor do they meet the maximum update while responsive criteria.
Some of my initial ideas on how to handle this:
Queue the items in the background worker and then dispatch them in a UI thread. This will ensure every item is painted but will result in lag which we don't want.
Perhaps use TPL
Perhaps use a timer in the UI thread to specify a refresh value. In this way, we can grab the data at the fastest rate that we can process. It will require accessing/sharing data across threads.
Update, I've updated to use a Timer to read a shared variable with the Background worker thread updates. Now for some reason, this method produces a good form response and also allows the background worker to update about 1,000x as fast. But, interestingly it only 1 millisecond accurate.
So we should be able to change the pattern to read the current time and call the updates from the bw thread without the need for the timer.
Here is the new pattern:
//Timer setup
{
RefreshTimer.SynchronizingObject = this;
RefreshTimer.Elapsed += RefreshTimer_Elapsed;
RefreshTimer.AutoReset = true;
RefreshTimer.Start();
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int x = 0; x < 1000000000; x++)
{
//bw.ReportProgress(0, x);
//mUiContext.Post(UpdateLabel, x);
SharedX = x;
}
}
void RefreshTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
label1.Text = SharedX.ToString();
}
Update And here we have the new solution that doesn't require the timer and doesn't block the thread! We achieve a high performance in calculations and fidelity on the updates with this pattern. Unfortunately, ticks TickCount is only 1 MS accurate, however we can run a batch of X updates per MS to get faster then 1 MS timing.
void bw_DoWork(object sender, DoWorkEventArgs e)
{
long lastTickCount = Environment.TickCount;
for (int x = 0; x < 1000000000; x++)
{
if (Environment.TickCount - lastTickCount > 1)
{
bw.ReportProgress(0, x);
lastTickCount = Environment.TickCount;
}
}
}
There is little point in trying to report progress any faster than the user can keep track of it.
If your background thread is posting messages faster than the GUI can process them, (and you have all the symtoms of this - poor GUI resonse to user input, DoEvents runaway recursion), you have to throttle the progress updates somehow.
A common approach is to update the GUI using a main-thread form timer at a rate sufficiently small that the user sees an acceptable progress readout. You may need a mutex or critical section to protect shared data, though that amy not be necessary if the progress value to be monitored is an int/uint.
An alternative is to strangle the thread by forcing it to block on an event or semaphore until the GUI is idle.
The UI thread should not be held for more than 50ms by a CPU-bound operation taking place on it ("The 50ms Rule"). Usually, the UI work items are executed upon events, triggered by user input, completion of an IO-bound operation or a CPU-bound operation offloaded to a background thread.
However, there are some rare cases when the work needs to be done on the UI thread. For example, you may need to poll a UI control for changes, because the control doesn't expose proper onchange-style event. Particularly, this applies to WebBrowser control (DOM Mutation Observers are only being introduced, and IHTMLChangeSink doesn't always work reliably, in my experience).
Here is how it can be done efficiently, without blocking the UI thread message queue. A few key things was used here to make this happen:
The UI work tasks yields (via Application.Idle) to process any pending messages
GetQueueStatus is used to decide on whether to yield or not
Task.Delay is used to throttle the loop, similar to a timer event. This step is optional, if the polling needs to be as precise as possible.
async/await provide pseudo-synchronous linear code flow.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinForms_21643584
{
public partial class MainForm : Form
{
EventHandler ContentChanged = delegate { };
public MainForm()
{
InitializeComponent();
this.Load += MainForm_Load;
}
// Update UI Task
async Task DoUiWorkAsync(CancellationToken token)
{
try
{
var startTick = Environment.TickCount;
var editorText = this.webBrowser.Document.Body.InnerText;
while (true)
{
// observe cancellation
token.ThrowIfCancellationRequested();
// throttle (optional)
await Task.Delay(50);
// yield to keep the UI responsive
await ApplicationExt.IdleYield();
// poll the content for changes
var newEditorText = this.webBrowser.Document.Body.InnerText;
if (newEditorText != editorText)
{
editorText = newEditorText;
this.status.Text = "Changed on " + (Environment.TickCount - startTick) + "ms";
this.ContentChanged(this, EventArgs.Empty);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
async void MainForm_Load(object sender, EventArgs e)
{
// navigate the WebBrowser
var documentTcs = new TaskCompletionSource<bool>();
this.webBrowser.DocumentCompleted += (sIgnore, eIgnore) => documentTcs.TrySetResult(true);
this.webBrowser.DocumentText = "<div style='width: 100%; height: 100%' contentEditable='true'></div>";
await documentTcs.Task;
// cancel updates in 10 s
var cts = new CancellationTokenSource(20000);
// start the UI update
var task = DoUiWorkAsync(cts.Token);
}
}
// Yield via Application.Idle
public static class ApplicationExt
{
public static Task<bool> IdleYield()
{
var idleTcs = new TaskCompletionSource<bool>();
if (IsMessagePending())
{
// register for Application.Idle
EventHandler handler = null;
handler = (s, e) =>
{
Application.Idle -= handler;
idleTcs.SetResult(true);
};
Application.Idle += handler;
}
else
idleTcs.SetResult(false);
return idleTcs.Task;
}
public static bool IsMessagePending()
{
// The high-order word of the return value indicates the types of messages currently in the queue.
return 0 != (GetQueueStatus(QS_MASK) >> 16 & QS_MASK);
}
const uint QS_MASK = 0x1FF;
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern uint GetQueueStatus(uint flags);
}
}
This code is specific to WinForms. Here is a similar approach for WPF.
I am trying to disable the user's ability to alter the state of a checkbox in a List Control. I am currently changing the state pragmatically. I already handle the LVN_ITEMCHANGED message, and trying to alter the state there isn't an option due to the layout of the rest of the program. I have also tried doing a HitTest when the user clicks in the List Control and simply resetting the checkbox there but that isn't giving me the exact results I am looking for.
Is there a specific message sent or a function I can override when the user clicks the checkbox itself? I would just like to override the handler or catch the message so that it doesn't go anywhere.
Solution:
I ended up removing the LVS_EX_CHECKBOXES flag and created my own implementation. That way the there is only one way to change the icons. Reading the link from the previous question gave me an idea to set a "busy" flag, otherwise I would get stack overflow errors.
// In my dialog class
m_CListCtrl.SetImageList(&m_ImgList, LVSIL_SMALL); // Custom checkboxes (only two images)
// ...
void CMyDialog::OnLvnItemchangedList(NMHDR *pNMHDR, LRESULT *pResult)
{
if(busy) { return; }
// ....
}
// When calling the SetCheck function:
busy = TRUE; // Avoid stack overflow errors
m_CListCtrl.SetCheck(index, fCheck);
busy = FALSE;
// I derived a class from CListCtrl and did an override on the get/set check:
class CCustomListCtrl : public CListCtrl
{
BOOL CCustomListCtrl::SetCheck(int nItem, BOOL fCheck)
{
TCHAR szBuf[1024];
DWORD ccBuf(1024);
LVITEM lvi;
lvi.iItem = nItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_TEXT | LVIF_IMAGE;
lvi.pszText = szBuf;
lvi.cchTextMax = ccBuf;
GetItem(&lvi);
lvi.iImage = (int)fCheck;
SetItem(&lvi);
return TRUE;
}
// Just need to determine which image is set in the list control for the item
BOOL CCustomListCtrl::GetCheck(int nItem)
{
LVITEM lvi;
lvi.iItem = nItem;
lvi.iSubItem = 0;
lvi.mask = LVIF_IMAGE;
GetItem(&lvi);
return (BOOL)(lvi.iImage);
}
}
This is not as elegant as I had hoped, but it works flawlessly.