I'm learning WPF and have a specific goal.
Imagine you have a grid (3 rows by 3 columns), and a control, say a simple blue rectangle fills the middle cell. When I click on the cell I want the square to rotate smoothly through 180 degrees.
The specific issue I have at the moment is; as the rectangle rotates, it won't change its dimensions, so it will extend beyond the boundary of the cell. I don't want it to clip, i want it to appear on top, partially obscuring surrounding cells.
The second part of this is, if there is one cell that fills the entire window, and I click on the blue rectangle in that cell, can I make the rectangle rotate and extend beyond the sides of the form?
If that doesn't make sense, please ask. I'm finding it hard to google because I don't know the exact terms I should be using.
Thank you
The first part can be acomplished by using the attached property Panel.ZIndex, set it to a high value when you start the animation and a lower value when the animation is complete. The second part (having a control outside of the window) is more complicated. I tried a few things and this method seemed to be the best. It uses a full screen window instead of a Popup as I encountered cliping issues. A copy of the element is made using RenderTargetBitmap this is then placed in the same position. The original element is hidden whilst the copy is animated.
public void PopupAnimation(UIElement element)
{
double w = element.RenderSize.Width,h = element.RenderSize.Height;
var screen = new Canvas();
var pos = element.PointToScreen(new Point(0, 0));
var rtb = new RenderTargetBitmap((int)w,(int)h, 96, 96, PixelFormats.Pbgra32);
rtb.Render(element);
Image i = new Image { Source = rtb, Width = w, Height = h,Stretch=Stretch.Fill};
Canvas.SetLeft(i, pos.X);
Canvas.SetTop(i, pos.Y);
screen.Children.Add(i);
var window = new Window() {
Content = screen, AllowsTransparency = true,
Width=SystemParameters.PrimaryScreenWidth,Height=SystemParameters.PrimaryScreenHeight,
WindowStyle=WindowStyle.None,ShowInTaskbar=false,Topmost=true,
Background=Brushes.Transparent,ShowActivated=false,Left=0,Top=0
};
var transform = new RotateTransform();
i.RenderTransformOrigin = new Point(0.5, 0.5);
i.RenderTransform = transform;
var anim = new DoubleAnimation { To = 360 };
anim.Completed += (s,e) =>
{
element.Visibility = Visibility.Visible;
var delay = new Storyboard { Duration = TimeSpan.FromSeconds(0.1) };
delay.Completed += (s2, e2) => window.Close();
delay.Begin();
};
window.ContentRendered += (s, e) =>
{
transform.BeginAnimation(RotateTransform.AngleProperty, anim);
element.Visibility = Visibility.Hidden;
};
window.Show();
}
Related
I have a UWP app, which I should start by pointing out that it uses very little XAML. The views are built from JSON object recieved from an API. This means that the vast majority of everything is done in C#, and therefore adds a little complexity to my problem.
I basically want to have a panel (e.g. Grid) that can have rounded corners and have a drop shadow applied to it. The drop shadow should also have the rounded corners, this can be seen in the sample below.
I have looked at the DropShadowPanel as part of the Windows Community Toolkit, but this from what I can tell doesn't do the rounded corners unless I change the content to be a rectangle or some other shape.
To use this as a solution would mean the XAML equivalent of something like:
<Grid>
<toolkit:DropShadowPanel>
<Rectangle />
<toolkit:DropShadowPanel>
<Grid CornerRadius="30">
<!-- My Content -->
</Grid>
</Grid>
To me, this seems like an inefficient use of XAML!
I have also discovered the Composition Pro Toolkit, which to me looks bery interesting as it is all code behind. In particular the ImageFrame control looks to achieve the basis of what I require - although far more advanced than my needs.
The below has been based on the ImageFrame, but doesn't work (content is my grid):
protected FrameworkElement AddDropShadow(FrameworkElement content)
{
var container = new Grid { HorizontalAlignment = content.HorizontalAlignment, VerticalAlignment = content.VerticalAlignment, Width = content.Width, Height = content.Height };
var canvas = new Canvas { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };
content.Loaded += (s, e) =>
{
var compositor = ElementCompositionPreview.GetElementVisual(canvas).Compositor;
var root = compositor.CreateContainerVisual();
ElementCompositionPreview.SetElementChildVisual(canvas, root);
var shadowLayer = compositor.CreateSpriteVisual();
var frameLayer = compositor.CreateLayerVisual();
var frameContent = compositor.CreateShapeVisual();
root.Children.InsertAtBottom(shadowLayer);
root.Children.InsertAtTop(frameLayer);
frameLayer.Children.InsertAtTop(frameContent);
var rectangle = root.Compositor.CreateRoundedRectangleGeometry();
rectangle.Size = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);
rectangle.CornerRadius = new Vector2(30f);
var shape = root.Compositor.CreateSpriteShape(rectangle);
shape.FillBrush = root.Compositor.CreateColorBrush(Colors.Blue);
//var visual = root.Compositor.CreateShapeVisual();
frameContent.Size = rectangle.Size;
frameContent.Shapes.Add(shape);
//create mask layer
var layerEffect = new CompositeEffect
{
Mode = Microsoft.Graphics.Canvas.CanvasComposite.DestinationIn,
Sources = { new CompositionEffectSourceParameter("source"), new CompositionEffectSourceParameter("mask") }
};
var layerEffectFactory = compositor.CreateEffectFactory(layerEffect);
var layerEffectBrush = layerEffectFactory.CreateBrush();
//CompositionDrawingSurface
var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, new Microsoft.Graphics.Canvas.CanvasDevice(forceSoftwareRenderer: false));
var frameLayerMask = graphicsDevice.CreateDrawingSurface(new Size(0, 0), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied);
layerEffectBrush.SetSourceParameter("mask", compositor.CreateSurfaceBrush(frameLayerMask));
frameLayer.Effect = layerEffectBrush;
var shadow = root.Compositor.CreateDropShadow();
//shadow.SourcePolicy = CompositionDropShadowSourcePolicy.InheritFromVisualContent;
shadow.Mask = layerEffectBrush.GetSourceParameter("mask");
shadow.Color = Colors.Black;
shadow.BlurRadius = 25f;
shadow.Opacity = 0.75f;
shadow.Offset = new Vector3(0, 0, 0);
shadowLayer.Shadow = shadow;
content.Opacity = 0; //hiding my actual content to see the results of this
};
container.Children.Add(canvas);
container.Children.Add(content);
return container;
}
In these tests, I am doing the same inefficient use of object, creating another container that has both the composition canvas, and also the grid. If possible, I'd like to apply the composition directly to the original content grid.
I am completely new to composition, so any thoughts, pointers, glaring errors or solutions would be most welcomed.
A Hack Solution?
I have changed my method to the following, visually it works - but is it right?
protected FrameworkElement AddDropShadow(FrameworkElement content)
{
var container = new Grid { HorizontalAlignment = content.HorizontalAlignment, VerticalAlignment = content.VerticalAlignment };
var rectangle = new Rectangle { Fill = new SolidColorBrush(Colors.Transparent) };
content.Loaded += (s, e) =>
{
rectangle.Fill = new SolidColorBrush(Colors.Black);
rectangle.Width = content.ActualWidth;
rectangle.Height = content.ActualHeight;
rectangle.RadiusX = 30;
rectangle.RadiusY = 30;
var compositor = ElementCompositionPreview.GetElementVisual(rectangle).Compositor;
var visual = compositor.CreateSpriteVisual();
visual.Size = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);
var shadow = compositor.CreateDropShadow();
shadow.BlurRadius = 30f;
shadow.Mask = rectangle.GetAlphaMask();
shadow.Opacity = 0.75f;
visual.Shadow = shadow;
ElementCompositionPreview.SetElementChildVisual(rectangle, visual);
};
container.Children.Add(rectangle);
container.Children.Add(content);
return container;
}
The concept here is that my container grid holds a rectangle and my content grid (or other element).
The first error of this method is that is assumes my input FrameworkElement will be rectangular. I imagine that this could be improved upon by creating a bitmap render of the content as highlighted in this blog - but this will likely be quite costly. I also have to ensure that the rectangle size and shape exactly matches that of my main content!
It feels very wrong that there is a rectangle drawn on the screen (even though hidden by my main content). The rectangle is purely there to create the alpha mask so I guess it could be scrapped if the mask is created from the renderof the content.
I've tried setting the visibility of the rectangle to collapsed to remove it from the visual tree. This means that I can attach the visual to the container instead:
ElementCompositionPreview.SetElementChildVisual(container, visual)
However, doing this means that the shadow displays in front of the main content, which means I need some other ui element to attach it too - may as well be the rectangle!
Your solution to use Rectangle is my current workaround everywhere I need rounded shadow under Grid or Border. It's simple and it's plain, why should I complain :)
But if it's not your choice you can draw a rounded rectangle and blur it:
GraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(Compositor, CanvasDevice.GetSharedDevice());
var roudRectMaskSurface = GraphicsDevice.CreateDrawingSurface(new Size(SurfaceWidth + BlurMargin * 2, SurfaceHeight + BlurMargin * 2), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(roudRectMaskSurface))
{
ds.Clear(Colors.Transparent);
ds.FillRoundedRectangle(new Rect(BlurMargin, BlurMargin, roudRectMaskSurface.Size.Width + BlurMargin, roudRectMaskSurface.Size.Height + BlurMargin), YourRadius, YourRadius, Colors.Black);
}
var rectangleMask = Compositor.CreateSurfaceBrush(roudRectMaskSurface);
Now you can apply this surface in the EffectBrush with blur effect to obtain custom shadow.
BlurMargin - corresponds to the blur amount, you need it because your blurred surface will be bigger than initial source rectangle (to avoid blur clip).
I want to move a shape using an animation and am currently using the following code. However this does not result in the object actually moving, it just seems to change the location the object is rendered in, which is expected given we are setting the crossHair.RenterTransform.
EDIT:
To clarify - I am using the animation to simulate what an input file instructions contain and as a result think I can only build the animations in code when parsing the input files. I could be wrong about this but can't see how one could do this in XAML. The input file format is not in XAML.
Because there are a number of sequential moves contained in the input file I an currently using the shapes current position as the start point for the next animation, however this does not work because it seems the animation is not actually moving the shape.
As a work around I am now changing the shapes actual position in the animations completion handler. This seems to be working.
So the question remains as to how can I use the same transform to actually move the shape rather than simply rendering it in a different position ?
private Storyboard MoveCrossHairToPoint(double x, double y)
{
// Adjust for crosshair size so its centered on the x
double xPos = x;
double yPos = y;
double xStart = Canvas.GetLeft(crossHair)+crossHair.Width/2;
double yStart = Canvas.GetTop(crossHair)+crossHair.Height/2;
// Create a NameScope for the page so that
// we can use Storyboards.
NameScope.SetNameScope(this, new NameScope());
// Create a MatrixTransform. This transform
// will be used to move the crossHair.
MatrixTransform crossHairMatrixTransform = new MatrixTransform();
crossHair.RenderTransform = crossHairMatrixTransform;
// Register the transform's name with the page
// so that it can be targeted by a Storyboard.
this.RegisterName("MoveCrossHairMatrixTransform", crossHairMatrixTransform);
// Create the animation path.
PathGeometry animationPath = new PathGeometry();
PathFigure pFigure = new PathFigure();
pFigure.StartPoint = new Point(xStart, yStart);
LineSegment lineSegment = new LineSegment(new Point(x, y),true);
pFigure.Segments.Add(lineSegment);
animationPath.Figures.Add(pFigure);
// Create a path to follow
Path path = new Path();
path.Data = animationPath;
path.Stroke = System.Windows.Media.Brushes.Green;
this.bedCanvas.Children.Add(path);
// Freeze the PathGeometry for performance benefits.
animationPath.Freeze();
// Create a MatrixAnimationUsingPath to move the
// button along the path by animating
// its MatrixTransform.
MatrixAnimationUsingPath matrixAnimation =
new MatrixAnimationUsingPath();
matrixAnimation.PathGeometry = animationPath;
double time = GetTimeForVelocityOverPath(animationPath, this.velocityMove);
matrixAnimation.Duration = TimeSpan.FromSeconds(time);
//matrixAnimation.RepeatBehavior = RepeatBehavior.;
// Set the animation's DoesRotateWithTangent property
// to true so that rotates the rectangle in addition
// to moving it.
matrixAnimation.DoesRotateWithTangent = false;
// Set the animation to target the Matrix property
// of the MatrixTransform named "ButtonMatrixTransform".
Storyboard.SetTargetName(matrixAnimation, "MoveCrossHairMatrixTransform");
Storyboard.SetTargetProperty(matrixAnimation,
new PropertyPath(MatrixTransform.MatrixProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard pathAnimationStoryboard = new Storyboard();
pathAnimationStoryboard.Children.Add(matrixAnimation);
return pathAnimationStoryboard;
}
I'm trying to get an image from the bottom right corner of a canvas to the top left corner of the canvas an repeating this behavior forever. The image should never do the reverse animation from the top left corner to the top right corner. I am new with animation, so I used this example.
At the moment my image is moving from the top left corner to the right bottom corner and back. I tried changing the start position of the image in the canvas, with the result, that the animation used this position as new starting point. I also tried using negative values, to get the image moving to the opposite direction. Reducing the amount of points in the segment got me a shorter animation path, but nothing else. I also set AutoReverse=false, without any changes in the animation behavior.
My ideas are,
- The segment class is building a circle out of the points, but which other class to use?
- The start position has to change, but how do I get the object to move up instead of down the screen?
My Code,
Storyboard animationSB = new Storyboard();
//Image book = createImage(model.keywordCollection[0].cover.small);
Image rope1 = createImage("pack://application:,,,/GUI;component/Resources/rope_trans.png");
rope1.Height = 360.0;
rope1.Width = 185.0;
//Transform to move the book image
TranslateTransform aniRope1 = new TranslateTransform();
this.RegisterName("AnimatedRope1", aniRope1);
rope1.RenderTransform = aniRope1;
Canvas.SetLeft(rope1, 258.659);
Canvas.SetTop(rope1, 583.212);
LeftRope.Children.Add(rope1);
//Anitmation path
PathGeometry animationPath1 = new PathGeometry();
PathFigure pathFigure1 = new PathFigure();
PolyLineSegment lineSegments1 = new PolyLineSegment();
lineSegments1.Points.Add(new Point(LeftRope.ActualWidth, LeftRope.ActualHeight));
lineSegments1.Points.Add(new Point(258.659, 583.212));
lineSegments1.Points.Add(new Point(120.596, 272.665));
lineSegments1.Points.Add(new Point(0, 0));
pathFigure1.Segments.Add(lineSegments1);
animationPath1.Figures.Add(pathFigure1);
animationPath1.Freeze();
//Animate transform to move image along the path on the x-axis
DoubleAnimationUsingPath translateXAnimation1 = new DoubleAnimationUsingPath();
translateXAnimation1.PathGeometry = animationPath1;
translateXAnimation1.Duration = TimeSpan.FromSeconds(6);
translateXAnimation1.Source = PathAnimationSource.X;
translateXAnimation1.AutoReverse = false;
Storyboard.SetTargetName(translateXAnimation1, "AnimatedRope1");
Storyboard.SetTargetProperty(translateXAnimation1, new PropertyPath(TranslateTransform.XProperty));
//Animate transform to move image along the path on the y-axis
DoubleAnimationUsingPath translateYAnimation1 = new DoubleAnimationUsingPath();
translateYAnimation1.PathGeometry = animationPath1;
translateYAnimation1.Duration = TimeSpan.FromSeconds(6);
translateYAnimation1.Source = PathAnimationSource.Y;
translateYAnimation1.AutoReverse = false;
Storyboard.SetTargetName(translateYAnimation1, "AnimatedRope1");
Storyboard.SetTargetProperty(translateYAnimation1, new PropertyPath(TranslateTransform.YProperty));
//Create Storyboard containing and applying the animation
//animationSB.RepeatBehavior = RepeatBehavior.Forever;
animationSB.Children.Add(translateXAnimation1);
animationSB.Children.Add(translateYAnimation1);
animationSB.AutoReverse = false;
The storyboard is started in another method.
I am developing on windows 8.1N with .Net 4.5.1 C# an desktop application.
You should replace the line:
lineSegments1.Points.Add(new Point(LeftRope.ActualWidth, LeftRope.ActualHeight));
with
pathFigure1.StartPoint = new Point(LeftRope.ActualWidth, LeftRope.ActualHeight);
It turns out that if you don't specify the StartPoint for PathFigure, it automatically closes the figure, i.e. connects last and first points from the points collection.
I have three question regarding ILPanel in ILNumerics:
How can I disable the double click interaction of ILPanel to reset the view.
How can I increase the speed of panning using right click and drag. The panning is very slow especially when the shape is vary large.
How can access the elements that I add to a camera and modify them.
Here is a simple example. Two spheres are added to a camera and a ILLable, "Hello", is on top of one of them (the blue one). I want the "Hello" label to jump on top of the green sphere if I rotate the scene with mouse and the green sphere become closer to the camera and visa verse. And, how can I change the color of the spheres say by clicking a button?
private void ilPanel1_Load(object sender, EventArgs e)
{
var scene = new ILScene();
var cam = scene.Camera;
var Sphere1 = new ILSphere();
Sphere1.Wireframe.Visible = true;
Sphere1.Fill.Color = Color.Blue;
Sphere1.Wireframe.Color = Color.Blue;
Sphere1.Transform = Matrix4.Translation(0, 0, 0);
var Sphere2 = new ILSphere();
Sphere2.Wireframe.Visible = true;
Sphere2.Transform = Matrix4.Translation(2, 0, 0);
cam.Add(Sphere1);
cam.Add(Sphere2);
cam.Add(new ILLabel("Hello")
{
Font = new System.Drawing.Font(FontFamily.GenericSansSerif, 8),
Position = new Vector3(0, 0, 1.1),
Anchor = new PointF(0, 1),
});
ilPanel1.Scene = scene;
}
Create a label, which is always on top of other 3D objects
cam.Add(
new ILScreenObject() {
Location = new PointF(0.5f, 0.5f),
Children = {
new ILLabel("Hello")
}
});
See also: http://ilnumerics.net/world3d-and-screen2d-nodes.html
Regarding your "jumping sphere" example: if your want your label to jump to another group (spheres are groups actually) at runtime, you will have to implement that logic into a function which is called at runtime. ilPanel1.BeginRenderFrame might be a good place to check for such updates.
Disable default behavior for the first camera:
scene.Camera.MouseDoubleClick += (_, arg) => {
arg.Cancel = true;
};
See: http://ilnumerics.net/mouse-events.html
React on a button press:
button1.Click += (_,args) => {
ilPanel1.Scene.First<ILSphere>().Fill.Color = Color.Red;
ilPanel1.Refresh();
};
Finding objects in the scene:
see: http://ilnumerics.net/nodes-accessing-managing.html
Controlling the panning speed:
There is no easy way to configure the speed currently. The best way would be to implement the corresponding mouse handler on your own:
Override MouseMove for the camera node.
Implement the panning similar to the way it was done in ILNumerics (see: ILCamera.OnMouseMove in the sources).
Increase the scale factor.
Dont forget to cancel the event processing at the end of your handler.
I would like to draw two shapes in WPF and merge them together. Then, I'd like to attach a drag/drop event to ONE of the original shapes.
So basically, you can only drag if you click on a certain part of the shape, but it will drag the entire shape with you.
Here is some code:
// Set up some basic properties for the two ellipses
Point centerPoint = new Point(100, 100);
SolidColorBrush ellipseColor_1 = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255));
double width_1 = 10; double height_1 = 10;
SolidColorBrush ellipseColor_2 = new SolidColorBrush(Color.FromArgb(50, 255, 0, 0));
double width_2 = 200; double height_2 = 200;
// Create the first ellipse: A small blue dot
// Then position it in the correct location (centerPoint)
Ellipse ellipse_1 = new Ellipse() { Fill = ellipseColor_1, Width = width_1, Height = height_1 };
ellipse_1.RenderTransform = new TranslateTransform(point.X - width_1 / 2, point.Y - height_1 / 2);
// Create the second ellipse: A large red, semi-transparent circle
// Then position it in the correct location (centerPoint)
Ellipse ellipse_2 = new Ellipse() { Fill = ellipseColor_2, Width = width_2, Height = height_2 };
ellipse_2.RenderTransform = new TranslateTransform(point.X - width_2 / 2, point.Y - height_2 / 2);
// ???
// How should I merge these?
// ???
// Now apply drag drop behavior to ONLY ellipse_1
MouseDragElementBehavior dragBehavior = new MouseDragElementBehavior();
dragBehavior.Attach(ellipse_1); // This may change depending on the above
// ...
// Add new element to canvas
This code creates two circles (a big one and a small one). I would like to only be able to drag if the small one is clicked, but I'd like to have them attached so they'll move together without having to manually add code that will take care of this.
If you put them both in a Grid (or Canvas, StackPanel, etc.), and set the drag behavior on the panel, they will be "merged". If you set IsHitTestVisible to false on ellipse_2, it won't respond to any mouse events, so effectively it won't be draggable.