Invalid WPF canvas mouse up event - c#

I tried to create a screenshot function, set the "Canvas" background as a picture, press and raise the mouse to draw a rectangle, and capture the content inside the rectangle, but my mouse up event is always invalid. I searched all kinds of information and tried, all of them seem to be invalid
public Bitmap GetScreenSnapshot()
{
try
{
System.Drawing.Rectangle rc = SystemInformation.VirtualScreen;
var bitmap = new Bitmap(rc.Width, rc.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics memoryGrahics = Graphics.FromImage(bitmap))
{
memoryGrahics.CopyFromScreen(rc.X, rc.Y, 0, 0, rc.Size, CopyPixelOperation.SourceCopy);
}
return bitmap;
}
catch (Exception)
{
}
return null;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var screenSnapshot = GetScreenSnapshot();
var bmp = ImageProcessing.ToBitmapSource(screenSnapshot);
bmp.Freeze();
Clipper clipper = new Clipper();
clipper.bitmap = screenSnapshot;//将图片传过去
clipper.Background = new ImageBrush(bmp);
clipper.Show();
}
Here, I use a button to create a Window (Clipper) in the main window, and provide the obtained desktop image to Canvas
<Window x:Class="MyOCR_WPF.Clipper"
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:MyOCR_WPF"
mc:Ignorable="d"
Title="Clipper" Height="450" Width="800" WindowStyle="None" WindowState="Maximized" WindowStartupLocation="CenterScreen">
<Canvas x:Name="canvas" MouseMove="canvas_MouseMove" MouseDown="canvas_MouseDown" MouseUp="canvas_MouseUp" Focusable="True" Background="Transparent">
</Canvas>
Its mouse up event is always invalid

Mouseup is invalid because you have drawn a Rectangle on cancas.When mouseup is triggered on Rectangle, the mouseup of Cancas cannot be triggered.
I tested and found no PreviewMouseUp on Cancas either.
I used a different solution to your problem.
view
<Grid>
<Canvas x:Name="canvas" Focusable="False" />
<Border MouseMove="canvas_MouseMove" MouseDown="canvas_MouseDown" MouseUp="canvas_MouseUp" Background="Transparent"/>
</Grid>

Related

How do I get a Rectangle drawn on an adorner to scale with the Image element its bound to when the window size is changed?

