I have a custom control that renders a bezier line from the top-left to the bottom-right corner. I would like to modify the hit test behavior so that the control is only "hit", if hovering over or near the bezier curve, but FillContainsWithDetail doesn't return the expected results.
What am I missing?
Here's a derived example class:
public class BezierControl : FrameworkElement
{
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
if (_geometry == null) return null;
var p = hitTestParameters.HitPoint;
EllipseGeometry expandedHitTestArea = new EllipseGeometry(p, 10.0, 10.0);
var intersection = expandedHitTestArea.FillContainsWithDetail(_geometry);
if (intersection > IntersectionDetail.Empty)
{
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
return null;
}
private StreamGeometry _geometry;
private StreamGeometry getGeometry()
{
var result = new StreamGeometry();
using (var context = result.Open())
{
var start = new Point(0, 0);
var startCp = new Point(10, 0);
var end = new Point(this.ActualWidth, this.ActualHeight);
var endCp = new Point(this.ActualWidth - 10, this.ActualHeight);
context.BeginFigure(start, false, false);
context.BezierTo(startCp, endCp, end, true, false);
}
result.Freeze();
return result;
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
if (_geometry == null) _geometry = getGeometry();
dc.DrawGeometry(null, _pen, _geometry);
}
private Pen _pen = new Pen(Brushes.Red, 1.0);
}
EDIT: The approved answer below is fine, but it also lead me to another alternative - or showed me why my attempted solution was failing: I was creating the geometry like this:
context.BeginFigure(bezier.Start, false, false);
The first bool parameter is called isFilled. Setting this to true allows intersecting between _geometry and expandedHitTestArea.
Before noticing this, I implemented the accepted answer below and decided to create the widenedGeometry lazily for performance reasons. Which now gets me wondering: from a performance perspective, which approach is better? Or, is this negligible in this case, because geometries are inexpensive anyway?
You don't need that EllipseGeometry. Just check if the hit point is inside a widened path geometry of the original geometry (which may of course also be created once when _geometry is created):
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
if (_geometry != null)
{
var widenedGeometry = _geometry.GetWidenedPathGeometry(new Pen(null, 20));
if (widenedGeometry.FillContains(hitTestParameters.HitPoint))
{
return new PointHitTestResult(this, hitTestParameters.HitPoint);
}
}
return null;
}
Related
I would like to have a Panel with a Background that shows a repeated pattern (e.g. dots, evenly separated of 30 pixels).
So far, I managed to create a subclass of XamlCompositionBrushBase that allows we to create my own shape (e.g. a single dot). but I am failing to understand how to repeat this pattern.
This is my custom Brush:
public sealed class DottedBackgroundBrush : XamlCompositionBrushBase
{
public DottedBackgroundBrush()
{
}
protected override void OnConnected()
{
// Delay creating composition resources until they're required.
if (CompositionBrush == null)
{
var compositor = Window.Current.Compositor;
// Actual Width/Height are going to be returned in effective pixels which
// is going to differ from the size of the bitmap that we'll render from the XAML.
var width = 400;
var height = 400;
// Make our visual:
var spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size = new Vector2(width, height);
CanvasDevice device = CanvasDevice.GetSharedDevice();
var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, device);
CompositionSurfaceBrush drawingBrush = compositor.CreateSurfaceBrush();
var drawingSurface = graphicsDevice.CreateDrawingSurface(
new Size(width, height),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(drawingSurface))
{
ds.Clear(Colors.Transparent);
ds.DrawCircle(new Vector2(10, 10), 5, Colors.Black, 3);
}
drawingBrush.Surface = drawingSurface;
CompositionBrush = drawingBrush;
}
}
protected override void OnDisconnected()
{
// Dispose of composition resources when no longer in use.
if (CompositionBrush != null)
{
CompositionBrush.Dispose();
CompositionBrush = null;
}
}
}
How can I enable the circle to be replicated indefinitely, instead of having as single instance?
For this, you want to create a CompositionEffectBrush as your main brush, using a Win2D BorderEffect - which does the actual tiling - and set it's source to be your SurfaceBrush.
Example (adapted from a repo of mine so it might be a bit roundabouts)
public class TilingBrush : XamlCompositionBrushBase
{
protected Compositor _compositor => Window.Current.Compositor;
protected CompositionBrush _imageBrush = null;
protected IDisposable _surfaceSource = null;
protected override void OnConnected()
{
base.OnConnected();
if (CompositionBrush == null)
{
CreateEffectBrush();
Render();
}
}
protected override void OnDisconnected()
{
base.OnDisconnected();
this.CompositionBrush?.Dispose();
this.CompositionBrush = null;
ClearResources();
}
private void ClearResources()
{
_imageBrush?.Dispose();
_imageBrush = null;
_surfaceSource?.Dispose();
_surfaceSource = null;
}
private void UpdateBrush()
{
if (CompositionBrush != null && _imageBrush != null)
{
((CompositionEffectBrush)CompositionBrush).SetSourceParameter(nameof(BorderEffect.Source), _imageBrush);
}
}
protected ICompositionSurface CreateSurface()
{
double width = 20;
double height = 20;
CanvasDevice device = CanvasDevice.GetSharedDevice();
var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(_compositor, device);
var drawingSurface = graphicsDevice.CreateDrawingSurface(
new Size(width, height),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
/* Create Drawing Session is not thread safe - only one can ever be active at a time per app */
using (var ds = CanvasComposition.CreateDrawingSession(drawingSurface))
{
ds.Clear(Colors.Transparent);
ds.DrawCircle(new Vector2(10, 10), 5, Colors.Black, 3);
}
return drawingSurface;
}
private void Render()
{
ClearResources();
try
{
var src = CreateSurface();
_surfaceSource = src as IDisposable;
var surfaceBrush = _compositor.CreateSurfaceBrush(src);
surfaceBrush.VerticalAlignmentRatio = 0.0f;
surfaceBrush.HorizontalAlignmentRatio = 0.0f;
surfaceBrush.Stretch = CompositionStretch.None;
_imageBrush = surfaceBrush;
UpdateBrush();
}
catch
{
// no image for you, soz.
}
}
private void CreateEffectBrush()
{
using (var effect = new BorderEffect
{
Name = nameof(BorderEffect),
ExtendY = CanvasEdgeBehavior.Wrap,
ExtendX = CanvasEdgeBehavior.Wrap,
Source = new CompositionEffectSourceParameter(nameof(BorderEffect.Source))
})
using (var _effectFactory = _compositor.CreateEffectFactory(effect))
{
this.CompositionBrush = _effectFactory.CreateBrush();
}
}
}
For the longest time I've meant to add it to the WindowsCommunityToolkit, but for the longest time I've been slamming into bugs with the Visual Layer that stop me. This particular case should work fine however.
I can change my markers position with
markers.Markers[2].Position = new PointLatLng(30.0000, 30.00000);
but how can i change marker icon with setting a varible like above?
I am declaring points as
GMap.NET.WindowsForms.GMapMarker marker3 =
new GMap.NET.WindowsForms.Markers.GMarkerGoogle(
new GMap.NET.PointLatLng(30.0000, 30.00000),
new Bitmap("images/2.png"));
thanks...
To solve this problem, i contacted the creator of the library: radioman.
He referred some code called 'GMarkerArrow.' Here is the code:
namespace Demo.WindowsForms.CustomMarkers
{
using System;
using System.Drawing;
using System.Runtime.Serialization;
using GMap.NET;
using GMap.NET.WindowsForms;
[Serializable]
public class GMarkerArrow : GMapMarker, ISerializable
{
[NonSerialized]
public Brush Fill = new SolidBrush(Color.FromArgb(155, Color.Blue));
//[NonSerialized]
//public Pen Pen = new Pen(Brushes.Blue, 5);
static readonly Point[] Arrow = new Point[] { new Point(-7, 7), new Point(0, -22), new Point(7, 7), new Point(0, 2) };
public float Bearing = 0;
private float scale = 1;
public float Scale
{
get
{
return scale;
}
set
{
scale = value;
Size = new System.Drawing.Size((int)(14 * scale), (int)(14 * scale));
Offset = new System.Drawing.Point(-Size.Width / 2, (int)(-Size.Height / 1.4));
}
}
public GMarkerArrow(PointLatLng p)
: base(p)
{
Scale = (float)1.4;
}
public override void OnRender(Graphics g)
{
//g.DrawRectangle(Pen, new System.Drawing.Rectangle(LocalPosition.X, LocalPosition.Y, Size.Width, Size.Height));
{
g.TranslateTransform(ToolTipPosition.X, ToolTipPosition.Y);
var c = g.BeginContainer();
{
g.RotateTransform(Bearing - Overlay.Control.Bearing);
g.ScaleTransform(Scale, Scale);
g.FillPolygon(Fill, Arrow);
}
g.EndContainer(c);
g.TranslateTransform(-ToolTipPosition.X, -ToolTipPosition.Y);
}
}
public override void Dispose()
{
//if(Pen != null)
//{
// Pen.Dispose();
// Pen = null;
//}
if (Fill != null)
{
Fill.Dispose();
Fill = null;
}
base.Dispose();
}
#region ISerializable Members
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
protected GMarkerArrow(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
}
}
Save this as GMarkerArrow.cs and add it to your project.
Add this to your Form1 code:
using Demo.WindowsForms.CustomMarkers;
Now you can use the new marker via following:
GMarkerArrow marker1 = new GMarkerArrow(new PointLatLng(-30, -40));
marker1.ToolTipText = "blablabla";
marker1.ToolTip.Fill = Brushes.Black;
marker1.ToolTip.Foreground = Brushes.White;
marker1.ToolTip.Stroke = Pens.Black;
marker1.Bearing = 30; // Rotation angle
marker1.Fill = new SolidBrush(Color.FromArgb(155, Color.Blue)); // Arrow color
markers.Markers.Add(marker1);
gMapControl1.Overlays.Add(markers);
Also i want to thanks #rdoubleui , thanks you sir.
I hope this helps to everyone.
GMapOverlay markersOverlay;
private void AddOrUpdateMarker(string tag, double lat, double lng, Image NewImage)
{
Bitmap bmpmarker = (Bitmap)NewImage;
var marker = markersOverlay.Markers.FirstOrDefault(m => m.Tag == tag);
if(marker!=null)
{
markersOverlay.Markers.Remove(marker);
gMapControl1.Refresh();
marker = new GMarkerGoogle(new PointLatLng(lat, lng), bmpmarker);
marker.Tag = tag;
markersOverlay.Markers.Add(marker);
gMapControl1.Refresh();
}
else
{
marker = new GMarkerGoogle(new PointLatLng(lat, lng), bmpmarker);
marker.Tag = tag;
markersOverlay.Markers.Add(marker);
gMapControl1.Refresh();
}
}
The problem updating the image of that marker is that the Image property is not publicly accessible, therefore you can not update the image that way.
There are two possibilities: first one is to replace the marker reference with a new one giving you the opportunity to set a new image and copying the position of the current marker. However that is not the clean way as you unnecessarily create a whole bunch of references only to dispose them right away depending on the use case. If it's a one-time update, then this approach is fine.
The preferable way is to derive from GMapMarker as the Google marker does (you can use that as a template, leaving out the whole google specific icon logic). Are you familiar with the concept of deriving? It will require some more effort but will be worth it, could help with that.
Also your main reference is probably the project's github page.
EDIT
using System.Drawing;
public class GImageMarker : GMapMarker
{
public Bitmap Bitmap { get; set; }
public GImageMarker(PointLatLng p, Bitmap Bitmap)
: base(p)
{
this.Bitmap = Bitmap;
Size = new System.Drawing.Size(Bitmap.Width, Bitmap.Height);
Offset = new Point(-Size.Width / 2, -Size.Height);
}
public override void OnRender(Graphics g)
{
g.DrawImage(Bitmap, LocalPosition.X, LocalPosition.Y, Size.Width, Size.Height);
}
}
Hi WPF users and developers.
I am new to WPF, I am actually a little bit confused by the adorners. so far I understand that the Adorner are rendered on Top of the element in a specific layer (AdornerLayer) which is defined in higher level of the visual tree.
Firstly
How can I create an AdornerLayer for a specific element, for example a shape.
Secondly
I guess I need to add the AdornerLayer to the visual children of the Adorned element in order to get valid hit testing on the Adorners in the AdornerLayer. How to do It?
Thank you in advance.
first of all you have to crate a class derived from Adorner like below. you can use thumb or thumbs to be rendered. here is code that I have implement before but I have changed a little bit for simplicity so there may be mistakes.
public class ResizingAdorner : Adorner
{
Thumb topLeft;
// To store and manage the adorner's visual children.
VisualCollection visualChildren;
// Initialize the ResizingAdorner.
public ResizingAdorner(UIElement adornedElement)
: base(adornedElement)
{
visualChildren = new VisualCollection(this);
// Call a helper method to initialize the Thumbs
BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);
//these are events for the thumbs you may want it or not according to your needs//
topLeft.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(topLeft_PreviewMouseLeftButtonDown);
topLeft.DragDelta += new DragDeltaEventHandler(HandleTopLeft);
}
//override this method for rendering
protected override void OnRender(DrawingContext drawingContext)
{
Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
// Some arbitrary drawing implements.
SolidColorBrush RectBrush = new SolidColorBrush(Colors.Green);
RectBrush.Opacity = 0.3;
Pen renderPen = new Pen(RectBrush, 1.5);
// Draw Rectangle
drawingContext.DrawRectangle(null, renderPen, adornedElementRect);
}
// Arrange the Adorners.
protected override Size ArrangeOverride(Size finalSize)
{
Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
// desiredWidth and desiredHeight are the width and height of the element that's being adorned.
// These will be used to place the ResizingAdorner at the corners of the adorned element.
double desiredWidth = adornedElementRect.BottomRight.X;
double desiredHeight = adornedElementRect.BottomRight.Y;
// adornerWidth & adornerHeight are used for placement as well.
double adornerWidth = adornedElementRect.TopLeft.X;
double adornerHeight = adornedElementRect.TopLeft.Y;
//Arrange PathPoints with the helper Methods
topLeft.Arrange(new Rect(new Point(adornerWidth, adornerHeight), new Point(desiredWidth, desiredHeight)));
// Return the final size.
return finalSize;
}
// set some appearance properties, and add the elements to the visual tree//
void BuildAdornerCorner(ref Thumb cornerThumb, Cursor customizedCursor)
{
Path adornered = AdornedElement as Path;
if (cornerThumb != null) return;
cornerThumb = new Thumb();
// Set some arbitrary visual characteristics.
cornerThumb.Cursor = customizedCursor;
cornerThumb.Height = cornerThumb.Width = 10;
cornerThumb.Opacity = 1;
cornerThumb.Background = new SolidColorBrush(Colors.Black);
visualChildren.Add(cornerThumb);
}
// UnScale Adorners. this part is optional
public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
{
if (this.visualChildren != null)
{
foreach (var thumb in this.visualChildren.OfType<Thumb>())
{
thumb.RenderTransform
= new ScaleTransform(1 / ScaleXC, 1 / ScaleYC);
thumb.RenderTransformOrigin = new Point(0.5, 0.5);
}
}
return base.GetDesiredTransform(transform);
}
}
after that use this class as shown below to adorn element
AdornerLayer ADL; //consider this is as a public field somewhere in your class
...
...
ResizingAdorner A = new XamlEditor.ResizingAdorner(p); // p is an UIElement I my case
ADL = AdornerLayer.GetAdornerLayer(p);
ADL.Add(A);
I am having a really hard time waiting for the ChartPlotter in D3 to show itself, when using markers. Of course I am trying to plot a Gazillion records (well, 700,000 records). When using just a line, all is well (20 seconds or so). When using markers, we're talking 5 minutes. That's not acceptable.
Any ideas?
Here's what I have done, with explanations under it.
public static string MakeSimplePlot(double[][] xData, double[][] yData, string[] legend, string xAxisTitle, string yAxisTitle, bool[] showLines, bool[] showMarkers)
{
ChartPlotter plotter = new ChartPlotter();
plotter.MainHorizontalAxis = new HorizontalAxis();
plotter.MainVerticalAxis = new VerticalAxis();
HorizontalAxisTitle horizontalAxisTitle = new HorizontalAxisTitle();
horizontalAxisTitle.Content = xAxisTitle;
plotter.AddChild(horizontalAxisTitle);
VerticalAxisTitle verticalAxisTitle = new VerticalAxisTitle();
verticalAxisTitle.Content = yAxisTitle;
plotter.AddChild(verticalAxisTitle);
Color[] plotColors = new Color[13] { Colors.Blue, Colors.Red, Colors.Green, Colors.Chartreuse, Colors.Yellow, Colors.Violet, Colors.Tan, Colors.Silver, Colors.Salmon, Colors.Lime, Colors.Brown, Colors.Chartreuse, Colors.DarkGray };
for (int seriesCounter = 0; seriesCounter < legend.Count(); seriesCounter++)
{
DataFile clearedInputs = ClearExcess(new DataFile(xData[seriesCounter], yData[seriesCounter]));
xData[seriesCounter] = clearedInputs.time;
yData[seriesCounter] = clearedInputs.data;
var xDataSource = new EnumerableDataSource<double>(xData[seriesCounter]);
xDataSource.SetXMapping(x => x);
var yDataSource = new EnumerableDataSource<double>(yData[seriesCounter]);
yDataSource.SetYMapping(x => x);
CompositeDataSource plotSeries = new CompositeDataSource(xDataSource, yDataSource);
CirclePointMarker circlePointMarker = new CirclePointMarker();
circlePointMarker.Fill = new SolidColorBrush(plotColors[seriesCounter]);
circlePointMarker.Pen = new Pen(circlePointMarker.Fill, 0);
circlePointMarker.Size = (showMarkers[seriesCounter] == false) ? 0 : 8;
int lineWidth = (showLines[seriesCounter] == false) ? 0 : 2;
if (showMarkers[seriesCounter] == false)
{
plotter.AddLineGraph(plotSeries, new Pen(circlePointMarker.Fill, lineWidth), new PenDescription("Dummy"));
}
else
{
plotter.AddLineGraph(plotSeries, new Pen(circlePointMarker.Fill, lineWidth), circlePointMarker, new PenDescription("Dummy"));
}
}
UIParameters.plotWindow.mainGrid.Children.Clear();
UIParameters.plotWindow.mainGrid.RowDefinitions.Clear();
UIParameters.plotWindow.mainGrid.Children.Add(plotter);
plotter.Viewport.FitToView();
plotter.LegendVisible = false;
plotter.NewLegendVisible = false;
if (legend.Count() > 1)
{
ShowLegend(legend, plotColors);
}
UIParameters.plotWindow.WindowState = WindowState.Minimized;
UIParameters.plotWindow.WindowState = WindowState.Normal;
string filename = Path.ChangeExtension(Path.GetTempFileName(), "png");
RenderTargetBitmap targetBitmap = new RenderTargetBitmap((int)UIParameters.plotWindow.mainGrid.ActualWidth, (int)UIParameters.plotWindow.mainGrid.ActualHeight, 96d, 96d, PixelFormats.Default);
targetBitmap.Render(UIParameters.plotWindow.mainGrid);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(targetBitmap));
using (var fileStream = File.Open(filename, FileMode.OpenOrCreate))
{
encoder.Save(fileStream);
UIParameters.plotWindow.mainGrid.Clip = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
targetBitmap.Freeze();
if (targetBitmap != null) targetBitmap.Clear();
targetBitmap = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
return filename;
}
Explanations:
I hide the plotter legend and make my own using ShowLegend, since the legend does not show if it has only markers (am I wrong?)
I minimize and maximize the plot window, since otherwise the plot does not update, or it updates but does not get saved to a file. This also works if I move the window (I guess some kind of redraw event), but since the process is autoamatic, the user does not have any interaction. I tries Invalidate, to no avail. Ideas?
Thanks!
I wrote my own class to hide markers when they are off screen. It's a virtualization technique that speeds up performance tenfold when you don't have tons of markers on screen. It looks like this :
using System;
using System.Windows;
using System.Windows.Media;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Microsoft.Research.DynamicDataDisplay.PointMarkers;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay.Charts {
public class FilteredMarkerPointsGraph : MarkerPointsGraph {
public FilteredMarkerPointsGraph()
: base() {
;
}
public FilteredMarkerPointsGraph(IPointDataSource dataSource)
: base(dataSource) {
;
}
protected override void OnRenderCore(DrawingContext dc, RenderState state) {
// base.OnRenderCore
if (DataSource == null) return;
if (Marker == null) return;
var left = Viewport.Visible.Location.X;
var right = Viewport.Visible.Location.X + Viewport.Visible.Size.Width;
var top = Viewport.Visible.Location.Y;
var bottom = Viewport.Visible.Location.Y + Viewport.Visible.Size.Height;
var transform = Plotter2D.Viewport.Transform;
DataRect bounds = DataRect.Empty;
using (IPointEnumerator enumerator = DataSource.GetEnumerator(GetContext())) {
Point point = new Point();
while (enumerator.MoveNext()) {
enumerator.GetCurrent(ref point);
if (point.X >= left && point.X <= right && point.Y >= top && point.Y <= bottom)
{
enumerator.ApplyMappings(Marker);
Point screenPoint = point.DataToScreen(transform);
bounds = DataRect.Union(bounds, point);
Marker.Render(dc, screenPoint);
}
}
}
Viewport2D.SetContentBounds(this, bounds);
}
}
Make sure you call FilteredMarkerPointsGraph in the XAML instead of MarkerPointsGraph!
EDIT
I'm not sure what you need with the legend with markers, I've not actually used a legend in any of my graphs, but your solution seems to be fine.
Redrawing the plot is quite easy actually.
The best way that I have found to do this, is to have a Property in your code behind that represents the DataSource and bind the chart's DataSource to that property. Have your code behind implement INotifyPropertyChanged and call OnPropertyChanged every time you update or re-assign your data source. This will force the plotter to observe the binding and redraw your graph.
Example:
EnumerableDataSource<Point> m_d3DataSource;
public EnumerableDataSource<Point> D3DataSource {
get {
return m_d3DataSource;
}
set {
//you can set your mapping inside the set block as well
m_d3DataSource = value;
OnPropertyChanged("D3DataSource");
}
}
protected void OnPropertyChanged(PropertyChangedEventArgs e) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, e);
}
}
protected void OnPropertyChanged(string propertyName) {
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
And about your performance with your markers.. It's hard to pinpoint exactly what would be causing your performance issue, but my recommendation is to try using a different data source. I've been using EnumerableDataSource and it's always worked like a charm. Try bringing in your data in a singular object and setting the mapping in your set block like as shown above using:
value.SetYMapping(k => Convert.ToDouble(k.yData));
value.SetXMapping(k => Convert.ToDouble(k.xData));
The only thing you have to worry about is the mapping in Enumerable data source and D3 should handle the rest for you.
Well user can't probably see markers anyway when you are displaying the "Gazillion" of points: can't you switch the mode from line to markers when the zoom level is more reasonable ?
I'm trying to create 1 complex composite shape on an InkCanvas, but I must be doing something wrong, as what I was expecting to happen, is not. I've tried several different incarnations of accomplishing this.
So I have this method.
private void InkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
Stroke stroke = e.Stroke;
// Close the "shape".
StylusPoint firstPoint = stroke.StylusPoints[0];
stroke.StylusPoints.Add(new StylusPoint() { X = firstPoint.X, Y = firstPoint.Y });
// Hide the drawn shape on the InkCanvas.
stroke.DrawingAttributes.Height = DrawingAttributes.MinHeight;
stroke.DrawingAttributes.Width = DrawingAttributes.MinWidth;
// Add to GeometryGroup. According to http://msdn.microsoft.com/en-us/library/system.windows.media.combinedgeometry.aspx
// a GeometryGroup should work better at Unions.
_revealShapes.Children.Add(stroke.GetGeometry());
Path p = new Path();
p.Stroke = Brushes.Green;
p.StrokeThickness = 1;
p.Fill = Brushes.Yellow;
p.Data = _revealShapes.GetOutlinedPathGeometry();
selectionInkCanvas.Children.Clear();
selectionInkCanvas.Children.Add(p);
}
But this is what I get:
http://img72.imageshack.us/img72/1286/actual.png
So where am I going wrong?
TIA,
Ed
The problem is that the Geometry returned by stroke.GetGeometry() is a path around the stroke, so the area you're filling with yellow is just the middle of the stroke. You can see this more clearly if you make the lines thicker:
_revealShapes.Children.Add(stroke.GetGeometry(new DrawingAttributes() { Width = 10, Height = 10 }));
You can do what you want if you convert the list of stylus points to a StreamGeometry yourself:
var geometry = new StreamGeometry();
using (var geometryContext = geometry.Open())
{
var lastPoint = stroke.StylusPoints.Last();
geometryContext.BeginFigure(new Point(lastPoint.X, lastPoint.Y), true, true);
foreach (var point in stroke.StylusPoints)
{
geometryContext.LineTo(new Point(point.X, point.Y), true, true);
}
}
geometry.Freeze();
_revealShapes.Children.Add(geometry);