Click and drag box over image - WPF - c#

I'm having trouble getting the selectionBox not to center. In the image below, I tried to draw a box around the word Final Bill (accented ms paint box) by clicking and dragging but the resulting selectionBox (dashed line in red) that outputs always starts in the center. The rectangle values that are calculated and saved in the mouseUp event are all correct, suggesting perhaps a XAML display issue?
I am very knew to WPF/XAML and front end stuff in general.
EDIT: By placing just the selectionBox in a <Canvas> tag I was able to get it almost working. It no longer centers but the start point appears to be twice as far from the left and top borders as the mouse is when clicked.
XAML
<DockPanel Width="Auto" Margin="225,65,5,5">
<Border x:Name="img_Border" ClipToBounds="True" Height="Auto" Width="Auto" Margin="0,0,0,0" VerticalAlignment="Top">
<Grid>
<ScrollViewer>
<Image x:Name="img_Box" ClipToBounds="True" MouseMove="img_Box_MouseMove" MouseWheel="img_Box_MouseWheel">
</Image>
</ScrollViewer>
<Rectangle x:Name="selectionBox" Visibility="Collapsed" Stroke="Red" StrokeThickness="3" StrokeDashArray="3,1">
</Rectangle>
</Grid>
</Border>
</DockPanel>
I can get the selectionBox to work correctly if I use <Canvas> tags. but that causes the image to not fit inside the <DockPanel>.
c#
public MainWindow()
{
InitializeComponent();
img_Box.MouseLeftButtonDown += img_Box_MouseLeftButtonDown;
img_Box.MouseLeftButtonUp += img_Box_MouseLeftButtonUp;
img_Box.MouseMove += img_Box_MouseMove;
Point mouseDownPos;
}
private void img_Box_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
img_Box.CaptureMouse();
var tt = (TranslateTransform)((TransformGroup)img_Box.RenderTransform).Children.First(tr => tr is TranslateTransform);
double BoxX = (e.GetPosition(img_Box).X / img_Box.ActualWidth) * 1;
double BoxY = (e.GetPosition(img_Box).Y / img_Box.ActualHeight) * 1;
double xValue = Math.Round((BoxX * img_Box.Source.Width), 0);
double yValue = Math.Round((BoxY * img_Box.Source.Height), 0);
StartDrag = new System.Windows.Point(xValue, yValue);
mouseDownPos.X = (int)xValue;
mouseDownPos.Y = (int)yValue;
Canvas.SetLeft(selectionBox, xValue);
Canvas.SetTop(selectionBox, yValue);
selectionBox.Width = 0;
selectionBox.Height = 0;
selectionBox.Visibility = Visibility.Visible;
}
private void img_Box_MouseMove(object sender, MouseEventArgs e)
{
double x = e.GetPosition(img_Box).X;
double y = e.GetPosition(img_Box).Y;
double BoxX = (x / img_Box.ActualWidth) * 1;
double BoxY = (y / img_Box.ActualHeight) * 1;
double xValue = Math.Round((BoxX * img_Box.Source.Width), 0);
double yValue = Math.Round((BoxY * img_Box.Source.Height), 0);
if (mouseDownPos.X < xValue)
{
Canvas.SetLeft(selectionBox, mouseDownPos.X);
selectionBox.Width = xValue - mouseDownPos.X;
}
else
{
Canvas.SetLeft(selectionBox, xValue);
selectionBox.Width = mouseDownPos.X - xValue;
}
if (mouseDownPos.Y < yValue)
{
Canvas.SetTop(selectionBox, mouseDownPos.Y);
selectionBox.Height = yValue - mouseDownPos.Y;
}
else
{
Canvas.SetTop(selectionBox, yValue);
selectionBox.Height = mouseDownPos.Y - yValue;
}
}

