Firing MouseDown events for overlapping Rectangles on a Canvas - c#

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.

Related

Print multiple canvas WPF?

Today I'm working with WPF! My app is almost finished, except for the last feature. I have created a graphic element which represents a label like the ones you can see under the goods in a supermarket shelf. Print one of them is easy, using PrintDialog.PrintVisual(myVisualItem,"description");.
Now, I have to print a series of these labels, using PintVisual() in a loop, prints every element in a separate document! There's no overload of PrintVisual() which accepts an IEnumerable of visual item. I've seen online that i should paginate these elements, but I cannot find a way to do it What can I do? Thanks!
You could combine all canvas you got into one big canvas and then print this one.
Here is some sample code how to concat multiple canvas into one big one:
public Canvas CombineCanvases(params Canvas[] canvases)
{
// Force each canvas to update its desired size
foreach (var canvas in canvases) { canvas.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); }
var result = new Canvas
{
Height = canvases.Sum(x => x.DesiredSize.Height), // add all heights to get needed height
Width = canvases.Max(x => x.DesiredSize.Width) // find required width
};
var pos = 0d;
foreach (var canvas in canvases)
{
Canvas.SetLeft(canvas, 0);
Canvas.SetTop(canvas, pos); // position element at this pixel count
pos += canvas.DesiredSize.Height; // and increment it
result.Children.Add(canvas);
}
return result;
}
You need to pass all canvas's you want to print and print the result.
var c1 = new Canvas
{
Width = 100,
Height = 100,
Children = { new TextBlock { Text = "Canvas 1" } }
};
var c2 = new Canvas
{
Width = 100,
Height = 100,
Children = { new TextBlock { Text = "Canvas 2" } }
};
var canvas = CombineCanvases(c1, c2);
// print canvas here
Pagination would be if you check how many labels fit on one page and then not concat all canvas into one huge on, but just take as many as are needed to fill one page.
Then print the page and repeat this until all Labels were printed. This is called pagination.
If you believe they will all fit on one page, you could put them in a StackPanel with Orientation = Vertical. Then print the stack panel.
var panel = new StackPanel() { Orientation = Orientation.Vertical };
panel.Children.Add(canvas1);
panel.Children.Add(canvas2);
panel.UpdateLayout();
var dlg = new PrintDialog();
dlg.PrintVisual(panel, "description");

Resizing drawlines on a paint event

