Attach Mouse Events to Rect WPF - c#

I have the following program that creates Rectangle using Rect object. I want to attach Mouse events to newly created Rect. How can I do that? Please help. Here's the code below.
XAML
<Window x:Class="TestDrawing.MainWindow"
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:TestDrawing"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Canvas>
<Button x:Name="BtnAddRectangle" Content="Add Rectngle" Click="BtnAddRectangle_Click" Height="20"/>
</Canvas>
</StackPanel>
<Canvas Name="canvas">
</Canvas>
</Grid>
CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TestDrawing
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
bool drag = false;
Point startPoint;
public MainWindow()
{
InitializeComponent();
}
// this creates and adds rectangles dynamically
private void BtnAddRectangle_Click(object sender, RoutedEventArgs e)
{
DrawingVisual drawingVisual = new DrawingVisual();
// Retrieve the DrawingContext in order to create new drawing content.
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Create a rectangle and draw it in the DrawingContext.
Rect rect = new Rect(new Size(150, 100));
drawingContext.DrawRectangle(Brushes.LightBlue, null,rect);
drawingContext.Close();
canvas.Children.Add(new VisualHost { Visual = drawingVisual });
foreach (UIElement child in canvas.Children)
{
//Not working
child.MouseDown += rectangle_MouseDown;
child.MouseMove += rectangle_MouseMove;
}
}
private void rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
// start dragging
drag = true;
// save start point of dragging
startPoint = Mouse.GetPosition(canvas);
}
private void rectangle_MouseMove(object sender, MouseEventArgs e)
{
// if dragging, then adjust rectangle position based on mouse movement
if (drag)
{
Rectangle draggedRectangle = sender as Rectangle;
Point newPoint = Mouse.GetPosition(canvas);
double left = Canvas.GetLeft(draggedRectangle);
double top = Canvas.GetTop(draggedRectangle);
Canvas.SetLeft(draggedRectangle, left + (newPoint.X - startPoint.X));
Canvas.SetTop(draggedRectangle, top + (newPoint.Y - startPoint.Y));
startPoint = newPoint;
}
}
private void rectangle_MouseUp(object sender, MouseButtonEventArgs e)
{
// stop dragging
drag = false;
}
}
public class VisualHost : UIElement
{
public Visual Visual { get; set; }
protected override int VisualChildrenCount
{
get { return Visual != null ? 1 : 0; }
}
protected override Visual GetVisualChild(int index)
{
return Visual;
}
}
}

Here's a quote from the MSDN page on Using DrawingVisual Objects:
The DrawingVisual is a lightweight drawing class that is used to render shapes, images, or text. This class is considered lightweight because it does not provide layout or event handling, which improves its performance. For this reason, drawings are ideal for backgrounds and clip art.
If you need event handling, why not just use the existing Rectangle class? Your existing code can be easily changed to use this class instead of your custom VisualHost.
private void BtnAddRectangle_Click(object sender, RoutedEventArgs e)
{
Rectangle rect = new Rectangle() { Fill = Brushes.LightBlue, Width = 150, Height = 100 };
canvas.Children.Add(rect);
foreach (UIElement child in canvas.Children)
{
child.MouseDown += rectangle_MouseDown;
child.MouseMove += rectangle_MouseMove;
}
}

Related

Performance difference between running program by Visual Studio and by local ( using WriteableBitmap to draw in WPF)