Not sure if I fully understand what exactly you are trying to accomplish here but if you just need a border around that text and not to allow the user to move it around with the mouse, set the image to be the background of the Grid and then partition the Grid so that one of the grid cells is directly above that text you want to place the border around. Place the border within that cell and If all the bill images are exactly the same size and have the text in exactly the same location it will always be directly over that text as long as you do not allow users to resize the image... If you want the scrolling just put the entire grid within the ScrollViewer control.
Something like this:
<Grid>
<Grid.Background>
<VisualBrush TileMode="None" >
<VisualBrush.Visual>
<Image Source="{Binding ImageSource}"/>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/> //Distance from the left edge of the image to the right edge of the border around the "Final Bill" text.
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="150"/> //Distance from the top of the image to the top of the desired location of the border around the "Final Bill" text.
<RowDefinition Height="50"/> //Desired height of the border around the "Final Bill" text.
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.Row="1" BorderThickness="1"/>
</Grid>

Related

Cover an image with black and reveal parts of the image using mouse

So I am creating a "Map Viewer" for a program I am working on. Basically I want to display a map and have it resize the image to fit in the grid. For this I am using Viewbox to hold the image and resize it. I was attempting to use this code to reveal the map but it does not center the circle on the mouse and it does not retain the revealed portions of the map (it ONLY reveals where the mouse is and not where it has been).
Here is the XAML:
<Viewbox x:Name="MapHolder" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="1" Grid.RowSpan="7" Margin="25,5,25,15">
<Image x:Name="SelectedMap" Source="/wizard_dungeon.jpg" Stretch="Uniform" MouseMove="SelectedMap_MouseMove" >
<Image.OpacityMask>
<VisualBrush Stretch="None" >
<VisualBrush.Visual>
<Ellipse Width="400" Height="400" StrokeThickness="1" Fill="Black"/>
</VisualBrush.Visual>
<VisualBrush.RelativeTransform>
<TransformGroup>
<TranslateTransform x:Name="OpacityFilterTransform" X="1" Y="1"/>
</TransformGroup>
</VisualBrush.RelativeTransform>
</VisualBrush>
</Image.OpacityMask>
</Image>
</Viewbox>
And the code-behind:
private void SelectedMap_MouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this);
var height = MapHolder.ActualHeight;
var width = MapHolder.ActualWidth;
// with the position values, interpolate a TranslateTransform for the opacity mask
var transX = position.X / width;
var transY = position.Y / height;
OpacityFilterTransform.X = transX - 0.5;
OpacityFilterTransform.Y = transY - 0.5;
}
I want there to basically be a Image under a Black screen and I can erase the black screen to reveal the image in the areas I have erased.

Draw rectangle over SwapChainPanel