I've seen few questions about this problem, I tried every solution but none of them worked for my case.
My code is working; this image shows what happens when I click on Draw button.
I need to zoom on that drawing.Is it possible to code something like autocad feature "zoom/extent"?
Pen myPen = new Pen(Color.Black);
int centerpointx, centerpointy;
private void pictureBoxDraw_Paint(object sender, PaintEventArgs e)
{
centerpointx = pictureBoxDraw.Size.Width/2;
centerpointy = pictureBoxDraw.Size.Height/2;
myPen.Width = 2;
if (binary > 0)
{
var sizecrestgeo = 40;
var distancearraycrestgeo = new float[sizecrestgeo];
var elevationarraycrestgeo = new float[sizecrestgeo];
for (int i = 0; i < sizecrestgeo; i++)
{
distancearraycrestgeo[i] = float.Parse(dataGridViewCrestGeo.Rows[i].Cells[0].Value.ToString());
elevationarraycrestgeo[i] = float.Parse(dataGridViewCrestGeo.Rows[i].Cells[1].Value.ToString())*-1;
}
for (int i=0; i < sizecrestgeo-1; i++)
{
e.Graphics.DrawLine(myPen, distancearraycrestgeo[i]+centerpointx, elevationarraycrestgeo[i]+centerpointy, distancearraycrestgeo[i + 1]+centerpointx, elevationarraycrestgeo[i + 1]+centerpointy);
}
}
else
{
}
}
private void buttonDraw_Click_1(object sender, EventArgs e)
{
if (Hd > 0.0001)
{
binary = 1;
pictureBoxDraw.Invalidate();
}
else
{
MessageBox.Show("No data to draw, perform analysis first.");
}
}
private void buttoncleardraw_Click(object sender, EventArgs e)
{
binary = 0;
pictureBoxDraw.Invalidate();
}
}
This is not so hard, provided you know all the puzzle pieces.
Let's start with the obvious one:
You can scale the Graphics object to create zoomed graphics with ScaleTransform.
As I mentioned, this will include the widths of pens, font sizes and also any images you draw (though not the hatches of a HatchBrush).
You also asked about keeping the drawing 'centered'. This is a non-obvious concept: Just what is the center of your drawing surface??
When zooming (just like rotating) you always need to know the center point of the zoom (or the rotation.) By default this is the origin (0,0). I chose the center of the Panel. You may want to pick some other point..
Once you do you can move the origin of the graphics viewport to this point with TranslateTransform.
Once you have achieved all this you almost certainly will want to allow scrolling.
To do so you have two options:
You can keep AutoScroll = false and nest the canvas control inside another control, usually a Panel, which has AutoScroll = true; next make the canvas control big enough to always hold your drawing and you're done.
Or you can turn on AutoScroll for the canvas control and also set a large enough AutoScrollMinSize. If you then add the current scrolling position to the translation you are also done. Let's see this solution in action:
This is the code in the Paint event:
Size sz = panel3.ClientSize;
Point center = new Point(sz.Width / 2, sz.Height / 2);
Graphics g = e.Graphics;
// center point for testing only!
g.DrawEllipse(Pens.Orange, center.X - 3, center.Y - 3, 6, 6);
// you determine the value of the zooming!
float zoom = (trackBar1.Value+1) / 3f;
// move the scrolled center to the origon
g.TranslateTransform(center.X + panel3.AutoScrollPosition.X,
center.Y + panel3.AutoScrollPosition.Y);
// scale the graphics
g.ScaleTransform(zoom, zoom);
// draw some stuff..
using(Pen pen = new Pen(Color.Yellow, 0.1f))
for (int i = -100; i < 100; i+= 10)
g.DrawEllipse(Pens.Yellow, i-22,i-22,44,44);
A few notes:
I draw an orange circle in the center to show this point is invariant.
My coordinates go from the negative to the positive so you can see that this works nicely.
I draw with a tiny pen width; so the width of the drawing only changes once the resulting pen goes over 1 pixel. Anything draw will always be draw with 1 pxiel width, though.
I first translate and then scale so I don't have to calculate scaled poitions.
The only line in the TrackBar's Scroll event is to trigger the Paint event: panel3.Invalidate();
The only settings needed for the Panel are
panel3.AutoScroll = true;
panel3.AutoScrollMinSize = new Size(500, 500); // use the size you want to allow!
However to avoid flicker it is highly recommended to use a DoubleBuffered control, maybe a Panel subclass like this:
class DrawPanel : Panel
{
public DrawPanel() { DoubleBuffered = true; }
}
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Graphics.ScaleTransform() is how you can zoom. Try using something like this inside your paint event handler:
e.Graphics.ScaleTransform(2.0F, 2.0F);

WinForms create transparent clearable pictureBox overlays

