using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
//using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Research.DynamicDataDisplay.Charts;
using Microsoft.Research.DynamicDataDisplay.Common;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Microsoft.Research.DynamicDataDisplay.PointMarkers;
using Microsoft.Research.DynamicDataDisplay.Charts.Shapes;
using System.Windows.Input;
namespace Microsoft.Research.DynamicDataDisplay
{
public abstract class BitmapBasedGraph : ViewportElement2D, IDisposable
{
static BitmapBasedGraph()
{
Type thisType = typeof(BitmapBasedGraph);
BackgroundRenderer.UsesBackgroundRenderingProperty.OverrideMetadata(thisType, new FrameworkPropertyMetadata(true));
}
private int disposed = 0;
private int nextRequestId = 0;
/// Latest complete request
private RenderRequest completedRequest = null;
/// Currently running request
private RenderRequest activeRequest = null;
/// Result of latest complete request
private BitmapSource completedBitmap = null;
/// Pending render request
private RenderRequest pendingRequest = null;
/// Single apartment thread used for background rendering
/// STA is required for creating WPF components in this thread
private Thread renderThread = null;
private AutoResetEvent renderRequested = new AutoResetEvent(false);
private ManualResetEvent shutdownRequested = new ManualResetEvent(false);
/// True means that current bitmap is invalidated and is to be re-rendered.
private bool bitmapInvalidated = true;
/// Shows tooltips.
private PopupTip popup;
///
/// Initializes a new instance of the class.
///
public BitmapBasedGraph()
{
ManualTranslate = true;
}
protected virtual UIElement GetTooltipForPoint(Point point, DataRect visible, Rect output)
{
return null;
}
protected PopupTip GetPopupTipWindow()
{
if (popup != null)
return popup;
foreach (var item in Plotter.Children)
{
if (item is ViewportUIContainer)
{
ViewportUIContainer container = (ViewportUIContainer)item;
if (container.Content is PopupTip)
return popup = (PopupTip)container.Content;
}
}
popup = new PopupTip();
popup.Placement = PlacementMode.Relative;
popup.PlacementTarget = this;
Plotter.Children.Add(popup);
return popup;
}
protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
{
base.OnMouseMove(e);
var popup = GetPopupTipWindow();
if (popup.IsOpen)
popup.Hide();
if (bitmapInvalidated) return;
Point p = e.GetPosition(this);
Point dp = p.ScreenToData(Plotter2D.Transform);
var tooltip = GetTooltipForPoint(p, completedRequest.Visible, completedRequest.Output);
if (tooltip == null) return;
popup.VerticalOffset = p.Y + 20;
popup.HorizontalOffset = p.X;
popup.ShowDelayed(new TimeSpan(0, 0, 1));
Grid grid = new Grid();
Rectangle rect = new Rectangle
{
Stroke = Brushes.Black,
Fill = SystemColors.InfoBrush
};
StackPanel sp = new StackPanel();
sp.Orientation = Orientation.Vertical;
sp.Children.Add(tooltip);
sp.Margin = new Thickness(4, 2, 4, 2);
var tb = new TextBlock();
tb.Text = String.Format("Location: {0:F2}, {1:F2}", dp.X, dp.Y);
tb.Foreground = SystemColors.GrayTextBrush;
sp.Children.Add(tb);
grid.Children.Add(rect);
grid.Children.Add(sp);
grid.Measure(SizeHelper.CreateInfiniteSize());
popup.Child = grid;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
GetPopupTipWindow().Hide();
}
///
/// Adds a render task and invalidates visual.
///
public void UpdateVisualization()
{
if (Viewport == null) return;
Rect output = new Rect(this.RenderSize);
CreateRenderTask(Viewport.Visible, output);
InvalidateVisual();
}
protected override void OnVisibleChanged(DataRect newRect, DataRect oldRect)
{
base.OnVisibleChanged(newRect, oldRect);
CreateRenderTask(newRect, Viewport.Output);
InvalidateVisual();
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
CreateRenderTask(Viewport.Visible, new Rect(sizeInfo.NewSize));
InvalidateVisual();
}
protected abstract BitmapSource RenderFrame(DataRect visible, Rect output);
private void RenderThreadFunc()
{
WaitHandle[] events = new WaitHandle[] { renderRequested, shutdownRequested };
while (true)
{
lock (this)
{
activeRequest = null;
if (pendingRequest != null)
{
activeRequest = pendingRequest;
pendingRequest = null;
}
}
if (activeRequest == null)
{
WaitHandle.WaitAny(events);
if (shutdownRequested.WaitOne(0))
break;
}
else
{
try
{
BitmapSource result = (BitmapSource)RenderFrame(activeRequest.Visible, activeRequest.Output);
if (result != null && !IsDisposed)
Dispatcher.BeginInvoke(
new RenderCompletionHandler(OnRenderCompleted),
new RenderResult(activeRequest, result));
}
catch (Exception exc)
{
Trace.WriteLine(String.Format("RenderRequest {0} failed: {1}", activeRequest.RequestID, exc.Message));
}
}
}
}
private void CreateRenderTask(DataRect visible, Rect output)
{
lock (this)
{
bitmapInvalidated = true;
if (activeRequest != null)
activeRequest.Cancel();
pendingRequest = new RenderRequest(nextRequestId++, visible, output);
renderRequested.Set();
}
if (renderThread == null)
{
renderThread = new Thread(RenderThreadFunc);
renderThread.IsBackground = true;
renderThread.SetApartmentState(ApartmentState.STA);
renderThread.Start();
}
}
private delegate void RenderCompletionHandler(RenderResult result);
protected virtual void OnRenderCompleted(RenderResult result)
{
if (IsDisposed)
return;
completedRequest = result.Request;
completedBitmap = result.Bitmap;
bitmapInvalidated = false;
InvalidateVisual();
BackgroundRenderer.RaiseRenderingFinished(this);
}
protected override void OnRenderCore(DrawingContext dc, RenderState state)
{
if (completedRequest != null && completedBitmap != null)
dc.DrawImage(completedBitmap, completedRequest.Visible.ViewportToScreen(Viewport.Transform));
}
public bool IsDisposed
{
get
{
return disposed > 0;
}
}
#region IDisposable Members
public void Dispose()
{
Interlocked.Increment(ref disposed);
shutdownRequested.Set();
}
#endregion
}
public class RenderRequest
{
private int requestId;
private DataRect visible;
private Rect output;
private int cancelling;
public RenderRequest(int requestId, DataRect visible, Rect output)
{
this.requestId = requestId;
this.visible = visible;
this.output = output;
}
public int RequestID
{
get { return requestId; }
}
public DataRect Visible
{
get { return visible; }
}
public Rect Output
{
get { return output; }
}
public bool IsCancellingRequested
{
get { return cancelling > 0; }
}
public void Cancel()
{
Interlocked.Increment(ref cancelling);
}
}
public class RenderResult
{
private RenderRequest request;
private BitmapSource bitmap;
/// Constructs successul rendering result
/// Source request
/// Rendered bitmap
public RenderResult(RenderRequest request, BitmapSource result)
{
this.bitmap = result;
this.request = request;
result.Freeze();
}
public RenderRequest Request
{
get { return request; }
}
public BitmapSource Bitmap
{
get { return bitmap; }
}
}
}