the code below draws two vertical lines on a canvas. these lines appear to be of different thickness on the screen although they are the same in code. i am tying to find a way to make them look as sharp as the border around the canvas. setting Path.SnapsToDevicePixels does not have any effect. The code is a contrived example, and in general the canvas that plots these lines can be nested deeper inside the visual tree.
thanks for any help
konstantin
<Window x:Class="wpfapp.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="10">
<Canvas x:Name="Canvas"
SizeChanged="OnCanvasSizeChanged" />
</Border>
</Grid>
</Window>
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace wpfapp
{
public partial class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
}
private void OnCanvasSizeChanged(object sender, SizeChangedEventArgs e)
{
StreamGeometry g = new StreamGeometry();
double h = this.Canvas.ActualHeight;
using (StreamGeometryContext c = g.Open())
{
c.BeginFigure(new Point(7, 0), false, false);
c.LineTo(new Point(7, h), true, false);
c.BeginFigure(new Point(14, 0), false, false);
c.LineTo(new Point(14, h), true, false);
}
g.Freeze();
Path p = new Path();
p.Data = g;
p.SnapsToDevicePixels = true;
p.Stroke = new SolidColorBrush(Colors.Black);
p.StrokeThickness = 1;
this.Canvas.Children.Clear();
this.Canvas.Children.Add(p);
}
}
}
need to use GuidelineSet:
protected override void OnRender(DrawingContext c)
{
base.OnRender(c);
Pen pen = new Pen(Brushes.Black, 1);
double h = this.ActualHeight;
double d = pen.Thickness / 2;
foreach (double x in new double[] { 7, 14 })
{
GuidelineSet g = new GuidelineSet(new double[] { x + d },
new double[] { 0 + d, h + d });
c.PushGuidelineSet(g);
c.DrawLine(pen, new Point(x, 0), new Point(x, h));
c.Pop();
}
}
Related
This code for a WPF works for one program. It is not working in a new program being created. It results in an exception --- "System.InvalidOperationException: 'Specified element is already the logical child of another element. Disconnect it first.'"
This is part of the .xaml code:
<Canvas x:Name="gCanvasPlotTop"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="0,0,0,0"
Width="500"
Height="150" />
<Canvas x:Name="gCanvasPlotBottom"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="0,0,0,0"
Width="500"
Height="150" />
Below is part of the .cs code.
Before I call dlgDisplayTwoXYPlots(), I define poXY:
Polyline poXYTop = new Polyline { Stroke = Brushes.Blue };
Polyline poXYBottom = new Polyline { Stroke = Brushes.Blue };
//------------------------------
public dlgDisplayTwoXYPlots(List<double> listdParamsTop,
List<Point> listPointsTop,
Polyline poXYTop,
List<double> listdParamsBottom,
List<Point> listPointsBottom,
Polyline poXYBottom)
{
InitializeComponent();
glistdParamsTop = listdParamsTop;
glistPointsTop = listPointsTop;
gpoXYTop = poXYTop;
glistdParamsBottom = listdParamsBottom;
glistPointsBottom = listPointsBottom;
gpoXYBottom = poXYBottom;
}//DlgPlotXY()
//------------------------------
//------------------------------
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Plot(gCanvasPlotTop, gpoXYTop, glistdParamsTop, glistPointsTop);
Plot(gCanvasPlotBottom, gpoXYBottom, glistdParamsBottom, glistPointsBottom);
}//Window_Loaded
//------------------------------
//------------------------------
private void Plot(Canvas canvas, Polyline poXY, List<double> listdParams, List<Point> listPoints)
{
int iii = 0;
int iNumOfPoints = (int)listdParams[iii++];
double dXmin = listdParams[iii++];
double dXmax = listdParams[iii++];
double dYmin = listdParams[iii++];
double dYmax = listdParams[iii++];
double dPlotWidth = dXmax - dXmin;
double dPlotHeight = dYmax - dYmin;
for (int ii = 0; ii < iNumOfPoints; ii++) {
var pointResult = new Point {
X = (listPoints[ii].X - dXmin) * canvas.Width / dPlotWidth,
Y = canvas.Height - (listPoints[ii].Y - dYmin) * canvas.Height / dPlotHeight
};
poXY.Points.Add(pointResult);
}
canvas.Children.Add(poXY);
//------------------------------
Why does a WPF canvas result in System.InvalidOperationException?
In MainWindow I had
Polyline poXYTop = new Polyline { Stroke = Brushes.Blue};
Polyline poXYBottom = new Polyline { Stroke = Brushes.Blue};
OnePlot(listdSignalXValues, listdSignalYValues, poXYTop, "Signal");
TwoPlots(listdSignalXValues, listdSignalYValues, poXYTop, "Signal DupTop",
listdSignalXValues, listdSignalYValues, poXYBottom, "Signal Bottom");
I had assumed that poXYTop would not return poXYTop with additional info as it was not a ref poXYTop. Now I know it does.
This works:
Polyline poXY = new Polyline { Stroke = Brushes.Blue };
Polyline poXYTop = new Polyline { Stroke = Brushes.Blue };
Polyline poXYBottom = new Polyline { Stroke = Brushes.Blue };
OnePlot(listdSignalXValues, listdSignalYValues, poXY, "Signal");
TwoPlots(listdSignalXValues, listdSignalYValues, poXYTop, "Signal DupTop",
listdSignalXValues, listdSignalYValues, poXYBottom, "Signal Bottom");
The following custom control
public class DummyControl : FrameworkElement
{
private Visual visual;
protected override Visual GetVisualChild(int index)
{
return visual;
}
protected override int VisualChildrenCount { get; } = 1;
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
{
var pt = hitTestParameters.HitPoint;
return new PointHitTestResult(visual, pt);
}
public DummyControl()
{
var dv = new DrawingVisual();
using (var ctx = dv.RenderOpen())
{
var penTransparent = new Pen(Brushes.Transparent, 0);
ctx.DrawRectangle(Brushes.Green, penTransparent, new Rect(0, 0, 1000, 1000));
ctx.DrawLine(new Pen(Brushes.Red, 3), new Point(0, 500), new Point(1000, 500));
ctx.DrawLine(new Pen(Brushes.Red, 3), new Point(500, 0), new Point(500, 1000));
}
var m = new Matrix();
m.Scale(0.5, 0.5);
RenderTransform = new MatrixTransform(m);
//Does work; but only the left top quater enters hit test
//var hv = new HostVisual();
//var vt = new VisualTarget(hv);
//vt.RootVisual = dv;
//visual = hv;
//Never enters hit test
visual = dv;
}
}
The xaml
<Window x:Class="MyNamespace.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyNamespace"
mc:Ignorable="d">
<Border Width="500" Height="500">
<local:DummyControl />
</Border>
</Window>
Display a green area with two red coordinate lines through the center. But its hit testing behavior is not understandable for me.
I put a breakpoint in the method HitTestCore but it never hits.
If I un-comment the code to use HostVisual and VisualTarget instead, it hits but only when the mouse is in the left top quater (indiciaed by the red lines given above)
How could the above being explained and how can I could make it work as expected (enters hit test on full range)?
(Originally, I just wanted to handle mouse events on the custom control. Some existing solutions pointed me to overriding the HitTestCore method. So if you could provide any idea that can let me handle mouse events, I don't have to make HitTestCore method working.)
Update
Clemen's answer is good if I decided to use DrawingVisual. However, when I use HostVisual and VisualTarget it is Not working without overriding HitTestCore, and even I do this, still only the top left quater will receive mouse events.
The original question also includes explainations. Also, the use of HostVisual allows me to run the render (time consuming in my real case) in another thread.
(Let me hightlight the code using HostVisual above)
//Does work; but only the left top quater enters hit test
//var hv = new HostVisual();
//var vt = new VisualTarget(hv);
//vt.RootVisual = dv;
//visual = hv;
Any idea?
UPDATE #2
Clemen's new answer is still not working for my purpose. Yes, all the visual area receives hit test. However, what I wanted is to have the full viewport to receive hit test. Which, in his case, is the blank area as he scaled the full visual to the visual area.
In order to establish a visual tree (and thus make hit testing work by default), you also have to call AddVisualChild. From MSDN:
The AddVisualChild method sets up the parent-child relationship
between two visual objects. This method must be used when you need
greater low-level control over the underlying storage implementation
of visual child objects. VisualCollection can be used as a default
implementation for storing child objects.
Besides that, your control should re-render whenever its size changes:
public class DummyControl : FrameworkElement
{
private readonly DrawingVisual visual = new DrawingVisual();
public DummyControl()
{
AddVisualChild(visual);
}
protected override int VisualChildrenCount
{
get { return 1; }
}
protected override Visual GetVisualChild(int index)
{
return visual;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
using (var dc = visual.RenderOpen())
{
var width = sizeInfo.NewSize.Width;
var height = sizeInfo.NewSize.Height;
var linePen = new Pen(Brushes.Red, 3);
dc.DrawRectangle(Brushes.Green, null, new Rect(0, 0, width, height));
dc.DrawLine(linePen, new Point(0, height / 2), new Point(width, height / 2));
dc.DrawLine(linePen, new Point(width / 2, 0), new Point(width / 2, height));
}
base.OnRenderSizeChanged(sizeInfo);
}
}
When your control uses a HostVisual and a VisualTarget it would still have to re-render itself when its size changes, and also call AddVisualChild to establish a visual tree.
public class DummyControl : FrameworkElement
{
private readonly DrawingVisual drawingVisual = new DrawingVisual();
private readonly HostVisual hostVisual = new HostVisual();
public DummyControl()
{
var visualTarget = new VisualTarget(hostVisual);
visualTarget.RootVisual = drawingVisual;
AddVisualChild(hostVisual);
}
protected override int VisualChildrenCount
{
get { return 1; }
}
protected override Visual GetVisualChild(int index)
{
return hostVisual;
}
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParams)
{
return new PointHitTestResult(hostVisual, hitTestParams.HitPoint);
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
using (var dc = drawingVisual.RenderOpen())
{
var width = sizeInfo.NewSize.Width;
var height = sizeInfo.NewSize.Height;
var linePen = new Pen(Brushes.Red, 3);
dc.DrawRectangle(Brushes.Green, null, new Rect(0, 0, width, height));
dc.DrawLine(linePen, new Point(0, height / 2), new Point(width, height / 2));
dc.DrawLine(linePen, new Point(width / 2, 0), new Point(width / 2, height));
}
base.OnRenderSizeChanged(sizeInfo);
}
}
You could now set a RenderTransform and still get correct hit testing:
<Border>
<local:DummyControl MouseDown="DummyControl_MouseDown">
<local:DummyControl.RenderTransform>
<ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
</local:DummyControl.RenderTransform>
</local:DummyControl>
</Border>
This will work for you.
public class DummyControl : FrameworkElement
{
protected override void OnRender(DrawingContext ctx)
{
Pen penTransparent = new Pen(Brushes.Transparent, 0);
ctx.DrawGeometry(Brushes.Green, null, rectGeo);
ctx.DrawGeometry(Brushes.Red, new Pen(Brushes.Red, 3), line1Geo);
ctx.DrawGeometry(Brushes.Red, new Pen(Brushes.Red, 3), line2Geo);
base.OnRender(ctx);
}
RectangleGeometry rectGeo;
LineGeometry line1Geo, line2Geo;
public DummyControl()
{
rectGeo = new RectangleGeometry(new Rect(0, 0, 1000, 1000));
line1Geo = new LineGeometry(new Point(0, 500), new Point(1000, 500));
line2Geo = new LineGeometry(new Point(500, 0), new Point(500, 1000));
this.MouseDown += DummyControl_MouseDown;
}
void DummyControl_MouseDown(object sender, MouseButtonEventArgs e)
{
}
}
Using DrawingContext.DrawingGeometry I'm drawing two triangles with common edge. I want this triangles to be filled, but not stroked with pen, because pen has thickness, and resulting triangles would be half thickness bigger than expected. Using code attached below I'm getting strange result (see picture) - there is a small gap between triangles. What am I doing wrong? Is there some better way, than drawing extra line on common edge?
XAML:
<Window x:Class="LearnDrawing.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LearnDrawing" xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="Window1"
Height="500"
Width="500">
<Grid>
<wpfApplication1:DrawIt Width="400" Height="400" />
</Grid>
</Window>
Code:
using System.Windows;
using System.Windows.Media;
namespace WpfApplication1
{
class DrawIt : FrameworkElement
{
VisualCollection visuals;
public DrawIt()
{
visuals = new VisualCollection(this);
this.Loaded += new RoutedEventHandler(DrawIt_Loaded);
}
void DrawIt_Loaded(object sender, RoutedEventArgs e)
{
var visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
var t1 = CreateTriangleGeometry(new Point(0, 0), new Point(200, 0), new Point(0, 200));
var t2 = CreateTriangleGeometry(new Point(200, 0), new Point(200, 200), new Point(0, 200));
dc.DrawGeometry(Brushes.Black, null, t1);
dc.DrawGeometry(Brushes.Black, null, t2);
}
visuals.Add(visual);
}
static PathGeometry CreateTriangleGeometry(Point aPt1, Point aPt2, Point aPt3)
{
var figure = new PathFigure();
figure.StartPoint = aPt1;
figure.Segments.Add(new PolyLineSegment(new []{aPt2, aPt3}, true));
var pg = new PathGeometry();
pg.Figures.Add(figure);
figure.IsClosed = true;
figure.IsFilled = true;
return pg;
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return visuals.Count;
}
}
}
}
Result:
You may set the EdgeMode of your visuals to EdgeMode.Aliased.
public DrawIt()
{
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
...
}
See also the Visual.VisualEdgeMode property.
I am creating a vertical scrolling game that utilizes a canvas. Though there is no performance issues just yet I am anticipating that there will be since I don't believe the canvas inherently offers virtualization. Is there such thing as a VirtualCanvas similar to the VirtualStackPanel? I want the same functionality where it only draws what is currently being displayed.
Right now my structure looks like this
<canvas Name="GameCanvas">
<canvas Name="StaticBG">
</canvas>
<canvas Name="DynamIcBG">
</canvas>
<canvas Name="CollidableObjects">
</canvas>
<canvas Name="Hud">
</canvas>
</canvas>
I would like to virtualize the DynamicBG and the CollidableObjects canvases
EDIT:
Can I possibly put all of my stuff inside of a VirtualStackPanel? Would that work?
<Canvas>
<Canvas>
<VirtualizingStackPanel>
<Canvas Name="Collidables">
<TextBlock>HOMES IT WORKS</TextBlock>
</Canvas>
</VirtualizingStackPanel>
</Canvas>
<Canvas>
<VirtualizingStackPanel>
<Canvas Name="DynamicBG">
<TextBlock>IT WORKS HOMES</TextBlock>
</Canvas>
</VirtualizingStackPanel>
</Canvas>
</Canvas>
The Canvas is NOT Virtualized - at least, not in the way you'd typically define Virtualization.
The following LINQPad-ready harness will show this - even if the "bugs" are outside of the window extents, the contained bugs will continue to render.
void Main()
{
var bugCount = 4;
var window = System.Windows.Markup.XamlReader.Parse(someXaml)
as System.Windows.Window;
window.Show();
var canvas = window.FindName("canvas")
as System.Windows.Controls.Canvas;
var bugs = new List<Bug>();
var r = new Random();
if(canvas != null)
{
for(int i=0; i < bugCount; i++)
{
var bug = new Bug();
bug.Height = 50;
bug.Width = 50;
bug.Location = new System.Windows.Point(
r.Next(0, (int)canvas.Width),
r.Next(0, (int)canvas.Height));
canvas.Children.Add(bug);
bugs.Add(bug);
}
}
var dt = new System.Windows.Threading.DispatcherTimer();
dt.Tick += (o,e) => MoveIt(bugs);
dt.Interval = TimeSpan.FromMilliseconds(100);
dt.Start();
}
public void MoveIt(List<Bug> bugs)
{
var r = new Random();
foreach (var bug in bugs)
{
var dir = r.Next(0,4);
switch (dir)
{
case 0:
bug.Location =
new System.Windows.Point(bug.Location.X + 1, bug.Location.Y);
break;
case 1:
bug.Location =
new System.Windows.Point(bug.Location.X - 1, bug.Location.Y);
break;
case 2:
bug.Location =
new System.Windows.Point(bug.Location.X, bug.Location.Y + 1);
break;
case 3:
bug.Location =
new System.Windows.Point(bug.Location.X, bug.Location.Y - 1);
break;
}
}
}
public class Bug : System.Windows.Controls.UserControl
{
private static int _bugCounter = 0;
public Bug()
{
this.BugId = _bugCounter++;
}
public int BugId {get; private set;}
private System.Windows.Point _location;
public System.Windows.Point Location
{
get { return _location; }
set
{
_location = value;
System.Windows.Controls.Canvas.SetLeft(this, value.X);
System.Windows.Controls.Canvas.SetTop(this, value.Y);
InvalidateVisual();
}
}
protected override void OnRender(System.Windows.Media.DrawingContext ctx)
{
base.OnRender(ctx);
Console.WriteLine("Yo, bug #{0} is rendering!", BugId);
ctx.DrawRectangle(
System.Windows.Media.Brushes.Red,
new System.Windows.Media.Pen(System.Windows.Media.Brushes.White, 1),
new System.Windows.Rect(Location, this.RenderSize));
var formattedText = new System.Windows.Media.FormattedText(
this.BugId.ToString(),
System.Globalization.CultureInfo.CurrentCulture,
System.Windows.FlowDirection.LeftToRight,
new System.Windows.Media.Typeface("Arial"),
12,
System.Windows.Media.Brushes.White);
ctx.DrawText(
formattedText,
new System.Windows.Point(Location.X + 10, Location.Y + 10));
}
}
string someXaml =
#"
<Window
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
Width=""320""
Height=""240""
>
<Canvas
x:Name=""canvas""
Width=""640""
Height=""480""
Background=""LightGray""
/>
</Window>
";
The answer to this question is that the Canvas fakes virtualization. If you load a bunch of objects on the canvas and they don't need to be drawn on the screen they are overlooked. I say fakes virtualization because this comes from my own investigation and research. I have yet to find a single article relating to this topic.
If you however were to load 200 images that were 100x100 onto a canvas in various spots both on and off the screen. You will notice that as you move the canvas around you will experience no delay.
I have an application where I draw polygons on inkCanvas. I would like to add a function where after clicking one of drawn polygons it would be in editing mode and then I could change some of this proporties for example Fill.
I wrote this code but it selects all area from top left of inkcanvas to the end of my polygon but I need only polygon area.
Xaml:
<DockPanel>
<ToolBarTray DockPanel.Dock="Left" Orientation="Vertical" IsLocked="True">
<ToolBar Padding="2">
<RadioButton x:Name="rbDraw" IsChecked="False"
ToolTip="Add Rectangle" Margin="3" Checked="rbDraw_Checked">
<Rectangle Width="20" Height="12" Stroke="Blue"
Fill="LightBlue" />
</RadioButton>
<RadioButton x:Name="rbSelect" IsChecked="False"
ToolTip="Select" Margin="3">
<Path Stroke="Blue" Fill="LightBlue" Width="20" Height="20">
<Path.Data>
<PathGeometry Figures="M5,15L 10,0 15,15 12,15 12,20 8,20 8,15Z">
<PathGeometry.Transform>
<RotateTransform CenterX="10" CenterY="10" Angle="45"/>
</PathGeometry.Transform>
</PathGeometry>
</Path.Data>
</Path>
</RadioButton>
</ToolBar>
</ToolBarTray>
<Border BorderThickness="1" BorderBrush="Black">
<InkCanvas x:Name="canvas1" MouseMove="canvas1_MouseMove" PreviewMouseLeftButtonDown="canvas1_PreviewMouseLeftButtonDown" EditingMode="None">
</InkCanvas>
</Border>
</DockPanel>
Code behind
private Polyline polyline;
private PointCollection polylinePoints;
private bool drawOnMove = false;
private List<Polygon> polygons = new List<Polygon>();
public MainWindow()
{
InitializeComponent();
}
private void canvas1_MouseMove(object sender, MouseEventArgs e)
{
if (drawOnMove && (bool)rbDraw.IsChecked)
{
polyline.Points = polylinePoints.Clone();
polyline.Points.Add(e.GetPosition(canvas1));
}
}
private void canvas1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (rbDraw.IsChecked ?? false)
{
if (e.OriginalSource is Ellipse)
{
canvas1.Children.Remove((Ellipse)e.OriginalSource);
canvas1.Children.Remove(polyline);
Polygon tmpPolygon = new Polygon();
tmpPolygon.StrokeThickness = 2;
tmpPolygon.Stroke = Brushes.Black;
tmpPolygon.Points = polylinePoints.Clone();
polylinePoints.Clear();
polygons.Add(tmpPolygon);
drawOnMove = false;
rbDraw.IsChecked = false;
tmpPolygon.Fill = Brushes.Gray;
canvas1.Children.Add(tmpPolygon);
rbSelect.IsChecked = true;
}
else
{
polylinePoints.Add(e.GetPosition(canvas1));
polyline.Points = polylinePoints.Clone();
if (polyline.Points.Count == 1)
{
Ellipse el = new Ellipse();
el.Width = 10;
el.Height = 10;
el.Stroke = Brushes.Black;
el.StrokeThickness = 2;
el.Fill = new SolidColorBrush { Color = Colors.Yellow };
el.Margin =
new Thickness(left: polyline.Points[0].X - el.Width / 2, top: polyline.Points[0].Y - el.Height / 2, right: 0, bottom: 0);
canvas1.Children.Add(el);
}
drawOnMove = true;
}
}
else if (rbSelect.IsChecked ?? false)
{
if (e.OriginalSource is Polygon)
{
Polygon pol = (Polygon)e.OriginalSource;
canvas1.Select(new UIElement[] { pol });
}
}
}
private void rbDraw_Checked(object sender, RoutedEventArgs e)
{
polyline = new Polyline();
polylinePoints = new PointCollection();
polyline.StrokeThickness = 2;
polyline.Stroke = Brushes.Black;
canvas1.Children.Add(polyline);
}
Edit: I edited my code my first sample was a little too general. Selecting polygon looks like this but I want to select only polygon area.
I know this is a really old post, but I had this exact same problem and solved it by translating the points before the conversion to a polygon, and then back again, as such:
StrokeCollection sc = InkCanvas1.GetSelectedStrokes();
Rect r = sc.GetBounds();
PointCollection pc = new PointCollection();
//Shift all the points by the calculated extent of the strokes.
Matrix mat = new Matrix();
mat.Translate(-r.Left, -r.Top);
sc.Transform(mat, false);
foreach (Stroke s in sc)
{
foreach (Point p in s.StylusPoints){pc.Add(p);}
}
Polygon poly_ = new Polygon();
//Shift the polygon back to original location
poly_.SetValue(InkCanvas.LeftProperty, r.Left);
poly_.SetValue(InkCanvas.TopProperty, r.Top);
poly_.Points = pc;
InkCanvas1.Children.Add(poly_);
This makes the select box only equal to the size of the polygon:
Ok I solved my problem I added a custom Dependency Property to my Window which holds selected polygon. To show that polygon is selected I change its opacity.
public static readonly DependencyProperty SelectedShapeProperty =
DependencyProperty.Register
("SelectedShape", typeof(Polygon), typeof(MainWindow));
public Polygon Polygon
{
set{SetValue(SelectedShapeProperty, value);}
get{return (Polygon) GetValue(SelectedShapeProperty);}
}
private void canvas1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (rbDraw.IsChecked ?? false)
{
if (e.OriginalSource is Ellipse)
{
canvas1.Children.Remove((Ellipse)e.OriginalSource);
canvas1.Children.Remove(polyline);
Polygon tmpPolygon = new Polygon();
tmpPolygon.StrokeThickness = 2;
tmpPolygon.Stroke = Brushes.Black;
tmpPolygon.Points = polylinePoints.Clone();
polylinePoints.Clear();
polygons.Add(tmpPolygon);
drawOnMove = false;
rbDraw.IsChecked = false;
tmpPolygon.Fill = Brushes.Gray;
canvas1.Children.Add(tmpPolygon);
rbSelect.IsChecked = true;
}
else
{
polylinePoints.Add(e.GetPosition(canvas1));
polyline.Points = polylinePoints.Clone();
if (polyline.Points.Count == 1)
{
Ellipse el = new Ellipse();
el.Width = 10;
el.Height = 10;
el.Stroke = Brushes.Black;
el.StrokeThickness = 2;
el.Fill = new SolidColorBrush { Color = Colors.Yellow };
InkCanvas.SetLeft(el, polyline.Points[0].X - el.Width / 2);
InkCanvas.SetTop(el, polyline.Points[0].Y - el.Height / 2);
el.Margin =
new Thickness(left: polyline.Points[0].X - el.Width / 2, top: polyline.Points[0].Y - el.Height / 2, right: 0, bottom: 0);
canvas1.Children.Add(el);
}
drawOnMove = true;
}
}
else if (rbSelect.IsChecked ?? false)
{
if (e.OriginalSource is Polygon && Polygon == null)
{
Shape s = (Shape)e.OriginalSource;
Polygon = (Polygon)s;
Polygon.Opacity = 0.75;
}
else if (e.OriginalSource is Polygon && Polygon != null)
{
Polygon.Opacity = 1;
Polygon = null;
Shape s = (Shape)e.OriginalSource;
Polygon = (Polygon)s;
Polygon.Opacity = 0.75;
}
else if (Polygon != null)
{
Polygon.Opacity = 1;
Polygon = null;
}
}
else
{
if(Polygon != null)
Polygon = null;
}
}
You can select any drawing on your canvas by setting
drawCanvas.EditingMode = InkCanvasEditingMode.Select;
and then just clicking on this drawing.