using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; namespace TradeIdeas.TIProGUI { /// /// This is used to consolidate multiple requests in the GUI thread. Imagine that one piece of /// code says to change the background color of your button. And another says to change the /// foreground color. And a third one says to change the text. Naively you might redraw the /// button 3 times, once for each call. Another approach would be to make the caller first /// call StartTransaction() and then Commit() at the end. That's often very inconvenient for /// the caller. Instead, use WakeMeSoon. Any time someone changes the state of your button, store /// the new state (e.g. ForegroundColor = Color.Red) but don't call Paint() right away. Instead /// call WakeMeSoon.RequestWakeUp() each time you make a change. The callback will eventually /// be called. Call Paint() in the callback. /// /// The previous example has one issue. Windows provides Invalidate() for this exact purpose. /// If you want to call Paint() soon, possibly merging multiple calls, you should call /// Invalidate(). But Invalidate() only works with Paint(). WakeMeSoon() calls anything you /// want. /// /// You can call RequestWakeUp() from any thread. The callback will always come back in the /// GUI thread. /// /// If you call this from the GUI thread, you will not get a callback until the GUI event loop /// finishes processing the current event. It might take longer. In general, the busier the /// GUI thread is, the more events get consolidated. /// /// This was inspired by WakeMeSoon in the Delphi software. That was a little more complicated to /// write because Delphi didn't have anything like BeginInvoke() built it. It does have something /// like Invoke(), but I had to create BeginInvoke() myself. Otherwise the Delphi version was /// almost identical to this. /// public class WakeMeSoon { /// /// This will be called in the GUI thread. After a call to RequestWakeUp(), you are guaranteed /// to eventually get a one of these callbacks. However, multiple calls to RequestWakeUp() /// might be consolidated to a single call of this event. /// /// This can change at any time, including changing in multiple threads. However, this typically /// is only set once, right after we construct the object. /// public event Action OnWakeUp; /// /// None of the methods for System.Threading.Interlocked understand the bool type. So I created /// my own using int. /// private const int TRUE = 1; /// /// None of the methods for System.Threading.Interlocked understand the bool type. So I created /// my own using int. /// private const int FALSE = 0; /// /// This is how we consolidate multiple requests. This variable says TRUE if we've already called /// BeginInvoke() recently. /// private int _requestInProgress = FALSE; public void RequestWakeUp() { int alreadInProgress = System.Threading.Interlocked.Exchange(ref _requestInProgress, TRUE); // At this instant RequestInProgress will be true. (I mean "instant" literally, as it could // be changed back by another thread at any time.) If RequestInProgress was false, and // N threads all call this at once, exactly one thread should see alreadInProgress == false, // and the others will all see alreadInProgress == true. If RequestInProgress was initially // true, this will return true. if (alreadInProgress == TRUE) // This wakeup request is not required because one is already pending. return; // Note that we always call BeginInvoke(). Most of our code uses BeginInvokeIfRequired(). // That would just immediately call the code if we are already in the GUI thread. We want // just the opposite. We always want to submit this to the event queue. _control.BeginInvoke((MethodInvoker)delegate { _requestInProgress = FALSE; System.Threading.Thread.MemoryBarrier(); // Any requests to RequestWakeUp() made after this time will cause another callback. // Make a copy. This code is thread safe. It's possibly soeone could change the value // of OnWakeUp between our test if it is null and our attempt to call it. Action onWakeup = OnWakeUp; if (null != onWakeup) OnWakeUp(); }); } /// /// This is used for our call to BeginInvoke. /// private readonly Control _control; /// /// Create a new object. Each object is completely seperate. /// /// /// This is used by BeginInvoke. /// /// It is safe to leave this null, but only if you are in the GUI thread when you call this /// constructor. /// /// The handle must exist for this control before you call this constructor. We would /// create the handle ourselves, but that can only be done in the GUI thread, and this /// constructor can be called from any thread. /// /// Accessing the Control.Handle property is the most certain way to ensure the handle has /// been created. Calling Control.CreateControl() will work in many but not all cases. /// public WakeMeSoon(Control control = null) { if (null == control) { control = new Control(); // The following line would cause the assertion to fail. The documentation was // not 100% clear. CreateControl() does what we want for a brand new control, like // the one we created. Specifically, it creates the handle. However, if the control // or one of its parents is not visible, then CreateControl() does NOT create the // handle. So if someone else gives you a control and you don't know the details // of that control, calling CreateControl() may not be sufficient. (I added "Visible // = false" only as a temporary test. I don't really want to make this control // invisible.) //control.Visible = false; control.CreateControl(); } // I want to leave this assert in place. However, there's a catch. IsHandleCreated is // not thread safe. It is up to the callers to make sure they gives us a valid input. //System.Diagnostics.Debug.Assert(control.IsHandleCreated); _control = control; } } }