using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Windows.Media;
using Microsoft.WindowsAPICodePack.DirectX.Direct2D1;
using Microsoft.WindowsAPICodePack.DirectX.DirectWrite;
using DWrite = Microsoft.WindowsAPICodePack.DirectX.DirectWrite;
using Microsoft.WindowsAPICodePack.DirectX.DXGI;
using Microsoft.WindowsAPICodePack.DirectX.WindowsImagingComponent;
using System.Windows.Interop;
namespace WpfD2DSample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// CodePack D2D
D2DFactory d2dFactory;
DWriteFactory dwriteFactory;
RenderTarget renderTarget;
TextFormat textFormat;
// State management
private bool m_isAnimating;
private bool m_recreateSurfaceOnResize = true;
Point m_lastMousePosition;
bool m_hasCapture;
// Maintained simply to detect changes in the interop back buffer
IntPtr m_pIDXGISurfacePreviousNoRef;
// (fake) data to plot
int frameCount = 0;
RectF graphVisibleArea;
float scale = 1.01f;
Point2F[] dataPoints;
public MainWindow()
{
InitializeComponent();
host.Loaded += new RoutedEventHandler(host_Loaded);
host.SizeChanged += new SizeChangedEventHandler(host_SizeChanged);
host.MouseMove += new MouseEventHandler(host_MouseMove);
InitializeData();
}
#region Callbacks
void host_Loaded(object sender, RoutedEventArgs e)
{
// Create the D2D Factory
d2dFactory = D2DFactory.CreateFactory(D2DFactoryType.SingleThreaded);
// Create the DWrite Factory
dwriteFactory = DWriteFactory.CreateFactory();
// The text format
textFormat = dwriteFactory.CreateTextFormat("Bodoni MT", 24, DWrite.FontWeight.Normal, DWrite.FontStyle.Italic, DWrite.FontStretch.Normal);
InteropImage.HWNDOwner = (new System.Windows.Interop.WindowInteropHelper(this)).Handle;
InteropImage.OnRender = this.DoRender;
// Start rendering now!
InteropImage.RequestRender();
}
void host_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Potentially update the interop surface size
if (m_recreateSurfaceOnResize)
{
UpdateSize();
}
}
private void ResetVisibleArea(object sender, RoutedEventArgs e)
{
graphVisibleArea = new RectF(0, 0, 1, 1);
InteropImage.RequestRender();
}
private void ToggleScaleAnimation(object sender, RoutedEventArgs e)
{
if (m_isAnimating)
{
System.Windows.Media.CompositionTarget.Rendering -= new EventHandler(AnimatedScaleRenderCallback);
m_isAnimating = false;
((Button)sender).Content = "Start Scale Animation";
}
else
{
System.Windows.Media.CompositionTarget.Rendering += new EventHandler(AnimatedScaleRenderCallback);
m_isAnimating = true;
((Button)sender).Content = "Stop Scale Animation";
}
}
private void host_MouseDown(object sender, MouseButtonEventArgs e)
{
if ((Keyboard.IsKeyDown(Key.LeftCtrl) ||
Keyboard.IsKeyDown(Key.RightCtrl)) &&
host.CaptureMouse())
{
m_hasCapture = true;
m_lastMousePosition = e.GetPosition(host);
}
}
void host_MouseMove(object sender, MouseEventArgs e)
{
// Are we in capture/pan mode?
if (m_hasCapture && Object.ReferenceEquals(Mouse.Captured, host))
{
Point curMousePosition = e.GetPosition(host);
Vector delta = curMousePosition - m_lastMousePosition;
m_lastMousePosition = curMousePosition;
if (renderTarget != null)
{
float graphDeltaX = (float)(delta.X / host.ActualWidth * graphVisibleArea.Width);
float graphDeltaY = (float)(delta.Y / host.ActualHeight * graphVisibleArea.Height);
graphVisibleArea.Left -= graphDeltaX;
graphVisibleArea.Right -= graphDeltaX;
graphVisibleArea.Top -= graphDeltaY;
graphVisibleArea.Bottom -= graphDeltaY;
InteropImage.RequestRender();
}
}
else
{
Point mousePoint = e.GetPosition(host);
Point2F dataPoint = DataPointFromMousePoint(mousePoint);
int foundIdx = Array.BinarySearch(dataPoints, dataPoint, new PointComparer());
float window = (float)(2.0 / host.ActualWidth * graphVisibleArea.Width);
if (foundIdx < 0)
{
int i = ~foundIdx;
for (; i < dataPoints.Length && dataPoints[i].X <= dataPoint.X + window; i++)
{
if ((dataPoints[i].X <= dataPoint.X + window) &&
(dataPoints[i].Y >= dataPoint.Y - window) &&
(dataPoints[i].Y <= dataPoint.Y + window))
{
foundIdx = i;
break;
}
}
if (foundIdx < 0)
{
i = ~foundIdx - 1;
for (; i >= 0 && dataPoints[i].X >= dataPoint.X + window; i--)
{
if ((dataPoints[i].X >= dataPoint.X + window) &&
(dataPoints[i].Y >= dataPoint.Y - window) &&
(dataPoints[i].Y <= dataPoint.Y + window))
{
foundIdx = i;
break;
}
}
}
}
if (foundIdx >= 0 && foundIdx < dataPoints.Length)
{
Point2F foundPoint = dataPoints[foundIdx];
Point foundPointInCanvasSpace = MousePointFromDataPoint(foundPoint);
Canvas.SetLeft(Highlight, foundPointInCanvasSpace.X - 4);
Canvas.SetTop(Highlight, foundPointInCanvasSpace.Y - 4);
Highlight.Visibility = System.Windows.Visibility.Visible;
Details.DataContext = new { Point = new Point(foundPoint.X, 1 - foundPoint.Y), };
if (foundPointInCanvasSpace.X > host.ActualWidth / 2)
{
Details.ClearValue(Canvas.LeftProperty);
Canvas.SetRight(Details, host.ActualWidth - foundPointInCanvasSpace.X + 10);
}
else
{
Details.ClearValue(Canvas.RightProperty);
Canvas.SetLeft(Details, foundPointInCanvasSpace.X + 10);
}
if (foundPointInCanvasSpace.Y > host.ActualHeight / 2)
{
Details.ClearValue(Canvas.TopProperty);
Canvas.SetBottom(Details, host.ActualHeight - foundPointInCanvasSpace.Y + 10);
}
else
{
Details.ClearValue(Canvas.BottomProperty);
Canvas.SetTop(Details, foundPointInCanvasSpace.Y + 10);
}
Details.Visibility = System.Windows.Visibility.Visible;
}
else
{
Highlight.Visibility = System.Windows.Visibility.Hidden;
Details.Visibility = System.Windows.Visibility.Hidden;
}
}
}
private void host_MouseUp(object sender, MouseButtonEventArgs e)
{
if (Object.ReferenceEquals(Mouse.Captured, host))
{
host.ReleaseMouseCapture();
m_hasCapture = false;
}
}
private void host_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point curMousePosition = e.GetPosition(host);
Point2F centerPoint = DataPointFromMousePoint(curMousePosition);
float wheelScale = 1.1f;
if (e.Delta > 0)
{
wheelScale = 1 / wheelScale;
}
float width = graphVisibleArea.Width;
float height = graphVisibleArea.Height;
graphVisibleArea.Left = (graphVisibleArea.Left - centerPoint.X) * wheelScale + centerPoint.X;
graphVisibleArea.Top = (graphVisibleArea.Top - centerPoint.Y) * wheelScale + centerPoint.Y;
graphVisibleArea.Right = graphVisibleArea.Left + width * wheelScale;
graphVisibleArea.Bottom = graphVisibleArea.Top + height * wheelScale;
InteropImage.RequestRender();
}
private void ScaleStretch_Checked(object sender, RoutedEventArgs e)
{
m_recreateSurfaceOnResize = true;
UpdateSize();
}
private void ScaleStretch_Unchecked(object sender, RoutedEventArgs e)
{
m_recreateSurfaceOnResize = false;
}
void AnimatedScaleRenderCallback(object sender, EventArgs e)
{
if (++frameCount % 100 == 0)
{
scale = 1 / scale;
}
float width = graphVisibleArea.Width;
float height = graphVisibleArea.Height;
graphVisibleArea.Left = (graphVisibleArea.Left - 0.75f) * scale + 0.75f;
graphVisibleArea.Top = (graphVisibleArea.Top - 0.75f) * scale + 0.75f;
graphVisibleArea.Right = graphVisibleArea.Left + width * scale;
graphVisibleArea.Bottom = graphVisibleArea.Top + height * scale;
InteropImage.RequestRender();
}
#endregion Callbacks
#region Helpers
void UpdateSize()
{
// TODO: handle non-96 DPI
uint surfWidth = (uint)(host.ActualWidth < 0 ? 0 : Math.Ceiling(host.ActualWidth));
uint surfHeight = (uint)(host.ActualHeight < 0 ? 0 : Math.Ceiling(host.ActualHeight));
InteropImage.SetPixelSize(surfWidth, surfHeight);
}
private void InitializeData()
{
dataPoints = new Point2F[100000];
Random r = new Random();
for (int i = 0; i < dataPoints.Length; i++)
{
float x = (float)r.NextDouble();
dataPoints[i] = new Point2F(x, 1.0F - ((float)(Math.Pow(x, 2)) + x * (x * ((float)r.NextDouble() / 2.0f - 0.5f))));
}
// Sort for easy future lookup
dataPoints = dataPoints.OrderBy((p) => { return p.X * 10000 + p.Y; }).ToArray();
graphVisibleArea = new RectF(0, 0, 1, 1);
}
private Point MousePointFromDataPoint(Point2F dataPoint)
{
return new Point((float)((dataPoint.X - graphVisibleArea.Left) / graphVisibleArea.Width * host.ActualWidth),
(float)((dataPoint.Y - graphVisibleArea.Top) / graphVisibleArea.Height * host.ActualHeight));
}
private Point2F DataPointFromMousePoint(Point mousePos)
{
return new Point2F((float)(mousePos.X / host.ActualWidth * graphVisibleArea.Width + graphVisibleArea.Left),
(float)(mousePos.Y / host.ActualHeight * graphVisibleArea.Height + graphVisibleArea.Top));
}
private class PointComparer : IComparer<Point2F>
{
#region IComparer<Point2F> Members
public int Compare(Point2F x, Point2F y)
{
return (x.X * 10000 + x.Y).CompareTo(y.X * 10000 + y.Y);
}
#endregion
}
#endregion Helpers
#region D2D
void PaintGrid(RenderTarget target)
{
double logW = Math.Log10(graphVisibleArea.Width);
double logH = Math.Log10(graphVisibleArea.Height);
SolidColorBrush spGridBrush = target.CreateSolidColorBrush(new ColorF(0.33f, 0.34f, 0.36f, 1.0f));
SolidColorBrush blackBrush = renderTarget.CreateSolidColorBrush(new ColorF(0, 0, 0, 1));
for (int i = 1; i <= 2; i++)
{
if (i == 2)
{
spGridBrush.Opacity = -(float)((logW - 100) % 1) * 0.9f + 0.1f;
}
double xStep = Math.Pow(10, Math.Ceiling(logW - i));
for (double x = graphVisibleArea.Left - Math.IEEERemainder(graphVisibleArea.Left, xStep); x < graphVisibleArea.Right; x += xStep)
{
float scaledX = (float)((x - graphVisibleArea.Left) / graphVisibleArea.Width * (float)target.PixelSize.Width);
target.FillRectangle(new RectF(scaledX - 1, 0, scaledX + 1, renderTarget.PixelSize.Height), spGridBrush);
if (i == 1)
{
renderTarget.DrawText(
String.Format("{0:G4}", x),
textFormat,
new RectF(scaledX - 15, target.PixelSize.Height - textFormat.FontSize - 2, scaledX + 45, target.PixelSize.Height), blackBrush);
}
}
if (i == 2)
{
spGridBrush.Opacity = -(float)((logH - 100) % 1) * 0.9f + 0.1f;
}
double yStep = Math.Pow(10, Math.Ceiling(logH - i));
for (double y = graphVisibleArea.Top - Math.IEEERemainder(graphVisibleArea.Top, xStep); y < graphVisibleArea.Bottom; y += yStep)
{
float scaledY = (float)((y - graphVisibleArea.Top) / graphVisibleArea.Height * (float)target.PixelSize.Height);
target.FillRectangle(new RectF(0, scaledY - 1, renderTarget.PixelSize.Width, scaledY + 1), spGridBrush);
if (i == 1)
{
renderTarget.DrawText(
String.Format("{0:G4}", (1.0f - y)),
textFormat,
new RectF(0, scaledY - textFormat.FontSize / 2.0f,
60, scaledY + textFormat.FontSize / 2.0f), blackBrush);
}
}
}
}
void DoRender(IntPtr pIDXGISurface)
{
if (pIDXGISurface != m_pIDXGISurfacePreviousNoRef)
{
m_pIDXGISurfacePreviousNoRef = pIDXGISurface;
// Create the render target
Surface dxgiSurface = Surface.FromNativeSurface(pIDXGISurface);
SurfaceDescription sd = dxgiSurface.Description;
RenderTargetProperties rtp =
new RenderTargetProperties(
RenderTargetType.Default,
new PixelFormat(Format.Unknown, AlphaMode.Premultiplied),
96,
96,
RenderTargetUsage.None,
Microsoft.WindowsAPICodePack.DirectX.Direct3D.FeatureLevel.Default);
try
{
renderTarget = d2dFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, rtp);
}
catch (Exception)
{
return;
}
// Clear the surface to transparent
renderTarget.BeginDraw();
renderTarget.Clear(new ColorF(1, 1, 1, 0));
renderTarget.EndDraw();
}
SolidColorBrush spBrush = renderTarget.CreateSolidColorBrush(new ColorF(1, 1, 1, 1.0f));
renderTarget.BeginDraw();
renderTarget.Clear(new ColorF(1, 1, 1, 0));
PaintGrid(renderTarget);
float scaleX = (float)renderTarget.PixelSize.Width / graphVisibleArea.Width;
float scaleY = (float)renderTarget.PixelSize.Height / graphVisibleArea.Height;
float opacity = Math.Max(renderTarget.PixelSize.Width * renderTarget.PixelSize.Height / (25.0f * (dataPoints.Length * graphVisibleArea.Width * graphVisibleArea.Height)), 0) * 0.8f + 0.2F;
for (int i = 0; i < dataPoints.Length; i++)
{
Point2F point = dataPoints[i];
if (point.X >= graphVisibleArea.Left && point.X <= graphVisibleArea.Right &&
point.Y >= graphVisibleArea.Top && point.Y <= graphVisibleArea.Bottom)
{
spBrush.Color = new ColorF(point.Y, point.X, Math.Abs((point.X - 0.5f) * (point.Y - 0.5f) * 4), opacity);
float left = (point.X - graphVisibleArea.Left) * scaleX - 1.5f;
float top = (point.Y - graphVisibleArea.Top) * scaleY - 1.5f;
// Subtract Y from 1.0f to invert so the origin is bottom/left.
renderTarget.FillRectangle(
new Microsoft.WindowsAPICodePack.DirectX.Direct2D1.RectF(left, top, left + 3, top + 3),
spBrush);
}
}
renderTarget.EndDraw();
}
#endregion D2D
}
}