I'm receiving a live video from a DJI drone in a SwapChainPanel and I'm using template matching from OpenCV to find an image inside the frames of the video.
I want to draw a rectangle when the subgimage is detected, but there are white spaces in both sides of the frames and I don't know how to get the size of those or align the image to the left and top.
Here is the XAML:
<SwapChainPanel x:Name="swapChainPanel" Grid.Column="8" Grid.ColumnSpan="4" Grid.Row="2" Grid.RowSpan="11">
<Canvas>
<Rectangle x:Name="rectFiducial" Visibility="Collapsed" Stroke="Red"></Rectangle>
<Rectangle Stroke="Blue" Width="400" Height="400"></Rectangle>
</Canvas>
</SwapChainPanel>
Here is the code when I try to draw the rectangle with the results of the Template Matching:
SoftwareBitmap inputBitmap = SoftwareBitmap.CreateCopyFromBuffer(CryptographicBuffer.CreateFromByteArray(imageData),
BitmapPixelFormat.Rgba8,
imageWidth,
imageHeight);
if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8
|| inputBitmap.BitmapAlphaMode != BitmapAlphaMode.Premultiplied)
{
inputBitmap = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
int x = 0, y = 0, matchMethod = 0;
double matchResult = 0;
Stopwatch testWatch = new Stopwatch();
testWatch.Start();
await txtMatchMethod.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
int.TryParse(txtMatchMethod.Text, out matchMethod);
});
OpenCV.TemplateMatching(inputBitmap, template, matchMethod, out x, out y, out matchResult);
testWatch.Stop();
await txtMatchTime.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
txtMatchTime.Text = testWatch.ElapsedMilliseconds.ToString();
txtMatchResult.Text = matchResult.ToString();
double swapX = x * swapChainPanel.ActualWidth / imageWidth;
double swapY = y * swapChainPanel.ActualHeight / imageHeight;
double swapWidth = template.PixelWidth * swapChainPanel.ActualWidth / imageWidth;
double swapHeight = template.PixelHeight * swapChainPanel.ActualHeight / imageHeight;
rectFiducial.SetValue(Canvas.LeftProperty, swapX);
rectFiducial.SetValue(Canvas.TopProperty, swapY);
rectFiducial.Width = swapWidth;
rectFiducial.Height = swapHeight;
rectFiducial.Visibility = Visibility.Visible;
});
}
There is an event where I can get the bytes of the image and I tried to display it in an Image inside a Canvas, but the image didn't stretch to fit the canvas size.
Edit
I realized that I'm calculating the position based on the width and height of the SwapChainPanel, but I have to calculate it with the width and height of the image inside.
So I would need to get the data of the resized image that is inside the SwapChainPanel and the padding of the left side.
Well, I couldn't find a solution for the SwapChainPanel, but I found a solution with the image and the canvas.
I was trying to make something like this:
<Canvas x:Name="canvas" Grid.Column="8" Grid.ColumnSpan="4" Grid.Row="4" Grid.RowSpan="11">
<Image x:Name="imgDrone" HorizontalAlignment="Left"/>
<Rectangle x:Name="rectFiducial" Visibility="Collapsed" Stroke="Red"></Rectangle>
<Rectangle Stroke="Blue" Width="400" Height="400"></Rectangle>
</Canvas>
But I couldn't stretch the image, it was displayed with it's real size.
So instead of declaring the image inside the Canvas I just took it out and set it's column and row with the same values of the canvas.
Like this:
<Image x:Name="imgDrone" HorizontalAlignment="Left" Grid.Column="8" Grid.ColumnSpan="2" Grid.Row="2" Grid.RowSpan="11"/>
<Canvas x:Name="canvas" Grid.Column="8" Grid.ColumnSpan="4" Grid.Row="4" Grid.RowSpan="11">
<Rectangle x:Name="rectFiducial" Visibility="Collapsed" Stroke="Red"></Rectangle>
<Rectangle Stroke="Blue" Width="400" Height="400"></Rectangle>
</Canvas>

Can I draw a grid of dots within a custom WPF canvas control?