I want to create 2 transparent overlays for pictureBox in WinFrom application so I can separately draw on both and I can also clear it when I want to blank transparent overlay.
On one overlay I draw rectangles. I want these rectangles there all time.
On second overlay I draw circles but I just want to draw 1 circle and after user input clear this circle and draw another.
For now I'm using
var transparentOverlay = pictureBox.createGraphics();
But I don't know how to clear an overlay to blank transparent graphics.
I tried
transparentOverlay.Clear(Color.Transparent) which turned all overlay to black
pictureBox.Invalidate() which cleared all graphics from both overlays so my rectangles remain where they were
use some backup graphics which I created before any drawing and clear my overlay by assigning this graphics to it transparentOverlay = transparentOverlayBackup but this did nothing, all rectangles and all circles remain at their places
Is there a way to create clearable transparent graphics sticked to pictureBox?
EDIT:
I have a picture with text in that picture box. And what I want to do is to draw rectangles around words of the text and this rectangles should remain all the time over the picture.
Than I want to draw a single circle and wait for user to click on a screen. This is all ok but when user click on a screen, I want to clear that circle and draw another.
//this method I call by click on a button to start annotation
private void ExpertAnnotate(object sender, EventArgs e)
{
var pen = new Pen(Color.Black, 1);
if (!annotationIsRunning) //check if annotation is in process or not
{
annotationIsRunning = true;
annotationOverlay = pictureBox.CreateGraphics(); //create transparent overlay for drawing
//draw rectangles around all words in text (AOIs)
annotationAOIs.ForEach(a =>
{
annotationOverlay.DrawRectangle(pen, a.Start.X, a.Start.Y, (a.End.X - a.Start.X), (a.End.Y - a.Start.Y));
});
//subscribe mouseMove and mouseClick events on pictureBox
pictureBox.MouseMove += HighlightAOI;
pictureBox.MouseClick += SelectAOI;
}
//define brushes for drawing circles (fixations)
var brush = new SolidBrush(Color.FromArgb(128, Color.BlueViolet));
var dotBrush = new SolidBrush(Color.DarkBlue);
pen.Color = Color.Blue;
long sizeOfFixation;
var f = Fixations[fixationCounter - 1]; //get current fixation to draw
sizeOfFixation = (int)f.Length / FIX_SIZE_COEFICIENT; //compute size of circle
annotationOverlay.FillEllipse(dotBrush, f.PosX - 1, f.PosY - 1, 3, 3);
//draw fixation on overlay
annotationOverlay.FillEllipse(brush, (f.PosX - sizeOfFixation), (f.PosY - sizeOfFixation), sizeOfFixation * 2, sizeOfFixation * 2);
}
//eventHandler for mouseMove - this method color rectangle over which mouse hover to red border
private void HighlightAOI(object sender, EventArgs e)
{
//this just draw last highlighted rect to black when we not yet hover mouse above it
if (lastHighlightedAOI != null)
{
annotationOverlay.DrawRectangle(new Pen(Color.Black, 1), lastHighlightedAOI.Start.X, lastHighlightedAOI.Start.Y, (lastHighlightedAOI.End.X - lastHighlightedAOI.Start.X), (lastHighlightedAOI.End.Y - lastHighlightedAOI.Start.Y));
}
//get position of mouse sursor
var x = pictureBox.PointToClient(Cursor.Position).X;
var y = pictureBox.PointToClient(Cursor.Position).Y;
var tempFix = new Fixation() { PosX = x, PosY = y };
//get rectangle over which mouse hover
lastHighlightedAOI = tempFix.LiesIn(annotationAOIs).FirstOrDefault();
if (lastHighlightedAOI != null)
{
//highlight rectangle by painting red border
annotationOverlay.DrawRectangle(new Pen(Color.Red, 1), lastHighlightedAOI.Start.X, lastHighlightedAOI.Start.Y, (lastHighlightedAOI.End.X - lastHighlightedAOI.Start.X), (lastHighlightedAOI.End.Y - lastHighlightedAOI.Start.Y));
}
}
//eventHandler for mouse click
private void SelectAOI(object sender, EventArgs e)
{
//get position of cursor
var x = MousePosition.X;
var y = MousePosition.Y;
var tempFix = new Fixation() { PosX = x, PosY = y };
//get rectangle which we selected by a mouse click
var aoi = tempFix.LiesIn(annotationAOIs).FirstOrDefault();
//assign last shown fixation to selected rectangle
if (aoi != null)
{
aoi.AssignedFixations.Add(Fixations[fixationCounter - 1]);
}
//if it wasn't last fixation again call ExpertAnnotation function to draw another Fixation over image (!!! here I need to clear last drawn fixation (circle) disappear and draw next fixation in ExpertAnnotate method)
if (fixationCounter != Fixations.Count)
{
ExpertAnnotate(sender, e);
}
else
{
TerminateExpertAnnotation("regular");
}
}
Thanks to #Reza Aghaei who guided me in chat to solution.
For me acceptable solution was in building multilayer image and assigning it to pictureBox.Image attribute.
I built image by loading image from file:
Image im = new Bitmap(path); // loads image from file
Then create graphics from this image:
var g = Graphics.FromImage(im); // creates graphics from loaded image
Draw all needed rectangles to this image and backup this image to some global Image instance:
var pen = new Pen(Color.Black, 1);
// draws all rectangles on the image
annotationAOIs.ForEach(a =>
{
g.DrawRectangle(pen, a.Start.X, a.Start.Y, (a.End.X - a.Start.X), (a.End.Y - a.Start.Y));
});
g.Dispose(); // disposes used graphics
ImageBackup = new Bitmap(im); // backup image with rectangles
In above part I created a static part of an image, which will not change and I backed it up so next time I will just create new Image instance from backup without any rectangle drawing.
Then when I want to show up new circle over this image I just:
var image = new Bitmap(ImageBackup); // creates new instance of image with rectangles from backup
var g = Graphics.FromImage(image); // creates graphics from image
// in this part draw circle at specific point
var f = Fixations[fixationIndex];
sizeOfFixation = (int)f.Length / FIX_SIZE_COEFICIENT;
g.FillEllipse(dotBrush, f.PosX - 1, f.PosY - 1, 3, 3);
g.FillEllipse(brush, (f.PosX - sizeOfFixation), (f.PosY - sizeOfFixation), sizeOfFixation * 2, sizeOfFixation * 2);
pictureBox.Image.Dispose(); // dispose old pictureBox image
pictureBox.Image = image; // set new image
imageOverlay = pictureBox.CreateGraphics(); // get transparent graphics overlay for pictureBox so we can draw anything else over picture (in my case highlighting rectangles over which I hover a mouse)
g.Dispose(); // dispose used graphics
Your best approach would be to use the OnPaint event handler for the PictureBox control and place all your drawing calls in there.
You can use the Graphics object passed to the event handler to get the surface you want to draw on (i.e. the Picture box) and then use the various methods to draw the shapes that you are after.
To draw a 'transparent' shape simply draw an outline rather than a filled shape.