I have a WPF application for drawing (Like Microsoft Paint) and I use WriteableBitmap and subscribe mouse event to do that.
It works perfectly and the ink tracks my mouse immediately when I run the program by Visual Studio no matter in debug mode or release mode
However, I get bad performance when I drawing by running the program exe file (\bin\Release\net5.0-windows\oooo.exe OR \bin\Debug\net5.0-windows\oooo.exe). the ink is drawn slower and has some delay after the mouse moving when drawing.
The difference (click to view gifs):
Running the program by Visual Studio
Running the program exe file
Even I build this application on local, I got the same bad result. Is there any thing I can fix this?
The code is as follows:
MainWindow.xaml
<Window x:Class="paint_test.MainWindow"
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"
mc:Ignorable="d"
Title="MainWindow" Height="900" Width="1600"
Loaded="Window_Loaded">
<Grid>
<Canvas Name="MainCanvas" Background="Bisque"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace paint_test
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
MainCanvas.Children.Add(new PaintCanvas((int)MainCanvas.ActualWidth, (int)MainCanvas.ActualHeight));
}
}
}
PaintCanvas.cs
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace paint_test
{
public class PaintCanvas : System.Windows.Controls.Image
{
WriteableBitmap fullWriteableBmp;
int paintingSize = 10;
Color paintingColor = Colors.Blue;
public PaintCanvas(int width, int height)
{
RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.NearestNeighbor);
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
fullWriteableBmp = BitmapFactory.New(width, height);
Source = fullWriteableBmp;
MouseDown += PaintCanvas_MouseDown;
MouseMove += PaintCanvas_MouseMove;
}
private void PaintCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed) return;
DrawPixel(e);
}
private void PaintCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed) return;
DrawPixel(e);
}
private void DrawPixel(MouseEventArgs e)
{
int x1 = (int)e.GetPosition(this).X - (paintingSize / 2);
int y1 = (int)e.GetPosition(this).Y - (paintingSize / 2);
int x2 = (int)e.GetPosition(this).X + (paintingSize / 2);
int y2 = (int)e.GetPosition(this).Y + (paintingSize / 2);
fullWriteableBmp.FillEllipse(x1, y1, x2, y2, paintingColor);
}
}
}

WPF Get sender's window

as the title says I'm looking for a way to get the Window object the sender is in.
In my situation I have a window in which there is only one button:
<Window x:Class="WpfApp1.MainWindow"
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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="200">
<Grid>
<Button Click="Button_Click"/>
</Grid>
</Window>
By clicking the button, it generates a new window without borders and a red rectangle inside it:
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Window window = new Window();
window.Height = window.Width = 100;
window.WindowStyle = WindowStyle.None;
Rectangle rectangle = new Rectangle();
rectangle.Height = rectangle.Width = 50;
rectangle.Fill = new SolidColorBrush(Colors.Red);
Grid grid = new Grid();
grid.Children.Add(rectangle);
window.Content = grid;
window.Show();
}
}
}
My idea was to capture the MouseDown event on the rectangle to move the window (using DragMove()), unfortunately I have no idea how to get the Window object on which the rectangle that invoked the event is located, any idea to do it?.
You can use MouseDown event after new Rectangle and find Window through Parent property like this:
private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
var rect = sender as Rectangle;
FrameworkElement framework = rect;
while (framework != null && (framework as Window)==null && framework.Parent!= null)
{
framework = framework.Parent as FrameworkElement;
}
if(framework is Window window)
{
//here you has found the window
}
}

Refreshing canvas

I have a question about refreshing canvas. There is .Refresh() or .Clear() in WinForms.But what about WPF?
I watched some methods how to do it, but it didn't help me at all.
The situation is that my canvas contains textBox and Button. When I click button I can draw some ellipses.
And I need to clear these ellipses after my every click on button but without clearing textBox and Button from this Canvas!
My WPF Xaml:
<Window x:Class="draw.CreateEllipse"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Create" Height="768" Width="1024" WindowStartupLocation="CenterScreen" Name="Create" Closed="Create_Closed">
<Canvas Name="can" Background="White" MouseDown="can_MouseDown">
<TextBox Name="txbNumber" Height="34" Canvas.Left="797" TextWrapping="Wrap" Text="5" Canvas.Top="76" Width="209" FontSize="18"/>
<Button Name="btnCreate" Content="Create" Canvas.Left="797" Canvas.Top="130" Width="209" Height="66" FontSize="18" Click="btnCreate_Click"/>
</Canvas>
My C# :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace draw
{
/// <summary>
/// Логика взаимодействия для CreateGraph.xaml
/// </summary>
public partial class Create : Window
{
public Create()
{
InitializeComponent();
}
int n, i;
Ellipse[] v;
Rectangle rect = new Rectangle();
SolidColorBrush solidcolor = new SolidColorBrush();
private void btnCreate_Click(object sender, RoutedEventArgs e)
{
n = Convert.ToInt16(txbNumber.Text);
v = new Ellipse[n];
//can.Children.Clear();
}
private void Create_Closed(object sender, EventArgs e)
{
Application.Current.Shutdown();
}
private void can_MouseDown(object sender, MouseButtonEventArgs e)
{
solidcolor.Color = Colors.Transparent;
//Ellipse myEllipse = new Ellipse();
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
for (i = 0; i < n; i++)
{
v[i] = new Ellipse();
mySolidColorBrush.Color = Colors.Transparent;
v[i].Fill = mySolidColorBrush;
v[i].StrokeThickness = 2;
v[i].Stroke = Brushes.Black;
v[i].Width = 75;
v[i].Height = 75;
v[i].Margin = new Thickness(e.GetPosition(can).X, e.GetPosition(can).Y, 0, 0);
can.Children.Add(v[i]);
}
if (n <= 0)
return;
n--;
}
}
}
you can separate your elements with this code:
var can2 = can.Children.OfType<Ellipse>();
above code select all of Ellipses in your Canvas "can".
and you can remove them from your Canvas Like this:
foreach (var element in can2)
{
can.Children.Remove(element);
}
Now you have no ellipse in your Canvas.
Hope this helps you.