I'm using a rectangle drawn on an adorner to mark a region of interest on an image. The issue is that if I resize the window, the rectangle doesn't change size.
I'm new to WPF, so I've done a bunch of research, googling what I can with multiple different search terms. I actually just learned adorners that way, and I've gotten this far on that, but I've hit a wall on how to finish this last piece. I know that my problem is based in the size of the rectangle, but I don't know what to capture/look for to adjust it, since wpf resizes the actual image object on window resize, so there's no scale factor to look at.
Here's the XAML for the application I'm testing things in.
<Window x:Class="TestingAdorners.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:TestingAdorners"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid ClipToBounds="True">
<AdornerDecorator>
<Image Name="Btn" Source="nyan.png" Stretch="Uniform"/>
</AdornerDecorator>
</Grid>
</Window>
The adorner class:
class RoiAdorner : Adorner
{
public Rect rectangle = new Rect();
public RoiAdorner(UIElement adornedElement) : base(adornedElement)
{
rectangle.Height = 30;
rectangle.Width = 100;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen = new Pen(Brushes.Green, 5);
drawingContext.DrawRectangle(null, pen, rectangle);
}
}
And the Xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AdornerLayer.GetAdornerLayer(Btn).Add(new RoiAdorner(Btn));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
The desired result is that the rectangle scales with the image object so that it always covers the same region of the image. The problem is I don't know how to capture a scale factor to scale it up and down as the window resizes.
Update: After thinking through Frenchy's suggestion I realized the answer is simply: "Normalize your coordinates"
you just adapt your render method like this:
class RoiAdorner : Adorner
{
public double factorX = 0d;
public double factorY = 0d;
public Rect rectangle = new Rect();
public RoiAdorner(UIElement adornedElement) : base(adornedElement)
{
rectangle.Height = 30;
rectangle.Width = 100;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
if (factorY == 0)
factorY = rectangle.Height / AdornedElement.DesiredSize.Height;
if (factorX == 0)
factorX = rectangle.Width / AdornedElement.DesiredSize.Width;
var r = new Rect(new Size(AdornedElement.DesiredSize.Width * factorX, AdornedElement.DesiredSize.Height * factorY));
//Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
drawingContext.DrawRectangle(null, new Pen(Brushes.Red, 5), r);
}
this.AdornedElement.DesiredSize gives you the size of image.
The approach I would use is to render the picture and rectangle to the same thing. Then that one thing is stretched, scaled or whatever.
One way to do this would be to use a DrawingImage. Drawing methods are extremely efficient if rather low level.
<Grid ClipToBounds="True">
<AdornerDecorator>
<Image Name="img" Stretch="Uniform">
<Image.Source>
<DrawingImage PresentationOptions:Freeze="True">
<DrawingImage.Drawing>
<DrawingGroup>
<ImageDrawing Rect="0,0,595,446" ImageSource="DSC00025.jpg"/>
<GeometryDrawing Brush="Green">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,100,30" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</AdornerDecorator>
</Grid>
Another is with a visualbrush. Controls inherit from visual - this is somewhat higher level coding.
<Grid ClipToBounds="True">
<AdornerDecorator>
<Rectangle Name="rec">
<Rectangle.Fill>
<VisualBrush Stretch="Uniform">
<VisualBrush.Visual>
<Grid Height="446" Width="595">
<Image Source="DSC00025.jpg" Stretch="Fill"/>
<Rectangle Height="30" Width="100" Fill="Green"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
</AdornerDecorator>
</Grid>
Note that both of these are quick and dirty illustrations to give you the idea. The image I picked at random off my hard drive is sized 446 * 595. You could calculate sizes or bind or stretch as suits your requirement best.

how to check if an element is going out of its parent in c# wpf

I am creating shapes at run time on canvas in my app and except all the shapes, ellipse is going out of canvas. How do I restrict it to canvas? All other shapes are contained in canvas because of the control points at their vertices. How do I keep a check as to not let ellipse go out of canvas without clipping. I have used ClipToBounds and it doesn't meet my needs.
Also, an alternate solution is if I can add a controlpoint at the left side of ellipse of radiusX property. I can't add a controlpoint to left side of radiusX on ellipse. If you could help me with either of that?
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusYProperty, 1, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
//radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
//radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
//shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
Here is a quick example of what i would do. It could be improved on and the code is mainly written to be easy to read and follow. It also does not handle the possibility to if the shape's size is bigger than the Canvas (not sure if that is a use case in your project).
For the example I used the "Loaded" event on the Canvas, to reset the position before drawing. You would want this check before you draw the Ellipse object.
private void TestCanvas_Loaded(object sender, RoutedEventArgs e)
{
//canvas = 450 x 800
Ellipse test_ellipse = new Ellipse();
test_ellipse.Width = 100;
test_ellipse.Height = 100;
test_ellipse.Fill = Brushes.Red;
Canvas.SetLeft(test_ellipse, 700);
Canvas.SetTop(test_ellipse, -500);
Reset_Ellipse_Bounds(TestCanvas, ref test_ellipse);
TestCanvas.Children.Add(test_ellipse);
}
private void Reset_Ellipse_Bounds(Canvas myCanvas, ref Ellipse myEllipse)
{
var left = Canvas.GetLeft(myEllipse);
var top = Canvas.GetTop(myEllipse);
//handle too far right
if (left + myEllipse.Width > myCanvas.ActualWidth)
Canvas.SetLeft(myEllipse, myCanvas.ActualWidth - myEllipse.Width);
//handle too far left
if(left < 0)
Canvas.SetLeft(myEllipse, 0);
//handle too far up
if (top < 0)
Canvas.SetTop(myEllipse, 0);
//handle too far down
if (top + myEllipse.Height > myCanvas.ActualHeight)
Canvas.SetTop(myEllipse, myCanvas.ActualHeight - myEllipse.Height);
}
For Completeness the XAML:
<Grid>
<Canvas x:Name="TestCanvas" Loaded="TestCanvas_Loaded" />
</Grid>
The idea is to check the bounding box against the Canvas edges. There are ways to improve this, but i figured the simplest solution is easier to follow.
Within each if statement you could add more logic or a method to do further processing, but this should answer the general question of knowing if it is outside the parent.
Just set ClipToBounds="true" to its father control, it avoids the canvas to be drawn outside of it.
In my case I set it to Grid as followed :
<Grid x:Name="MainGrid" Background="WhiteSmoke" ClipToBounds="true">
<Canvas Margin="10" Background="Transparent"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel">
<Canvas x:Name="canvas" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform"/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="canvas2" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform2"/>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
</Grid>

Why is my Image.Source still black after setting it's stream?

So I am trying to add a "screenshot" onto the source of the Image by saving the Bitmap to the stream and the setting the Source to the BitmapImage.
But for some reason the Image control is just black it doesnt show anything.
Why is that?
//this.Hide();
//Create a black bitmap with the correct width and height and use it as a "Canvas" that we will
//be performing a bit-block transfer on. (Bitmap is mutable)
Bitmap printscreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height);
//To perform a bit-block transfer we need the object to be a "Graphics" object.
Graphics g = Graphics.FromImage(printscreen);
//Perform the bit-block transfer and alter the image.
//Source being the screens pixels, and the destination is the black bitmap.
g.CopyFromScreen(0, 0, 0, 0, printscreen.Size);
//Create a temporary memory stream for the image.
using (MemoryStream mStream = new MemoryStream())
{
//Save the graphics object (image) in the memory stream. with a specified format.
printscreen.Save(mStream, ImageFormat.Bmp);
var ImageSource = new BitmapImage();
ImageSource.BeginInit();
ImageSource.StreamSource = mStream;
ImageSource.EndInit();
ImageBox.Source = ImageSource;
}
As you can see I am setting the Source property to the ImageSource yet it's black.
WPF
<Window x:Class="eh.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:eh"
mc:Ignorable="d"
Title="MainWindow" Height="450" Loaded="MainWindow_OnLoaded" WindowStyle="None" ResizeMode="NoResize" Background="Black" WindowState="Maximized" WindowStartupLocation="Manual" Left="0" Top="0" Width="800">
<Grid>
<Image x:Name="ImageBox" HorizontalAlignment="Left" VerticalAlignment="Top" Height="1920" Width="1080"/>
</Grid>
</Window>
I forgot to add the CacheOption so I added
ImageSource.CacheOption = BitmapCacheOption.OnLoad;