How to stop Paint events from stacking up

I'm creating an application to schedule different tasks. These are displayed by drawing rectangles on a panel. This has to be responsive. So I need to draw and invalidate on every size change. When I reach a maximum height of my planning panel it autoscrolls.
The problem is when I grab the scrollbar and start scrolling away for a bit, when I release the scrollbar my whole application and computer freezes.
Most likely this is due to the onpaint event being called on every little scroll and stacking up, leaving the application to hang until they are all done.
Now my question is: How would I be able to fix this? Possibly by keeping the paint event from being called multiple times, but how?
The method called by the paint event:
private void panelPlanning_Paint(object sender, PaintEventArgs e)
{
for (int i = 0; i < userList.Count; i++)
{
Label temp = new Label();
temp.Text = userList[i].Text;
temp.Width = panelUsers.Width;
temp.Height = 50;
temp.BorderStyle = BorderStyle.FixedSingle;
temp.Location = new Point(0, i * 50);
temp.TextAlign = ContentAlignment.MiddleCenter;
panelUsers.Controls.Add(temp);
foreach (FullTask task in taskList)
{
if (task.AssignedTo == userList[i].Text && task.StartDate != "" && task.DueDate != "")
{
DateTime start = DateTime.ParseExact(task.StartDate, "dd/MM/yyyy", CultureInfo.InvariantCulture);
DateTime end = DateTime.ParseExact(task.DueDate, "dd/MM/yyyy", CultureInfo.InvariantCulture);
Brush brush;
if (task.Priority == Enums.priorities[2])
{
brush = Brushes.Yellow;
}
else if (task.Priority == Enums.priorities[1])
{
brush = new SolidBrush(Color.FromArgb(255, 0, 80, 123));
}
else
{
brush = Brushes.Red;
}
panelPlanning.CreateGraphics().FillRectangle(brush, new Rectangle((start.Subtract(dtPickerStart.Value).Days + 1) * labelDaysWidth, i * 50, (end.Subtract(start).Days + 1) * labelDaysWidth, 25));
}
}
}
}
You are adding new Label controls on every single paint event. Don't do this:
// Label temp = new Label();
// temp.Text = userList[i].Text;
// temp.Width = panelUsers.Width;
// temp.Height = 50;
// temp.BorderStyle = BorderStyle.FixedSingle;
// temp.Location = new Point(0, i * 50);
// temp.TextAlign = ContentAlignment.MiddleCenter;
// panelUsers.Controls.Add(temp);
Also, use the e.Graphics object supplied by the argument, not CreateGraphics,
The following line of code should only be executed once:
panelUsers.Controls.Add(temp);
You are constantly adding a new instance of temp to the panelUsers control.
You have to trace which FullTask actually has to be displayed on the screen. This can be achieved by looking on Control.ClientRectangle and keeping in mind current scroll position.
In this way from the all set of the FullTasks to draw you will get only that subset of tasks that actually visible on the screen, and draw only those ones.
This is a correct way of dealing with drawable artifacts which is implemented in more or less all drawing frameworks 2D or even 3D.
There is also concept of tracing if some element is covered (or part of it is covered) by another element, so it will be "culed", but in your case this doesn't seem to matter.
Pay attention also on fact that you adding controls. Do not do that. If the amount of FullTasks is big, just draw them with Graphics object.
as already Described by #LarsTech, don't add controls in the paint event, and use the supplied Graphics object instead of your own ...
If you still face performance problems, consider painting on a bitmap whenever you change something, and only draw that bitmap onto the screen in onPaint