How to make an object to change color with MouseEnter and MouseLeave WPF

Well this is my WPF project and the problem is in this user control, People.xaml.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
namespace Projet
{
public partial class People : UserControl
{
Random rnd = new Random();
string filename = "C:\\Users\\Kristen\\peoples.txt";
public People()
{
InitializeComponent();
dataFromFile();
}
void dataFromFile()
{
if (!File.Exists(filename))
return;
var source = File.ReadLines(filename)
.Select(line => line.Split(' '))
.Select(m => new { name = m[0], length = int.Parse(m[1]) })
.OrderBy(x => x.length);
int k = 200;
foreach (var data in source)
{
Rectangle r = new Rectangle ();
r.Height = data.length;
r.Width = 25;
r.Fill = new SolidColorBrush(Colors.Red);
Canvas.SetLeft(r, k);
Canvas.SetBottom(r, 200);
board.Children.Add(r);
k += 50;
}
}
private void board_MouseEnter_1(object sender, MouseEventArgs e)
{
board.Background = Brushes.Blue;
//I have tried with r.Fill = new SolidColorBrush(Colors.Red); but it wont work either
dataFromFile();
}
private void juur_MouseLeave_1(object sender, MouseEventArgs e)
{
board.Background = Brushes.Yellow;
}
IT takes data out of file. In file there are people names and their heights. It makes those heights to bar chart using rectangles on canvas. I want to make the color of rectangles change with MouseEnter and MouseLeave but it just changes my canvas background color. I'll but my People.xaml here too just in case.
<UserControl x:Class="Project.People"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Canvas Background="Yellow" x:Name="board" MouseEnter="board_MouseEnter_1" MouseLeave="board_MouseLeave_1">
</Canvas>
You have to attach the event handlers to each Rectangle, not to the Canvas. Remove them here:
<Canvas Background="Yellow" x:Name="board"/>
Then add them to each Rectangle:
foreach (var data in source)
{
var r = new Rectangle();
r.MouseEnter += Rectangle_MouseEnter;
r.MouseLeave += Rectangle_MouseLeave;
...
}
...
private void Rectangle_MouseEnter(object sender, MouseEventArgs e)
{
((Rectangle)sender).Fill = Brushes.Blue;
}
private void Rectangle_MouseLeave(object sender, MouseEventArgs e)
{
((Rectangle)sender).Fill = Brushes.Yellow;
}
Having said that I'd strongly suggest to throw away all that code and use an MVVM approach instead. Perhaps start reading about ItemsControl and Data Templating.
There is absolutely no reason to use behind-code for this, just use a style and the IsMouseOver trigger (although in yoru case obviously change the setters to modify/replace the appropriate child control properties):
<Canvas>
<Canvas.Style>
<Style>
<Setter Property="Canvas.Background" Value="Yellow"/>
<Style.Triggers>
<Trigger Property="Canvas.IsMouseOver" Value="True">
<Setter Property="Canvas.Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Canvas.Style>
</Canvas>

DrawingContext.DrawGeometry draws adjacent geometries with gap

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.

Categories