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");
Related
I am pretty new to WPF... So I need to bind a lines X1 and Y1 properties with an ellipses canvas.left and canvas.top property......
In XAML it works fine...
XAML Code
<Ellipse x:Name="tst" Width="40" Height="40" Canvas.Left="150" Canvas.Top="150" Stroke="Blue" StrokeThickness="2" MouseMove="adjustRoute"/>
<Line X1="{Binding ElementName=tst, Path=(Canvas.Left)}" Y1="{Binding ElementName=tst, Path=(Canvas.Top)}" X2="300" Y2="200" Stroke="Blue" StrokeThickness="2"/>
But I need to do it in the Code Behind Using C#
So I did this
temp = new Line();
tempe = new Ellipse();
tempe.Fill = Brushes.Transparent;
tempe.Stroke = Brushes.Blue;
tempe.StrokeThickness = 1;
tempe.Width = 20;
tempe.Height = 20;
Canvas.SetLeft(tempe, currentPoint.X-10);
Canvas.SetTop(tempe, currentPoint.Y-10);
tempe.MouseMove += adjustRoute;
Binding binding = new Binding { Source = tempe, Path = new PropertyPath(Canvas.LeftProperty), UpdateSourceTrigger=UpdateSourceTrigger.PropertyChanged };
temp.SetBinding(Line.X1Property, binding);
Binding binding2 = new Binding { Source = tempe, Path = new PropertyPath(Canvas.TopProperty), UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged };
temp.SetBinding(Line.Y1Property, binding2);
temp.Stroke = Brushes.Blue;
temp.StrokeThickness = 2;
temp.X1 = currentPoint.X;
temp.Y1 = currentPoint.Y;
temp.X2 = currentPoint.X + 200;
temp.Y2 = currentPoint.Y + 200;
testcanv.Children.Add(temp);
testcanv.Children.Add(tempe);
But it doesn't update the line position when I move the ellipse(in XAML it updates)....
Here currentPoint is the point I am capturing with my mouse click to draw the shapes during runtime and adjustRoute is the function to move them on drag
What did I do wrong here?
Thanks
Make sure you create the Line and Ellipse elements only once, and assign the Bindings only once. Assign the MouseMove handler to the Canvas instead of the Ellipse:
private Line line;
private Ellipse ellipse;
public MainWindow()
{
InitializeComponent();
line = new Line
{
Stroke = Brushes.Blue,
StrokeThickness = 2
};
ellipse = new Ellipse
{
Stroke = Brushes.Blue,
StrokeThickness = 1,
Width = 20,
Height = 20,
Margin = new Thickness(-10)
};
Binding xBinding = new Binding
{
Source = ellipse,
Path = new PropertyPath(Canvas.LeftProperty)
};
line.SetBinding(Line.X1Property, xBinding);
Binding yBinding = new Binding
{
Source = ellipse,
Path = new PropertyPath(Canvas.TopProperty)
};
line.SetBinding(Line.Y1Property, yBinding);
testcanv.Children.Add(line);
testcanv.Children.Add(ellipse);
testcanv.Background = Brushes.Transparent;
testcanv.MouseMove += adjustRoute;
}
private void adjustRoute(object sender, MouseEventArgs e)
{
var p = e.GetPosition(testcanv);
Canvas.SetLeft(ellipse, p.X);
Canvas.SetTop(ellipse, p.Y);
}
There is a Grid, which is filled dynamically with Image controls in code behind(Sorry for that).
Grid has 1 column, many pages, each page has 1 Border with Image as Border.Child inside. What I need is to Zoom (Scale) my Image in Grid when Button.Click event fires. I used Scale Transform with the Image before, but I didn't manage to bind Grid element Image with the Click handler.
Please suggest, how I can zoom images inside grid, step by step.
Thanks in advance!
Yes, I know this is horrible, should be done in different way, I'm still learning, how to do this right.
Method, that generates Grid. After that ZOOM click method ( only for zoom, there is another method for zoom out)
public void RefreshView(List<TiffImage> tiffImageList)
{
try
{
if (tiffImageList.Count == 0)
return;
SetControlSizes();
gridImageList.Children.Clear();
gridImageList.RowDefinitions.Clear();
gridImageList.ColumnDefinitions.Clear();
RowDefinitionCollection rd = gridImageList.RowDefinitions;
ColumnDefinitionCollection cd = gridImageList.ColumnDefinitions;
cd.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (int i = 0; i < tiffImageList.Count; i++)
{
rd.Add(new RowDefinition() { Height = GridLength.Auto });
}
int rowIndex = 0;
foreach (var tiffImage in tiffImageList)
{
Image imageListViewItem = new Image();
imageListViewItem.Margin = new Thickness(0, 0, 0, 0);
RenderOptions.SetBitmapScalingMode(imageListViewItem, BitmapScalingMode.HighQuality);
imageListViewItem.Name = $"Image{tiffImage.index.ToString()}";
imageListViewItem.Source = tiffImage.image;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.Stretch = Stretch.Uniform;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
Border border = new Border();
border.BorderBrush = Brushes.LightGray;
border.BorderThickness = new Thickness(1);
Thickness margin = border.Margin;
border.Margin = new Thickness(20, 10, 20, 10);
border.Child = imageListViewItem;
Grid.SetColumn(border, 0);
Grid.SetRow(border, rowIndex);
gridImageList.Children.Add(border);
rowIndex++;
}
}
catch (Exception ex)
{
throw ex;
}
}
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
if ((imgViewerScaleTransform.ScaleX + 0.2) > 3 || (imgViewerScaleTransform.ScaleY + 0.2) > 3)
return;
imgViewerScaleTransform.ScaleX += 0.2;
imgViewerScaleTransform.ScaleY += 0.2;
image.LayoutTransform = imgViewerScaleTransform;
}
}
Here is a very simple version of a scalable ItemsControl in a ScrollViewer.
It might be improved in many ways. First of all, you should replace handling Button Click events by binding the Button Command properties to ZoomIn and ZoomOut commands in the view model (left out for brevity).
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Images}">
<ItemsControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Scale}" ScaleY="{Binding Scale}"/>
</ItemsControl.LayoutTransform>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="LightGray">
<Image Source="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content=" + " Click="ZoomInButtonClick"/>
<Button Content=" - " Click="ZoomOutButtonClick"/>
</StackPanel>
</Grid>
The code behind:
public partial class MainWindow : Window
{
private readonly ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
foreach (string imageFile in Directory.EnumerateFiles(
#"C:\Users\Public\Pictures\Sample Pictures", "*.jpg"))
{
viewModel.Images.Add(new BitmapImage(new Uri(imageFile)));
}
}
private void ZoomInButtonClick(object sender, RoutedEventArgs e)
{
viewModel.Scale *= 1.1;
}
private void ZoomOutButtonClick(object sender, RoutedEventArgs e)
{
viewModel.Scale /= 1.1;
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ImageSource> Images { get; }
= new ObservableCollection<ImageSource>();
private double scale = 1;
public double Scale
{
get { return scale; }
set
{
scale = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Scale)));
}
}
}
I managed to find an ugly, horrible solution, sorry for that. Use it only, if there is no any alternative. Please, add answers with better solutions. Thanks for your time!
We need to add (in code behind) Image.LayoutTransform defined as ScaleTransform:
imageListViewItem.LayoutTransform = new ScaleTransform();
I used // to emphasize changes in method below. Also, most changes happened in Zoom/ZoomOut methods below.
public void RefreshView(List<TiffImage> tiffImageList)
{
try
{
if (tiffImageList.Count == 0)
return;
SetControlSizes();
gridImageList.Children.Clear();
gridImageList.RowDefinitions.Clear();
gridImageList.ColumnDefinitions.Clear();
RowDefinitionCollection rd = gridImageList.RowDefinitions;
ColumnDefinitionCollection cd = gridImageList.ColumnDefinitions;
cd.Add(new ColumnDefinition() { Width = GridLength.Auto });
for (int i = 0; i < tiffImageList.Count; i++)
{
rd.Add(new RowDefinition() { Height = GridLength.Auto });
}
int rowIndex = 0;
foreach (var tiffImage in tiffImageList)
{
Image imageListViewItem = new Image();
imageListViewItem.Margin = new Thickness(0, 0, 0, 0);
RenderOptions.SetBitmapScalingMode(imageListViewItem, BitmapScalingMode.HighQuality);
imageListViewItem.Name = $"Image{tiffImage.index.ToString()}";
imageListViewItem.Source = tiffImage.image;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.Stretch = Stretch.Uniform;
imageListViewItem.VerticalAlignment = VerticalAlignment.Center;
imageListViewItem.HorizontalAlignment = HorizontalAlignment.Center;
// Add HERE!!!
imageListViewItem.LayoutTransform = new ScaleTransform();
//
Border border = new Border();
border.BorderBrush = Brushes.LightGray;
border.BorderThickness = new Thickness(1);
Thickness margin = border.Margin;
border.Margin = new Thickness(20, 10, 20, 10);
border.Child = imageListViewItem;
Grid.SetColumn(border, 0);
Grid.SetRow(border, rowIndex);
gridImageList.Children.Add(border);
rowIndex++;
}
}
catch (Exception ex)
{
throw ex;
}
}
We take all elements from the Grid and Scale(Zoom) them, then we clear the Grid.Children and fill it with new Items.
private void btnZoom_Click(object sender, RoutedEventArgs e)
{
List<Border> list = new List<Border>();
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
imgViewerScaleTransform.CenterX = 0.5;
imgViewerScaleTransform.CenterY = 0.5;
if ((imgViewerScaleTransform.ScaleX + 0.2) > 3 || (imgViewerScaleTransform.ScaleY + 0.2) > 3)
return;
imgViewerScaleTransform.ScaleX += 0.2;
imgViewerScaleTransform.ScaleY += 0.2;
image.LayoutTransform = imgViewerScaleTransform;
border.Child = image;
list.Add(border);
}
gridImageList.Children.Clear();
foreach (Border border in list)
{
gridImageList.Children.Add(border);
}
}
private void btnZoomOut_Click(object sender, RoutedEventArgs e)
{
List<Border> list = new List<Border>();
foreach (UIElement item in gridImageList.Children)
{
Border border = (Border)item;
Image image = (Image)border.Child;
var imgViewerScaleTransform = (ScaleTransform)(image.LayoutTransform);
imgViewerScaleTransform.CenterX = 0.5;
imgViewerScaleTransform.CenterY = 0.5;
if ((imgViewerScaleTransform.ScaleX - 0.2) < 0.8 || (imgViewerScaleTransform.ScaleY - 0.2) < 0.8)
return;
imgViewerScaleTransform.ScaleX += -0.2;
imgViewerScaleTransform.ScaleY += -0.2;
image.LayoutTransform = imgViewerScaleTransform;
border.Child = image;
list.Add(border);
}
gridImageList.Children.Clear();
foreach (Border border in list)
{
gridImageList.Children.Add(border);
}
}
I have drawn an ellipse using an EllipsePoints array which defines the height width and color of the ellipse.
Then using a for loop to get the position of the ellipse using the ellipse points and a random number to set its position:
Random rand = new Random();
Int32 randomNumber = rand.Next(0, 310);
Int32 randomNumber2 = rand.Next(0, 500);
for (int j = 0; j < 60; j++)
{
ellipsePoints[j] = new Ellipse() { Width = 20, Height = 20, Fill = Brushes.Red };
canvas1.Children.Add(ellipsePoints[j]);
}
for (int i = 0; i < 60; i++)
{
Canvas.SetLeft(ellipsePoints[i], randomNumber2);
Canvas.SetTop(ellipsePoints[i], randomNumber);
}
What could I do to make the ellipse vanish after a certain amount of time and then appear in another random location?
There are 2 important aspects to this question.
Timer - In WPF we use the System.Windows.Threading.DispatcherTimer.
Remove elements- One way is to maintain a copy of the UI element before adding it to the Canvas. I have saved it in a class variable so that I can later remove it from the canvas using the following method
PaintCanvas.Children.Remove(ellipse);
Create you WPF and add a canvas called PaintCanvas
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="889" Width="1080">
<Canvas Name="PaintCanvas">
<Button Canvas.Left="46" Canvas.Top="274" Content="Button" Height="23" Name="button1" Width="75" Click="button1_Click" />
</Canvas >
</Window>
The Code. I have documented it.
public partial class MainWindow : Window
{
int loopCounter;
private System.Windows.Threading.DispatcherTimer timer;
Random rand = new Random();
Ellipse ellipse = null;
public MainWindow()
{
InitializeComponent();
//Initialize the timer class
timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1); //Set the interval period here.
timer.Tick += timer1_Tick;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
loopCounter = 10;
timer.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
//Remove the previous ellipse from the paint canvas.
PaintCanvas.Children.Remove(ellipse);
if (--loopCounter == 0)
timer.Stop();
//Add the ellipse to the canvas
ellipse=CreateAnEllipse(20,20 );
PaintCanvas.Children.Add(ellipse);
Canvas.SetLeft(ellipse, rand.Next(0, 310));
Canvas.SetTop(ellipse, rand.Next(0, 500));
}
// Customize your ellipse in this method
public Ellipse CreateAnEllipse(int height,int width)
{
SolidColorBrush fillBrush = new SolidColorBrush() { Color = Colors.Red };
SolidColorBrush borderBrush = new SolidColorBrush() { Color = Colors.Black };
return new Ellipse()
{
Height = height,
Width = width,
StrokeThickness = 1,
Stroke = borderBrush,
Fill = fillBrush
};
}
}
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.
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();
}
}