When changing the program size, the image box should automatically expand

I have a program size and if I increase this at the corners is to increase the ImageBox automatically with.Unfortunately, this does not work yet, since an error occurs.Has anyone a solution to this, if I change the form size, which also increases the ImageBox automatically.I would like the size of the ImageBox then also larger, if I change the size of the form.
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
imgPreview.Source = zoom(imgPreview, new System.Drawing.Size(Convert.ToInt32(this.Width), Convert.ToInt32(this.Height))); // Mistake is here
}
System.Drawing.Image zoom(System.Drawing.Image img, System.Drawing.Size size)
{
Bitmap bmp = new Bitmap(img, img.Width + (img.Width * size.Width / 100), img.Height + (img.Height * size.Height / 100));
Graphics g = Graphics.FromImage(bmp);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
return bmp;
}
<UserControl x:Class="Vorschau.UCOxyplotPreview"
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"
xmlns:local="clr-namespace:Vorschau"
mc:Ignorable="d" Height="379.573" Width="539.634" SizeChanged="UserControl_SizeChanged">
<Image x:Name="imgPreview" HorizontalAlignment="Left" Height="350" Margin="109,20,0,0" VerticalAlignment="Top" Width="350"/>
</UserControl>
Try to remove the properties HorizontalAlignment, Height, Margin, VerticalAlignment and Width. The Image should resize automatically when done.
//edit
Just to show you a minimalistic example. When using this code your Image size will change your Window size is changing. That's why I think the problem is in your Window xaml where you use your UserControl with the Image.
<Window x:Class="WpfApplication2.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:WpfApplication2"
mc:Ignorable="d"
xmlns:viewModel="clr-namespace:WpfApplication2">
<Image Source="IMG_20160831_105257778.jpg" />
</Window>

How to place an image by its center, not top-left corner?

I have an image (inside canvas). I need to specify the coordinates of the image in text box, and I want the image center to be in this point (not its top-left corner). How can I do that?
Update: I don't need to place my image in the center of the canvas. I want the position of the image to be defined by image center. Example, I have a code: <Image source"..." canvas.Left="0" canvas.Top="0">, and this point (0,0) means that the image center is in the top-left corner of the canvas.
Set the position using Attached properties:
Canvas.Left
Canvas.Top
If you want to place at center, set
Canvas.SetLeft(image, (canva.Width - Image.Width) / 2);
Canvas.SetTop(image, (canva.Height- Image.Height) / 2);
Also look Align images in WPF canvas in center
Update
Canvas.SetLeft(image, Image.Width / 2);
Canvas.SetTop(image, Image.Height / 2);
You can set it from behaviors for example
<Window x:Class="WpfSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:wpfSample="clr-namespace:WpfSample"
Title="MainWindow"
Width="800"
Height="800"
Background="Gray">
<Border Width="400"
Height="400"
HorizontalAlignment="Center"
VerticalAlignment="Center"
BorderBrush="Black"
BorderThickness="2">
<Canvas >
<Rectangle Fill="OrangeRed" Width="150" Height="150">
<i:Interaction.Behaviors>
<wpfSample:CenterBehavior/>
</i:Interaction.Behaviors>
</Rectangle>
</Canvas>
</Border>
</Window>
and the behavior (raw sample)
public class CenterBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
base.OnAttached();
UpdatePosition();
AssociatedObject.SizeChanged += OnSizeChanged;
}
private void UpdatePosition()
{
Canvas.SetLeft(AssociatedObject, -AssociatedObject.Width/2);
Canvas.SetTop(AssociatedObject, -AssociatedObject.Height/2);
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdatePosition();
}
}
Just subtract half of your Image.Width value your current Canvas.Left value and subtract half of your Image.Height value your current Canvas.Top value.

Categories