I've built a custom control that inherits from canvas. It uses the ArrangeOverride method to add drag drop functionality to all child elements, and this includes grid snapping to 50 pixels and some other custom behaviour such as persisting the element positions.
What I'd really like to do is extend this so that while dragging, it shows a grid of dots or crosses in the background so that you can see where the snap points are.
However, I can't modify the template of the control because it's a panel. I tried making it a custom control that contains a canvas, and passing the IEnumerable items source through, but it became really tricky to pick up on the collection changing, and also the collection then contained models and not framework elements.
So I'm not sure which path I should be taking. It feels as though there may be a very simple solution that I've overlooked, so I'm open to suggestions!
Thanks in advance.
I've done something similar once: drawing the grid lines on the UserControl, and then adding controls on top of it. It was for a simple scheduler app used to assign who will be working what shift in the current month. I had to have column headers (days) and row headers (people) and a scrollable grid inside, to which I could add/remove controls. I don't know to what extent my solution will help you, but here it is.
I use HeaderedScrollViewer from here. UCHeader is a UserControl with rotated textboxes with dates. The magic happens in the UC_GridLines.
<Grid>
<lib:HeaderedScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="AliceBlue">
<lib:HeaderedScrollViewer.TopHeader>
<lib:UCHeader Name="ucHeader"/>
</lib:HeaderedScrollViewer.TopHeader>
<lib:HeaderedScrollViewer.LeftHeader>
<Border BorderBrush="Black" BorderThickness="1,2,1,1">
<StackPanel Orientation="Vertical" Name="spRows" />
</Border>
</lib:HeaderedScrollViewer.LeftHeader>
<Grid Name="gridContentAll">
<Border Canvas.ZIndex="1" Background="Transparent"
BorderThickness="1,1,1,2" BorderBrush="Transparent">
<Grid ClipToBounds="True" Name="gridContent" Background="Transparent" />
</Border>
<Border Canvas.ZIndex="0" BorderThickness="1,1,1,2" BorderBrush="Black" Background="White">
<lib:UC_GridLines Name="ucGridLines" BorderThickness="0"/>
</Border>
</Grid>
</lib:HeaderedScrollViewer>
</Grid>
Important here are:
Setting Background="Transparent" for the control that is containing child controls
Setting ZIndex correctly, so the controls are shown over the grid.
Setting SnapsToDevicePixels="True" and UseLayoutRounding="True" on the root element (e.g. the WIndow)
UC_GridLines in its .xaml file has nothing (only <Grid></Grid>). Drawing the grid lines happens in the codebehind:
protected override void OnRender(DrawingContext drawingContext)
{
// VM contains data of the grid, used to draw gridlines
// such as number of days etc.
if (this.VM == null)
{
base.OnRender(drawingContext);
return;
}
double dpiFactor = 1;
try
{
Matrix m = PresentationSource.FromVisual(this)
.CompositionTarget.TransformToDevice;
dpiFactor = 1 / m.M11;
}
catch { }
Pen pen = new Pen(Brushes.Black, 1 * dpiFactor);
double halfPenWidth = pen.Thickness / 2;
GuidelineSet guidelines = new GuidelineSet();
double width = this.VM.Days.Count * this.VM.DayWidth - 16 * (this.VM.DayWidth / 24);
double height = this.VM.RowHeight * rowCount;
for (int i = 1; i < this.VM.Days.Count; i++)
{
guidelines.GuidelinesX.Add(i * this.VM.DayWidth + halfDashPenWidth);
}
for (int i = 1; i < rowCount; i++)
{
guidelines.GuidelinesY.Add(i * this.VM.RowHeight + halfPenWidth);
}
drawingContext.PushGuidelineSet(guidelines);
for (int i = 1; i < this.VM.Days.Count; i++)
{
drawingContext.DrawLine(dashpen, new Point(i * this.VM.DayWidth, 0), new Point(i * this.VM.DayWidth, height));
}
for (int i = 1; i < rowCount; i++)
{
drawingContext.DrawLine(pen, new Point(0, i * this.VM.RowHeight), new Point(width, i * this.VM.RowHeight));
}
drawingContext.Pop();
}

how to measure grid width/height in wpf dynamically as the window size changes

