In my WPF project I have a canvas on which I draw an ellipse in the XAML and add a MouseEnter event to it:
<Canvas Width="600" Height="480" Name="canvas1" HorizontalAlignment="Left">
<Ellipse Height="20" Width="20" Canvas.Left="50" Canvas.Top="50" Fill="blue" Name="ellipse1" Mouse.MouseEnter="ellipse1_MouseEnter" MouseLeave ="ellipse1_MouseLeave"/>
</Canvas>
In the codebehind I've got this code:
private void ellipse1_MouseEnter(object sender, MouseEventArgs e)
{
ellipse1.Fill = Brushes.Red;
}
When I enter the ellipse with my mouse, it turns red as expected.
I also have code to draw an ellipse on the canvas where I click my mouse. I have a class called Vertex in which I create a ellipse, which has a reference to the canvas.
When I instantiate a new Vertex (and so an ellipse), I add the ellipse to the canvas' children. Before adding it to the canvas, I add a handler to the MouseEnter event:
MyEllipse.MouseEnter += new System.Windows.Input.MouseEventHandler(MyEllipse_MouseEnter);
The "MyEllipse_MouseEnter" handler looks like this:
private void MyEllipse_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
this.MyEllipse.Fill = Brushes.Red;
}
I expected this to work as it is the same as the first example which works.
However, when I enter the drawn ellipse with the mouse, my handler is not caleld. BUT, when go back and forth over the ellipse many times, it will eventually trigger and color the ellipse red. But this only happens on one of the many ellipses I draw, which also seems very strange.
What could be causing this strange behaviour?
Solved the problem!
When drawing the ellipse as a part of a Vertex, I add a label to the ellipse.
Better said, I put a label on top of the ellipse:
Canvas.SetZIndex(myEllipse, 10);
Canvas.SetLeft(myEllipse, coordinates.X);
Canvas.SetTop(myEllipse, coordinates.Y);
Canvas.SetZIndex(myLabel, 10);
Canvas.SetLeft(myLabel, coordinates.X - 1);
Canvas.SetTop(myLabel, coordinates.Y - 5);
canvas.Children.Add(myEllipse);
canvas.Children.Add(myLabel);
So when I clicked an ellipse on the canvas I actually clicked the label rather then the ellipse. The solution for his was simple:
myLabel.IsHitTestVisible = false;
Now the label cannot be hitted :D
Thx everbody!
Can you share the code you are using to programatically draw the ellipse. I suspect that your programatic ellipse is not filled initially making it transparent, the transparent region is not treated as part of the ellipse and the messages are therefore not raised to the ellipse.
If my abovew assumption is correct, the occasions when you do get the messages, it is when the mouse pointer hits the outline of the ellipse.
Related
I am writing an application in C# UWP. Right now I have an InkCanvas covering the whole screen and a Rectangle symbolizing the area where the user is allowed to start drawing. Once the user has started drawing (by pressing inside the rectangle area) the user is allowed to draw everywhere on the whole screen until the mouse/pen is released. This is the code that I have right now:
<Grid Name="Root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<InkCanvas Name="MyInkCanvas"/>
<Rectangle Fill="Green" Width="100" Height="100"/>
</Grid>
The problem I have is if I put the InkCanvas on top (highest zIndex) I can draw the line but no other events go through to the rectangle. Also I can't find a way to get the points while I am drawing. This means I can not check if the start point is inside the rectangle area. If I instead put the rectangle on top, I can not get the InkCanvas to register a pressed action after I press the rectangle which means no line will be drawn. So, Is it possible to start drawing when inside a specific area?
For your requirement, you could refer to SimpleInk (scenario 8) official code sample.
With the following method in the scenario 8 of SimpleInk project you can find the mouse position and then check if the mouse is at the specified area before drawing.
private bool FollowCircleTest(CoreWetStrokeUpdateEventArgs args)
{
var position = args.NewInkPoints[0].Position;
var distanceFromCenter = DistanceFromCenter(position);
return InCircleEdgeZone(distanceFromCenter);
}
I'm creating a simple WPF app in which one of the core features would be that wherever the user clicks on the grid inside the main window, a number of buttons should appear around the position of the click.
Now, I try to achieve this with only 1 button. I know that I have to capture the current position of the mouse and then modify the 4 arguments of the Margin of the button (left, top, right, bottom) by creating new instances of Thickness-es.
I managed to create new Thickness-es to the Margins, with the left and top argument set to the mouse X and Y cordinates respectively, but I don't know how to calculate or what to use as the right, and bottom arguments of the newly created Margins.
Here is the relevant function from the xaml.cs (the values in question are indicated as 0-s and grid is intented to refer to the grid):
private void Grid_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
var mouseLocation = PointToScreen(Mouse.GetPosition(grid));
RandomButton.Margin = new Thickness(mouseLocation.X, mouseLocation.Y, 0, 0);
}
Here is the relevant part of the xaml:
<StackPanel>
<Button
Name="RandomButton"
Height="30"
Width="30"
Background="#FF130889"
Click="RandomButton_Click"
Content="RandomContent" />
</StackPanel>
It is also worth mentioning that when the button's HorizontalAlignment is set to Left and the VerticalAlignment is set to top, the button seem to do what I want with this setup, but only, when the windowsize is full.
I think I have to use the actual height of the window or the grid, but I don't know how. I know it is something simple, but I just started working with WPF, so I apreciate any kind of help!
As far as I understood the problem is in determining the click relative position.
In this case you can use Mouse.GetPosition method.
Here is the example with Canvas:
private void SetPos()
{
var relativePosition = Mouse.GetPosition(this.MainCanvas);
Canvas.SetLeft(this.btn1, relativePosition.X);
Canvas.SetTop(this.btn1, relativePosition.Y);
btn1.Visibility = Visibility.Visible;
}
I have the following problem: I'm using Image component in WPF. I load my Image that is a map for me. I want to make a Button that if I click on, I will be allowed to click on my map and draw a point. That point should be "unique", so it should remember coordinates/description(I will store it in a database).
Coordinates should be read only from that image, not from the whole form.
I need OnMouseClick event after my point creation.
What should I use/read about to do that ?
Canvas allows you to place an object using left/top coordinates.
You need to place canvas exactly over the image with same dimensions as image.
To accomplish this, you should use a Grid, as it allows controls to stack over each other.
You can get the mouse click coordinates using e.GetPosition() in MouseLeftButtonDown event handler. As canvas is covering up image, so you will get coord according to image.
<Grid>
<Image x:Name="MapImg" Source="img/map.gif" Stretch="Fill" MouseLeftButtonDown="Image_MouseLeftButtonDown_1"/>
<Canvas x:Name="Cnv"/>
</Grid>
private void Image_MouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
{
Ellipse ellipse = new Ellipse();
ellipse.Fill = Brushes.Sienna;
ellipse.Width = 100;
ellipse.Height = 100 ;
ellipse.StrokeThickness = 2;
Cnv.Children.Add(ellipse);
Canvas.SetLeft(ellipse, e.GetPosition(MapImg).X);
Canvas.SetTop(ellipse, e.GetPosition(MapImg).Y);
}
I am performing scaling, translation and rotation on an image using RenderTransform. Transformations are not done using mouse events, rather, the user will click a button and then the image will scale/translate/rotate on a fixed value.
My problem is I want to determine the new position/size of the image each time a scaling, translation or rotation is performed. So I added Changed events on the code-behind. The question is how do you get the new position/size?
Please take a look at what I've done so far:
XAML:
<Border x:Name="mainImageBorderCtrl" ClipToBounds="True">
<Grid x:Name="imageGridCtrl">
<Grid.RenderTransform>
<TranslateTransform Changed="TranslateTransform_Changed"/>
</Grid.RenderTransform>
<Image x:Name="mainImageCtrl" RenderTransformOrigin="0.5, 0.5" Source="{Binding Image}">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform Changed="ScaleTransform_Changed"/>
<RotateTransform />
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>
</Border>
Please note that I separated TranslateTransform from ScaleTransform and RotateTransform so that the orientation won't be affected.
The following are the 2 changed events. In here, I expect to get the new position/size every time a scale or a translate has occurred. But the bounds values do not change.
Code-behind
private Rect bounds;
private void TranslateTransform_Changed(object sender, EventArgs e)
{
bounds = imageGridCtrl.TransformToAncestor(mainImageBorderCtrl).TransformBounds(new Rect(imageGridCtrl.RenderSize));
}
private void ScaleTransform_Changed(object sender, EventArgs e)
{
bounds = imageGridCtrl.TransformToAncestor(mainImageBorderCtrl).TransformBounds(new Rect(imageGridCtrl.RenderSize));
}
EDIT: My goal here is to restrict the translation(panning) of the Image inside the Border control, that's why I need to get the bounds of the Image after each transformation, whatever the transformation is, so that I can check whether the bounds of the Image exceeds the Border.
TranslateTransform is generating correct bounds. However, for ScaleTransform you are getting bounds on control imageGridCtrl but the transform is added as child of control mainImageCtrl.
So you should use mainImageCtrl in place of imageGridCtrl to get correct bounds:
bounds = mainImageCtrl.TransformToAncestor(mainImageBorderCtrl)
.TransformBounds(new Rect(mainImageCtrl.RenderSize));
The transform change events fire too early. You should hook up to LayoutUpdated event of the image control. Then try:
mainImageCtrl.TransformToAncestor(mainImageBorderCtrl)
.TransformBounds(new Rect(mainImageCtrl.RenderSize))
I am responding to MouseLeftButtonDown events on elements added to a WPF canvas. It all works fine when clicked (i.e. the eventhandler fires off correctly), but it requires too much precision from the mouse pointer. You have to be perfectly on top of the circle to make it work. I need it to be a little more forgiving; maybe at least 1 or 2 pixles forgiving. The elements on the canvas are nice big circles (about the size of a quarter on the screen), so the circles themselves are not too small, but the StrokeWidth of each one is 1, so it is a thin line.
You can see a screenshot here: http://twitpic.com/1f2ci/full
Most graphics app aren't this picky about the mouse picking, so I want to give the user a familiar experience.
How can I make it a little more forgiving.
You can hook up to the MouseLeftButtonDown event of your root layout object instead, and check which elements is in range of a click by doing this:
List<UIElement> hits = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(Point, yourLayoutRootElement) as List<UIElement>;
http://msdn.microsoft.com/en-us/library/cc838402(VS.95).aspx
For the Point parameter, you can use the MouseEventArgs parameter e, and call its GetPosition method like this:
Point p = e.GetPosition(null)
I can't remember whether to use HitTest instead of the FindElementsInHostCoordinates. Try both.
http://msdn.microsoft.com/en-us/library/ms608752.aspx
You could create 4 Point objects from the mouse position to create a fake tolerence effect, and call either FindElementsInHostCoordinates or HitTest for all 4 points.
You might want to try to fill the circle with the Transparent colour to make the whole circle clickable...
If that fails, you can also draw helper circles on the same location as the other circles. Make the circle foreground colour Transparent, and make the thickness of the brush a few pixels wider for a more acceptable clickable region around the circle..
Hope this helps!
I think I've done it (with you help to get me started)...
First, I've moved the move event handling to the Canvas instead of each Ellipse. That's good and bad, from an OOP standpoint. At least when the mouse event handling is a responsibility of the HolePattern to set it on up each Hole (the ellipse that is the visual of the Hole), it is abstracted away so that any consumer of my HolePattern will get this functioanality automactically. However, by moving it to the main UI code, I now am dealing with my canvas mouse event at a higher level. But that's not all bad either. We could discuss this part for days.
The point is, I have designed a way to create a "margin of error" when picking something on a canvas with a mouse, and then reading the Hole that the selected Ellipse belongs to, and then I can read the HolePattern that the Hole belongs to, and my entire UI (ListView, textboxes, gridview fo coordinates) are ALL updated by the existing XAML binding, and the Canvas is updated with one call to an existing method to regenerate the canvas.
To be honest, I can't believe I've figured all this out (with your help and others too, of course). It is such a cool feeling to have the vision of this this and see it come to be.
Check out the main code here:
void canvas1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
int ClickMargin = 2;
Point ClickedPoint = e.GetPosition(canvas1);
Point p1 = new Point(ClickedPoint.X - ClickMargin, ClickedPoint.Y - ClickMargin);
Point p2 = new Point(ClickedPoint.X - ClickMargin, ClickedPoint.Y + ClickMargin);
Point p3 = new Point(ClickedPoint.X + ClickMargin, ClickedPoint.Y + ClickMargin);
Point p4 = new Point(ClickedPoint.X + ClickMargin, ClickedPoint.Y - ClickMargin);
var PointPickList = new Collection<Point>();
PointPickList.Add(ClickedPoint);
PointPickList.Add(p1);
PointPickList.Add(p2);
PointPickList.Add(p3);
PointPickList.Add(p4);
foreach (Point p in PointPickList)
{
HitTestResult SelectedCanvasItem = System.Windows.Media.VisualTreeHelper.HitTest(canvas1, p);
if (SelectedCanvasItem.VisualHit.GetType() == typeof(Ellipse))
{
var SelectedEllipseTag = SelectedCanvasItem.VisualHit.GetValue(Ellipse.TagProperty);
if (SelectedEllipseTag!=null && SelectedEllipseTag.GetType().BaseType == typeof(Hole))
{
Hole SelectedHole = (Hole)SelectedEllipseTag;
SetActivePattern(SelectedHole.ParentPattern);
SelectedHole.ParentPattern.CurrentHole = SelectedHole;
}
}
}
}
Just Increase Stroke ThickNess of the Ellipse so that it is adjustable
thus the MouseLeftButtonDown event works
Example:
In Ellipse tag:
Ellipse
Canvas.Left="10" Canvas.Top="133" Height="24" Name="ellipse1" Width="23" Stroke="Red" MouseLeftButtonDown="ellipse1_MouseLeftButtonDown" ToolTip="Temp Close" StrokeEndLineCap="Flat" StrokeThickness="12"
private void ellipse1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Application curApp = Application.Current;
curApp.Shutdown();
}