Im creating Path objects with curvy edges on a Canvas. I want to be able to rotate them and move them around the canvas. But when i try to apply several transform to and object it only visually displays the last one. I assume its because the transforms are applied to the coordinates of the Path object and are only displayed but not saved afterwards.
So if i would run something like:
my_canvas.Children[0].RenderTransform = new TranslateTransform(0, 100);
my_canvas.Children[0].RenderTransform = new TranslateTransform(0, 150);
it would move my Path 150 pixels down.
Is there a way i can save the transform progress of RenderTransform or do i have to recreate my Path with different parameters/write a method to displace the pixels manually?
Edit
Another example:
my_canvas.Children[0].MouseDown += (object sender, MouseButtonEventArgs e) =>
{
if (e.LeftButton == MouseButtonState.Pressed)
{
MouseDownLocation = e.GetPosition(my_canvas);
}
};
my_canvas.Children[0].MouseMove += (object sender, MouseEventArgs e) =>
{
if (e.LeftButton == MouseButtonState.Pressed)
{
my_canvas.Children[0].RenderTransform = new TranslateTransform(-(MouseDownLocation.X - e.GetPosition(my_canvas).X), -(MouseDownLocation.Y - e.GetPosition(my_canvas).Y));
}
};
This code allows me to move my element and it works fine: i can pick it up, visually move it, and let it go. But only once. If try to do it again it tries to do the transformation based on the elements' previous position. And as I am typing this i realize that i can probably solve this by keeping track of the offsets the transformations are causing.
Just add the next translation to the X and Y values of the existing RenderTransform:
my_canvas.Children[0].RenderTransform = new TranslateTransform(0, 100);
((TranslateTransform)my_canvas.Children[0].RenderTransform).Y += 150;
Or use Canvas.Left and Canvas.Top instead of a TranslateTransform:
UIElement element = my_canvas.Children[0];
Canvas.SetTop(element, 100);
Canvas.SetTop(element, Canvas.GetTop(element) + 150);
Related
I have a DataGridView with 2 columns of file-names. I would like to emulate the Windows File Explorer 'Rename' context menu on these file-names. To that end, I created a simple WinForms dialog with no header and a textbox entry for renaming. I display it when a grid's file-name cell is right-clicked. I am trying to position it directly over the cell, but have been unable to get it to display in the correct location. It's down by several rows and to the right by a few character widths. I'm positioning the dialog thusly:
Point location;
void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e) {
var cellRect = dataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
// Point location = dataGridView.Location;
location = dataGridView.Bounds.Location;
location.Offset(cellRect.Location);
location = dataGridView.PointToScreen(location);
}
async void renameToolStripMenuItem_Click(object sender, EventArgs e) {
using (var rfd = new RenameFileDialog(fi)) {
// Lifted from designer
rfd.ControlBox = false;
rfd.Text = string.Empty;
rfd.formBorderStyle = FormBorderStyle.SizableToolWindow;
// Actually in method
rfd.StartPosition = FormStartPosition.Manual;
rfd.Location = location;
rfd.ShowDialog(dataGridView);
}
}
I suspect I'm getting tripped up by Location vs ClientRectangle vs Control Bounds or margins or padding, but I haven't been able to identify where the undesired offsets are coming from. Can someone tell me how to position the dialog, or otherwise suggest a way to emulate Explorer's "Rename' in a dataGridView?
The original sin is here:
location = dataGridView.Bounds.Location;
to translate to Screen coordinates the origin point of a Control, using the Control itself as the relative reference, you have to consider its own origin, which is always (0, 0) (Point.Empty).
If you use its Location property, you instead consider the Control's offset in relation to its Parent.
If you then use this measure and call Control.PointToScreen(), you retrieve a position inside the Control's Client area
The offset of a location inside its ClientRectangle, added to this measure, is then of course moved right and down (since the Control's origin is not at (0, 0))
In other words, the Screen coordinates of the origin point of a Control are:
[Control].PointToScreen(Point.Empty);
As described in Open a Form under a DataGridView, you just need to consider the bounds of the Cell that raised the CellMouseDown event:
Point location = Point.Empty;
private void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
var dgv = sender as DataGridView;
var cellRect = dgv.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false);
location = dgv.RectangleToScreen(cellRect).Location;
}
As a note, in normal conditions, the coordinates that GetCellDisplayRectangle() returns are moved 7 pixels to the right and 1 pixel down, in relation to a Cell's grid, since it considers the internal bounds
If you want to position your Form over the Cell's grid, you could add:
location.Offset(-7, -1);
I have a WPF window containing a Canvas which is populated with rotated Rectangles in code. The rectangles each have a MouseDown event and their positions will be distributed according to coordinates provided by the user. Often two or more will overlap, partially obstructing the rectangle beneath it.
I need the MouseDown event to fire for each rectangle that is under the mouse when it is pressed, even if that rectangle is obstructed by another rectangle, but I am only getting the MouseDown event for the topmost rectangle.
I have tried setting e.Handled for the clicked rectangle, and routing the events through the Canvas with no luck, and even gone as far as trying to locate the objects beneath the mouse based on their coordinates, but the rotation of the rectangles make that difficult to calculate.
public MainWindow()
{
InitializeComponent();
Rectangle r1 = new Rectangle() {Width = 80, Height = 120, Fill = Brushes.Blue };
r1.MouseDown += r_MouseDown;
RotateTransform rt1 = new RotateTransform(60);
r1.RenderTransform = rt1;
Canvas.SetLeft(r1, 150);
Canvas.SetTop(r1, 50);
canvas1.Children.Add(r1);
Rectangle r2 = new Rectangle() { Width = 150, Height = 50, Fill = Brushes.Green };
r2.MouseDown += r_MouseDown;
RotateTransform rt2 = new RotateTransform(15);
r2.RenderTransform = rt2;
Canvas.SetLeft(r2, 100);
Canvas.SetTop(r2, 100);
canvas1.Children.Add(r2);
}
private void r_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("Rectangle Clicked");
}
}
There is another question that is similar to this, but it has no accepted answer and it is quite unclear as to what the final solution should be to resolve this issue. Let's see if we can be a little more clear.
First off, the solution outlined below will use the VisualTreeHelper.HitTest method in order to identify if the mouse has clicked your rectangles. The VisualTreeHelper allows us to find the rectangles even if they have moved around due to things like Canvas.SetTop and various .RenderTransform operations.
Secondly, we are going to be capturing the click event on your canvas element rather than on the individual rectangles. This allows us to handle things at the canvas level and check all the rectangles at once, as it were.
public MainWindow()
{
InitializeComponent();
//Additional rectangle for testing.
Rectangle r3 = new Rectangle() { Width = 175, Height = 80, Fill = Brushes.Goldenrod };
Canvas.SetLeft(r3, 80);
Canvas.SetTop(r3, 80);
canvas1.Children.Add(r3);
Rectangle r1 = new Rectangle() { Width = 80, Height = 120, Fill = Brushes.Blue };
RotateTransform rt1 = new RotateTransform(60);
r1.RenderTransform = rt1;
Canvas.SetLeft(r1, 100);
Canvas.SetTop(r1, 100);
canvas1.Children.Add(r1);
Rectangle r2 = new Rectangle() { Width = 150, Height = 50, Fill = Brushes.Green };
RotateTransform rt2 = new RotateTransform(15);
r2.LayoutTransform = rt2;
Canvas.SetLeft(r2, 100);
Canvas.SetTop(r2, 100);
canvas1.Children.Add(r2);
//Mouse 'click' event.
canvas1.PreviewMouseDown += canvasMouseDown;
}
//list to store the hit test results
private List<HitTestResult> hitResultsList = new List<HitTestResult>();
The HitTest method being used is the more complicated one, because the simplest version of that method only returns "the topmost" item. And by topmost, they mean the first item drawn, so it's actually visually the one on the bottom of the stack of rectangles. In order to get all of the rectangles, we need to use the complicated version of the HitTest method shown below.
private void canvasMouseDown(object sender, MouseButtonEventArgs e)
{
if (canvas1.Children.Count > 0)
{
// Retrieve the coordinates of the mouse position.
Point pt = e.GetPosition((UIElement)sender);
// Clear the contents of the list used for hit test results.
hitResultsList.Clear();
// Set up a callback to receive the hit test result enumeration.
VisualTreeHelper.HitTest(canvas1,
new HitTestFilterCallback(MyHitTestFilter),
new HitTestResultCallback(MyHitTestResult),
new PointHitTestParameters(pt));
// Perform actions on the hit test results list.
if (hitResultsList.Count > 0)
{
string msg = null;
foreach (HitTestResult htr in hitResultsList)
{
Rectangle r = (Rectangle)htr.VisualHit;
msg += r.Fill.ToString() + "\n";
}
//Message displaying the fill colors of all the rectangles
//under the mouse when it was clicked.
MessageBox.Show(msg);
}
}
}
// Filter the hit test values for each object in the enumeration.
private HitTestFilterBehavior MyHitTestFilter(DependencyObject o)
{
// Test for the object value you want to filter.
if (o.GetType() == typeof(Label))
{
// Visual object and descendants are NOT part of hit test results enumeration.
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}
else
{
// Visual object is part of hit test results enumeration.
return HitTestFilterBehavior.Continue;
}
}
// Add the hit test result to the list of results.
private HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
//Filter out the canvas object.
if (!result.VisualHit.ToString().Contains("Canvas"))
{
hitResultsList.Add(result);
}
// Set the behavior to return visuals at all z-order levels.
return HitTestResultBehavior.Continue;
}
The test example above just displays a message box showing the fill colors of all rectangles under the mouse pointer when it was clicked; verifying that VisualTreeHelper did in fact retrieve all the rectangles in the stack.
I would like to write an application that will measure fragments of a specimen examined under a microscope. I thought that the best way would be to capture the image and draw on selected parts of the specimen then count the value of the drawn line in pixels (and later to convert this value into the appropriate unit).
Is there anything that helps solve such issue already implemented or any tool/package or something that allows such calculations?
I will also willingly learn about solutions in other programming languages if they allow to solve this problem in a easier way or just in some way.
This is a very basic example of measuring a segmented line drawn onto an image in winforms.
It uses a PictureBox to display the image, a Label to display the current result and for good measure I added two Buttons the clear all points and to undo/remove the last one.
I collect to pixel positions in a List<Point> :
List<Point> points = new List<Point>();
The two edit buttons are rather simple:
private void btn_Clear_Click(object sender, EventArgs e)
{
points.Clear();
pictureBox1.Invalidate();
show_Length();
}
private void btn_Undo_Click(object sender, EventArgs e)
{
if (points.Any())points.Remove(points.Last());
pictureBox1.Invalidate();
show_Length();
}
Note how I trigger the Paint event by invalidating the image whenever the points collection changes..
The rest of the code is also simple; I call a function to calculate and display the sum of all segment lengths. Note that I need at least two points before I can do that or display the first line..
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
points.Add(e.Location);
pictureBox1.Invalidate();
show_Length();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (points.Count > 1) e.Graphics.DrawLines(Pens.Red, points.ToArray());
}
void show_Length()
{
lbl_len.Text = (pointsF.Count) + " point(s), no segments. " ;
if (!(points.Count > 1)) return;
double len = 0;
for (int i = 1; i < points.Count; i++)
{
len += Math.Sqrt((points[i-1].X - points[i].X) * (points[i-1].X - points[i].X)
+ (points[i-1].Y - points[i].Y) * (points[i-1].Y - points[i].Y));
}
lbl_len.Text = (points.Count-1) + " segments, " + (int) len + " pixels";
}
A few notes:
The image is displayed without any zooming. PictureBox has a SizeMode property to make zoomed display simple. In such a case I recommend to store not the direct pixel locations of the mouse but 'unzoomed' values and to use a 'rezoomed' list of values for the display. This way you can zoom in and out and still have the points stick to the right spots.
For this you ought to use a List<PointF> to keep precision.
When zooming e.g. by enlarging the PictureBox, maybe after nesting it in a Panel, make sure to either keep the aspect ratio equal to that of the Image or to do a full calculation to include the extra space left or top; in SizeMode.Normal the image will always sit flush TopLeft but in other modes it will not always do so.
For the calculation of actual i.e. physical distances simply divide by the actual dpi value.
Let's see what we have in action:
Update:
To get a chance to create cloers fits and better precision we obviously need to zoom in on the image.
Here are the necessary changes:
We add a list of 'floating points':
List<PointF> pointsF = new List<PointF>();
And use it to store the un-zoomed mouse positions in the mouse down:
pointsF.Add( scaled( e.Location, false));
We replace all other occurances of points with pointsF.
The Paint event always calculates the scaled points to the current zoom level:
if (pointsF.Count > 1)
{
points = pointsF.Select(x => Point.Round(scaled(x, true))).ToList();
e.Graphics.DrawLines(Pens.Red, points.ToArray());
}
And the function to do the scaling looks like this:
PointF scaled(PointF p, bool scaled)
{
float z = scaled ? 1f * zoom : 1f / zoom;
return new PointF(p.X * z, p.Y * z);
}
It uses a class level variable float zoom = 1f; which gets set along with the picturebox's Clientsize in the Scroll event of a trackbar:
private void trackBar1_Scroll(object sender, EventArgs e)
{
List<float> zooms = new List<float>()
{ 0.1f, 0.2f, 0.5f, 0.75f, 1f, 2, 3, 4, 6, 8, 10};
zoom = zooms[trackBar1.Value];
int w = (int)(pictureBox2.Image.Width * zoom);
int h = (int)(pictureBox2.Image.Height * zoom);
pictureBox2.ClientSize = new Size(w, h);
lbl_zoom.Text = "zoom: " + (zoom*100).ToString("0.0");
}
The picturebox is nested inside a Panel with AutoScroll on. Now we can zoom and scroll while adding segments:
Inside my page, I have a frame that loads another page. I am using this to detect if they want to move the frame around the screen:
private void Frame_MouseMove(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
}
}
How do I move the frame itself though? I have been trying to look for it and I cant seem to find the variables that control its position.
Try positioning using the frame's margin:
AppFrame.Margin = new Thickness(Mouse.X, Mouse.Y, 0, 0);
Alternatively, some frames are able to be moved with their Left and Top variables.
frame.Left = Mouse.X; // or whatever
frame.Top = Mouse.Y; // or whatever
In order to move it proportionally to the mouse, record the mouse and frame's original positions when the mouse was first dragged, and referenced them when positioned:
frame.Left = originalFrameX + (Mouse.X - originalMouseX);
frame.Top = originalFrameY + (Mouse.Y - originalMouseY);
Actually i am wondering to erase an image to transparency. like i have an image on page background and another image above that. Now i want that if i erase above image by finger then lower image should be appear, simply means to say image will become transparent.i'm doing something like this but its not meet my requirements.
Your suggestions are Welcome :)
private void Canvas_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
currentPoint = e.GetPosition(this.canvas);
//Initialize line according to currentpoint position.
Line line = new Line()
{
X1 = currentPoint.X,
Y1 = currentPoint.Y,
X2 = oldPoint.X,
Y2 =oldPoint.Y
};
line.StrokeDashCap = PenLineCap.Round;
line.StrokeEndLineCap = PenLineCap.Round;
line.StrokeLineJoin = PenLineJoin.Round;
line.StrokeThickness = 20;
line.Stroke = new SolidColorBrush(Colors.Black) ;
////////////////////////////////
//Set color & thickness of line.
//Line add in canvas children to draw image & assign oldpoint.
this.canvas.Children.Add(line);
oldPoint = currentPoint;
}
You can do it with 3 different ways:
Using opaque overlay and UIElement.Clip property. But you need to deal with Geometry. And I'm afraid it will be very CPU cost.
Using WriteableBitmap and changing an alpha channel of image. You can do it using WriteableBitmapEx.
Using opaque overlay and UIElement.OpacityMask property. I think it's the best way to accomplish that, as you're not limited to use bitmap (so you can place any XAML control below overlay) as in the second way.