102 lines
3.4 KiB
C#
102 lines
3.4 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using JD.Threading;
|
|
using TeaBox;
|
|
using TeaBox.Utilities;
|
|
|
|
namespace JD.Application.View.Utilities
|
|
{
|
|
/// <summary>
|
|
/// Paints in a separate thread and notifies when the costly work is done.
|
|
/// </summary>
|
|
/// <typeparam name="TRequest">Parameter for the given paint request.</typeparam>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
public abstract class AsyncPaintCache<TRequest> : 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; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask for the image corresponding to the paint request.
|
|
/// </summary>
|
|
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);
|
|
|
|
/// <summary>
|
|
/// Notifies the parent control that a new image is ready.
|
|
/// </summary>
|
|
public event EventHandler Refreshed;
|
|
|
|
public void Dispose()
|
|
{
|
|
_image?.Dispose();
|
|
_highQualityTrigger?.Dispose();
|
|
}
|
|
}
|
|
}
|