using System; using System.Drawing; using System.Linq; using JD.Threading; using TeaBox; using TeaBox.Utilities; namespace JD.Application.View.Utilities { /// /// Paints in a separate thread and notifies when the costly work is done. /// /// Parameter for the given paint request. /// /// Two main system here: /// 1) Threading : The requesting thread ask for the image corresponding to its paint request. The current image is returned even if it doesnt fit. /// This first image is used until the new image is completely done. Then, when done, the thread notifies the user control which repaints using /// this new image. /// /// 2) QualityLevel : GDI+ locks to render images so the background thread is only useful for computation. /// To keep a good responsivenes, two paint are done. The first one is in low quality/fast so the lock does not affect the process too much. /// When the user stop requesting for new paint request, a last painting is done in high quality. /// This is achived with a timer, we reset it each time a new request appear. When the time expires, we ask for the final painting. /// public abstract class AsyncPaintCache : IDisposable where TRequest : struct // Because of comparison facilities { private Image _image; private TRequest? _currentRequest; private readonly WakeUpThread _paintThread; // Quality level variables private readonly Moderator _highQualityTrigger; private bool _lowQuality; public AsyncPaintCache() { _paintThread = new WakeUpThread(DoPaint); _highQualityTrigger = new Moderator(); _highQualityTrigger.Execute += _highQualityTrigger_Execute; _highQualityTrigger.InitialDelay.Interval = TimeSpan.FromMilliseconds(250); } protected bool LowQuality { get { return _lowQuality; } } /// /// Ask for the image corresponding to the paint request. /// public Image GetImage(TRequest request) { if (_currentRequest == null || !_currentRequest.Equals(request)) { _currentRequest = request; _lowQuality = true; _paintThread.WakeUp(); } return _image; } public void Invalidate() { _currentRequest = null; } private void DoPaint() { var request = _currentRequest; if (request == null) return; _image = DoPaint(request.Value); Refreshed?.Invoke(this, EventArgs.Empty); if (LowQuality) { _highQualityTrigger.InitialDelay.Reset(); _highQualityTrigger.TriggerAction(); } } void _highQualityTrigger_Execute() { _lowQuality = false; _paintThread.WakeUp(); } protected abstract Image DoPaint(TRequest request); /// /// Notifies the parent control that a new image is ready. /// public event EventHandler Refreshed; public void Dispose() { _image?.Dispose(); _highQualityTrigger?.Dispose(); } } }