What I want to do overall is add an ellipse to a canvas to represent the position of a golf ball on the Circumference of a circle when it is struck at a certain angle by a golf putter from the center of that circle. To do this I am using two images one which is a circle divided up into sections to represent the angle the ball was struck at. The other image is just a putter head that is rotated to represent the angle which the club hits the ball at. This image sits on top of the circle image. The blue ellipse represents the position of the ball.See the image output got from my application in the link below:
Required application output
To do this I basically calculated the width and height of the grid using this code:
public FacePage()
{
InitializeComponent();
GridGraphs.SizeChanged += GridGraphs_SizeChanged;
}
void GridGraphs_SizeChanged(object sender, SizeChangedEventArgs e)
{
GetMeasurements();
}
private void GetMeasurements()
{
GridGraphs.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double width = GridGraphs.DesiredSize.Width;
double height = GridGraphs.DesiredSize.Height;
RotatePutter(width, height);
}
and then passed the width and height into RotatePutter() where the the width and height was divided by two to get the centre point of the grid see below:
public void RotatePutter(double width, double height)
{
double radius = width * 0.5;
double centerX = width * 0.5;
double centerY = height * 0.5;
Random ran = new Random();
double angle = ran.Next(-15, 15);
if (angle >= 0)
{
if (angle <= 5)
{
lblShotStaus.Content = "Square";
}
else
{
lblShotStaus.Content = "Open";
}
}
else
{
lblShotStaus.Content = "Closed";
}
double angleradians = (angle * (Math.PI / 180));
Point putterCentre = new Point(0.5, 0.5);
imgPutter.RenderTransformOrigin = putterCentre;
RotateTransform rt = new RotateTransform(angle, 0.5, 0.5);
imgPutter.RenderTransform = rt;
BallLocation(radius, angleradians, centerX, centerY);
}
The centre point,radius,angle and the putter Image is rotated here and the radius, angleradians,centerX and centerY are passed to BallLocation to calculate the position of the Ball like so:
public void BallLocation(double rad, double anglerad, double centerX, double centerY)
{
Ellipse ellipse = new Ellipse();
double xBallPoint = (centerX - (rad * Math.Cos(anglerad)));
double yBallPoint = (centerY - (rad * Math.Sin(anglerad)));
ellipse.Height = 10;
ellipse.Width = 10;
ellipse.Fill = new SolidColorBrush(Colors.Aqua);
Canvas.SetLeft(ellipse, xBallPoint);
Canvas.SetTop(ellipse, yBallPoint);
canvasFaceAngle.Children.Add(ellipse);
}
This works ok in full screen put once I change the size of the window the position of the ball for the angle it was hit at is all wrong.
Would anyone have any idea how to dynamically get the grid width and height as the window size changes so to calculate the correct position for the ellipse (ball). Or any other alternative ways of completing this is also welcomed. Thanks in advance.
Here is my xaml:
<UserControl x:Class="HoleMorePuttsApplication.Pages.FacePage"
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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40*"/>
<ColumnDefinition Width="60*"/>
</Grid.ColumnDefinitions>
<Viewbox Grid.ColumnSpan="1">
<Label Content="Face Angle Page" FontWeight="Bold" />
</Viewbox>
<Grid Name="GridGraphs" Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Name="columnLeft" Width="50*"/>
<ColumnDefinition Name="columnRight" Width="50*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Name="rowTop" Height="70*"/>
<RowDefinition Name="rowBottom" Height="30*"/>
</Grid.RowDefinitions>
<Image x:Name="imgAngles" Source="C:\Users\BernardWork\Documents\Work\Net\PuttingApp\Images\360Angles.jpg" Grid.ColumnSpan="2" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image x:Name="imgPutter" Source="C:\Users\BernardWork\Documents\Work\Net\PuttingApp\Images\Putter.jpg" Opacity="0.5" Margin="130,105,70,105" Grid.ColumnSpan="2" HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
<Label x:Name="lblShotStaus" Content="Label" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Canvas Name="canvasFaceAngle" Grid.ColumnSpan="2" Grid.Row="0" RenderTransformOrigin="0.543,0.511"></Canvas>
</Grid>
</Grid>
If I understand correct, I believe the problem may be with:
double width = GridGraphs.DesiredSize.Width;
double height = GridGraphs.DesiredSize.Height;
You are calling Measure with no bounds provided which means that it will tell you the size the Grid would be were there no limitations (hence, the DesiredSize). The DesiredSize and the ActualSize may be two very different values.
In the case above, the args of the SizeChanged method will provide you with what you need.
void GridGraphs_SizeChanged(object sender, SizeChangedEventArgs e)
{
RotatePutter(e.NewSize.Width, e.NewSize.Height);
}
So if the goal is just to get the grid width/height, the ActualWidth and ActualHeight properties should give you that information. One thing to be careful about though: while the Grid control changes size along with the window, the Canvas control does not. In other words, if you read the actualwidth/height of a grid, it should correspond to the side of the window, but if you read the actualwith/height of a canvas, it will probably give you incorrect information.

Get the item index which is on focus inside a stackpanel

I have added 10 images in a stackpanel horizontally which is inside a scrollviewer. When user swipe the page the scrollviewer stops at certain position, if the scroll stops at &th image i want to get the name of the image. How to get that?
for (int i = 0; i <= 59; i++)
{
Uri uri = new Uri("http://d1mu9ule1cy7bp.cloudfront.net/2012/media/catalogues/47/pages/p_" + i + "/thump.jpg");
ImageSource img1 = new BitmapImage(uri);
Image rect = new Image { RenderTransform = new TranslateTransform() };
rect.Source = img1;
stack.Children.Add(rect);
}
XAML:
<ScrollViewer HorizontalContentAlignment="Left" HorizontalAlignment="Left" Name="scroll" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Visible">
<StackPanel Name="stack" Width="Auto" Orientation="Horizontal" HorizontalAlignment="Left" >
</StackPanel>
</ScrollViewer>
Assuming that your images are all the same size, you could calculate this by looking at the HorizontalOffset of the ScrollViewer.

Categories

Resources