TranslateTransform moving elements more than expected

I'm pretty new to WPF (and completely new to animations), so I would imagine there's just something that I'm missing here. I'm in the process of creating a new layout Panel that lays the controls out in a particular way; the details aren't (or shouldn't be) especially important here. What I'd like to do is animate the movement of the elements that I'm managing when I call Arrange on them.
In general, what I do is keep track of the last Rect that I used to Arrange each child element. When ArrangeOverride is called...
If animation is enabled and there is a previous bounding Rect for the element, I Arrange it to the X and Y of that bounding rect with the new Width and Height, then use two DoubleAnimations (one for X and one for Y) to animate it to the X and Y of the new bounds
If animation is disabled or there is no existing bounding Rect for the element, I just Arrange it to the new bounds
When I disable animation, everything renders correctly (which is a good thing). When I enable animation, the elements render in the incorrect locations. If I resize the container, they generally animate themselves back into the correct spot.
Edit for clarification
In general, the error appears to be directly related to the coordinates of the bounds of the control. In other words, if the control is farther to the right to begin with, then the TranslateTransform appears to offset it more than a control that is not as far to the right. The same goes for up/down. Again, resizing seems to correct the problem (in fact, the controls animate themselves into the correct spot, which is frustratingly neat), but only if the entire set of controls is visible in the new size.
This is an abbreviated version of the class:
public class MyPanel : Panel
{
private Dictionary<UIElement, Rect> lastBounds = new Dictionary<UIElement, Rect>();
protected override Size MeasureOverride(Size availableSize)
{
// do all of my measurements here and delete anything from lastBounds that
// isn't on the panel any more. This works fine
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach(UIElement child in Children)
{
Rect newBounds = // get the previously calculated new bounds;
TransformGroup group= child.RenderTransform as TransformGroup;
if (group == null)
{
group = new TransformGroup();
group.Children.Add(new TranslateTransform());
child.RenderTransform = group;
}
Rect lastBounds;
if (!this.lastBounds.TryGetValue(child, out lastBounds))
lastBounds = Rect.Empty;
if (!lastBounds.IsEmpty && EnableAnimation)
{
Rect tempBounds = new Rect(lastBounds.X, lastBounds.Y, newBounds.Width, newBounds.Height);
child.Arrange(tempBounds);
int animationDuration = 300;
DoubleAnimation xAnim = new DoubleAnimation(newBounds.X - lastBounds.X, TimeSpan.FromMilliseconds(animationDuration));
DoubleAnimation yAnim = new DoubleAnimation(newBounds.Y - lastBounds.Y, TimeSpan.FromMilliseconds(animationDuration));
xAnim.AccelerationRatio = yAnim.AccelerationRatio = 0.2;
xAnim.DecelerationRatio = yAnim.DecelerationRatio = 0.7;
TranslateTransform translate = group.Children[0] as TranslateTransform;
translate.BeginAnimation(TranslateTransform.XProperty, xAnim);
translate.BeginAnimation(TranslateTransform.YProperty, yAnim);
}
else
{
child.Arrange(newBounds);
}
}
}
}
You appear to be arranging the children to their old location, then applying a render transform to get them to appear in their new location. This may be an issue because the layout engine might have a problem with you giving it a location that is now outside the parent layout container. You may have better luck always arranging to the new bounds (within the parent element), then applying the render translate animation to move it back to where it really is:
child.Arrange(newBounds);
int animationDuration = 300;
DoubleAnimation xAnim = new DoubleAnimation(lastBounds.X - newBounds.X, 0, TimeSpan.FromMilliseconds(animationDuration));
DoubleAnimation yAnim = new DoubleAnimation(lastBounds.Y - newBounds.Y, 0, TimeSpan.FromMilliseconds(animationDuration));

Categories