Rotating Ellipse Around Center - wpf

I'm trying to build a generic loading control. I have the dummy control all set up with the gradient and masking, but I'm finding when I actually run it in a window it appears to rotate slightly off kilter. If you drop the below code into a user control, then drop that control into a window you should see the behavior I'm describing. I defined RenderTransformOrigin, so I'm a little confused as to why it's still not centering the rotation to the middle of the ellipse.
<UserControl x:Class="SpinningGradient.LoadingControl"
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"
xmlns:local="clr-namespace:SpinningGradient"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<Grid>
<Ellipse Stretch="Uniform"
RenderTransformOrigin=".5,.5">
<Ellipse.RenderTransform>
<RotateTransform x:Name="noFreeze" />
</Ellipse.RenderTransform>
<Ellipse.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Ellipse.RenderTransform).(RotateTransform.Angle)"
By="10"
To="360"
Duration="0:0:1"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Ellipse.Triggers>
<Ellipse.Fill>
<RadialGradientBrush RadiusX="0.5"
RadiusY="0.5">
<RadialGradientBrush.GradientOrigin>
<Point X=".9"
Y=".9" />
</RadialGradientBrush.GradientOrigin>
<RadialGradientBrush.Center>
<Point X="0.5"
Y="0.5" />
</RadialGradientBrush.Center>
<RadialGradientBrush.GradientStops>
<GradientStop Color="Blue"
Offset="1" />
<GradientStop Color="Red"
Offset="-.5" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Ellipse.Fill>
<Ellipse.OpacityMask>
<DrawingBrush>
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Brush>
<SolidColorBrush Color="Black" />
</GeometryDrawing.Brush>
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="EvenOdd">
<RectangleGeometry Rect="0,0,100,100" />
<EllipseGeometry RadiusX="30"
RadiusY="30"
Center="50,50" />
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Thickness="0"
Brush="Black" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Ellipse.OpacityMask>
</Ellipse>
</Grid>
</UserControl>

The UserControl below creates a very similar or the same visual result with a lot less XAML. The ratio between StrokeThickness and RadiusX/RadiusY determines the relative stroke width.
<UserControl ...>
<Viewbox>
<Path StrokeThickness="1" Stretch="Uniform" RenderTransformOrigin="0.5,0.5">
<Path.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Angle"
To="360" Duration="0:0:1" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Path.Triggers>
<Path.RenderTransform>
<RotateTransform/>
</Path.RenderTransform>
<Path.Data>
<EllipseGeometry RadiusX="2" RadiusY="2"/>
</Path.Data>
<Path.Stroke>
<RadialGradientBrush GradientOrigin="0.9,0.9">
<RadialGradientBrush.GradientStops>
<GradientStop Color="Blue" Offset="1" />
<GradientStop Color="Red" Offset="-0.5" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Path.Stroke>
</Path>
</Viewbox>
</UserControl>

The reason for that is that even though your Ellipse looks like a circle, it is still stretching to the size of its container (or, more precisely, to the size of the LoadingControl). So, unless the said LoadingControl is contained within a perfect square, the Ellipse's actual center point is going to be offset from the center point of the visible circle, either horizontally or vertically. And around that actual center point (rather than the apparent center point) the Ellipse is rotated.
#Clemens already gave you a handful of options to remedy this situation in the comments section, but let me quickly go over them:
Set Width and Height of your Ellipse to the same value - this is limiting since you have to hard-code the size of your control
Bind the Width to the ActualHeight of the Ellipse (or the other way around) - this gives you some extent of dynamic behavior, but things go bad if the target dimension constraint is smaller than the source dimension constraint (e.g. if you bind Width to ActualHeight, things go bad when your control is contained within a rectangle with its width smaller than its height - the Ellipse is clipped)
Put the Ellipse in a square container - or more precisely, within a container that would always arrange it in a square
I strongly recommend the last approach, since it gives you full flexibility in sizing your control. Now I don't think there's a component shipped with WPF that could accomplish this task, but it is fairly simple to devise one of your own, and I guarantee from my experience, that it'll become a useful tool in your toolbox. Here's an example implementation, which is based on a Decorator:
public class SquareDecorator : Decorator
{
protected override Size ArrangeOverride(Size arrangeSize)
{
if (Child != null)
{
var paddingX = 0d;
var paddingY = 0d;
if (arrangeSize.Width > arrangeSize.Height)
paddingX = (arrangeSize.Width - arrangeSize.Height) / 2;
else
paddingY = (arrangeSize.Height - arrangeSize.Width) / 2;
var rect = new Rect
{
Location = new Point(paddingX, paddingY),
Width = arrangeSize.Width - 2 * paddingX,
Height = arrangeSize.Height - 2 * paddingY,
};
Child.Arrange(rect);
}
return arrangeSize;
}
protected override Size MeasureOverride(Size constraint)
{
var desiredSize = new Size();
if (Child != null)
{
Child.Measure(constraint);
var max = Math.Min(constraint.Width, constraint.Height);
var desired = Math.Max(Child.DesiredSize.Width, Child.DesiredSize.Height);
desiredSize.Width = desiredSize.Height = Math.Min(desired, max);
}
return desiredSize;
}
}
Then you only need to slightly modify your control:
<UserControl (...)>
<local:SquareDecorator>
<Ellipse (...) />
</local:SquareDecorator>
</UserControl>

Related

Color change animation with a left-to-right flow

I wish to animate background color change from yellow to red with a flow from left to right. How to achieve this kind of animation in wpf?
A ColorAnimation could provide the gradual change from yellow to red but because you want it to flow from left to right it might be easier to use a LinearGradient.
Set it up like this
GradientStopOffet, color
0, red
0, red
0, yellow
1, yellow
This would make the area completely yellow.
Then you animate the offset of the third gradientstop from 0 to 1
This would slowly turn the area into a gradient from red to yellow.
Once this animation has finished (or is half way) animate the second gradientstop offset from 0 to 1
This would make the entire area red.
By moving the second and third gradientstops the brush will have a 'smooth' color transition between the four gradient stops: between the first and the second, the second and the third and the third and fourth. At the beginning and at the end the transitions between the gradients stops that are at the same offset are not visible and thereby hide the color transition.
Here is an example. Play around with the starting times and durations to make the animation to your liking.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<Storyboard x:Key="ToRed" >
<DoubleAnimation Storyboard.TargetName="YellowStop"
Storyboard.TargetProperty="Offset"
To="1"
Duration="0:0:1" />
<DoubleAnimation Storyboard.TargetName="RedStop"
Storyboard.TargetProperty="Offset"
BeginTime="0:0:0.5"
To="1"
Duration="0:0:1" />
</Storyboard>
</Window.Resources>
<Grid>
<StackPanel VerticalAlignment="Top"
Orientation="Horizontal">
<Button Click="ToRedButton_Click">To red</Button>
</StackPanel>
<Rectangle Margin="0,50,0,0">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,0">
<GradientStop Offset="0"
Color="Red" />
<GradientStop x:Name="RedStop"
Offset="0"
Color="Red" />
<GradientStop x:Name="YellowStop"
Offset="0"
Color="Yellow" />
<GradientStop Offset="1"
Color="Yellow" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
C# code for the button click:
private void ToRedButton_Click(object sender, RoutedEventArgs e)
{
var toRedAnimation = this.FindResource("ToRed") as Storyboard;
if(toRedAnimation != null)
{
toRedAnimation.Begin();
}
}
If you want a hard transition, animate the offset of the redstop at the same start time as the yellow animation.
Here is another setup, it looks different and animates the color:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<Storyboard x:Key="ToRed2">
<DoubleAnimation Storyboard.TargetName="MiddleStop"
Storyboard.TargetProperty="Offset"
To="1"
Duration="0:0:1" />
<ColorAnimation Storyboard.TargetName="MiddleStop"
Storyboard.TargetProperty="Color"
BeginTime="0:0:1"
To="Red"
Duration="0:0:1" />
</Storyboard>
</Window.Resources>
<Grid>
<StackPanel VerticalAlignment="Top"
Orientation="Horizontal">
<Button Click="ToRedButton_Click">To red</Button>
</StackPanel>
<Rectangle Margin="0,50,0,0">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0"
EndPoint="1,0">
<GradientStop Offset="0"
Color="Red" />
<GradientStop x:Name="MiddleStop"
Offset="0"
Color="Yellow" />
<GradientStop Offset="1"
Color="Yellow" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Window>
I can suggest you to use blend. This is the most easy way to edit your xaml.

Image transition animation on ribbon button

How can I create a WPF ribbon button switching between two images, with an animated "wipe" transition between images? I can't place both images within a a grid within the button and animate the opacity of each image in turn (as suggested here) because I can't set the content of the ribbon directly, only the LargeImageSource/SmallImageSource and Label properties.
Update
I tried BorisB.'s suggestion together with the animation from the link above, but there is now no image displayed in the ribbon button. Removing the animation, opacity mask and multiple images, and leaving the following code also doesn't show the image at all.
<RibbonToggleButton Label="Dashboard" Name="btnDashboard" IsChecked="True">
<RibbonToggleButton.LargeImageSource>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<ImageDrawing ImageSource="/Icons/Dashboard.png" />
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</RibbonToggleButton.LargeImageSource>
</RibbonToggleButton>
You can use a DrawingImage as an ImageSource. Then you can assign a DrawingGroup as a DrawingImage.Drawing. That drawing group can contain two ImageDrawings wrapped in their own DrawingGroups, so you could apply the approach from your link:
<Grid>
<Ribbon>
<RibbonTab x:Name="HomeTab" Header="Home">
<RibbonGroup x:Name="Group1" Header="Group1">
<RibbonButton x:Name="Button1" Label="Button1">
<RibbonButton.LargeImageSource>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup>
<ImageDrawing Rect="0, 0, 32, 32" ImageSource=ImageOne.png"/>
</DrawingGroup>
<DrawingGroup>
<ImageDrawing Rect="0, 0, 32, 32" ImageSource="ImageTwo.png"/>
<DrawingGroup.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Black" x:Name="BlackStop"/>
<GradientStop Offset="0" Color="Transparent" x:Name="TransparentStop"/>
</LinearGradientBrush>
</DrawingGroup.OpacityMask>
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</RibbonButton.LargeImageSource>
</RibbonButton>
</RibbonGroup>
</RibbonTab>
</Ribbon>
</Grid>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="TransparentStop"
Storyboard.TargetProperty="Offset" By="1" Duration="0:0:2" />
<DoubleAnimation Storyboard.TargetName="BlackStop"
Storyboard.TargetProperty="Offset" By="1" Duration="0:0:2"
BeginTime="0:0:0.05" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Window.Triggers>
The wrapping DrawingGroups are used so you can use OpacityMask, which is essential for the effect.

RenderTransform.TranslateTransform animating in usercontrol in wpf

i want to create a wpf usercontrol that displays an Image and has a toolbar panel,
i want to set features listed below to the my userControl:
tool bar panel hidden when mouse cursor is out side of the usercontrol.
When mouse cursor enter the usercontrol,toolbar panel move up from the bottom of the usercontrol and locate at the bottom of the usercontrol.
i create it but ,i have a problem ,
see bellow:
when mouse cursor enter the UserControl:
and when mouse cursor leaved UserControl:
My Problem:
when panel is moving out side of the UserControl,the out side parts must be invisible,
like bellow:
my UserControl's Xaml codes:
<UserControl.Resources>
<Storyboard x:Key="SB_MouseEnter">
<DoubleAnimation To="0" Storyboard.TargetName="button_panel" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)" Duration="0:0:0.2"/>
</Storyboard>
<Storyboard x:Key="SB_MouseLeave">
<DoubleAnimation To="40" Storyboard.TargetName="button_panel" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)" Duration="0:0:0.2"/>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard Storyboard="{StaticResource ResourceKey=SB_MouseEnter}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard Storyboard="{StaticResource ResourceKey=SB_MouseLeave}"/>
</EventTrigger>
</UserControl.Triggers>
<Border CornerRadius="4" BorderBrush="SeaGreen" BorderThickness="2">
<Grid>
<Image Source="Images/Koala.jpg"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" Name="button_panel" Height="40" RenderTransformOrigin="0.5,0.5">
<StackPanel.RenderTransform>
<TranslateTransform Y="40"/>
</StackPanel.RenderTransform>
<StackPanel.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="1"/>
<GradientStop Color="#66FFFFFF"/>
</LinearGradientBrush>
</StackPanel.Background>
<Button Padding="5" Margin="5,0" Width="80" Height="30">
Open
</Button>
<Button Padding="5" Margin="5,0" Width="80" Height="30">
Clear
</Button>
</StackPanel>
</Grid>
</Border>
Just Clip the button_panel when it leaves the Border with ClipToBounds="True" on the Border
something like:
...
<Border BorderBrush="SeaGreen"
BorderThickness="2"
ClipToBounds="True"
CornerRadius="4">
...
Now with ClipToBounds="True" being set on the Border, any child of the Border which is outside the Border is not going to be visible.
This would thus satisfy your requirement of having the StackPanel invisible when the mouse is not over the image as well since TranslateTransform keeps it outside the Border. Thus you only see the StackPanel when mouse is over the image and the Slide in/out is only visible inside the Border.

Animating an overlay color over a TextBlock

I'm trying to come up with a creative solution to give this particular effect:
My initial idea: A dynamically sized rectangle with a chroma key shader effect will slide into place over the text. However, I do not want to kill the fidelity of the text edges which tends to happen with shaders.
I also considered using the FormattedText class, though I'm not sure it supports what I'm trying to do.
Any suggestions?
EDIT
To clarify, the text will be essentially a 'TabItem'. I would like the highlighted block to float across all tab items to the selected item. They are currently laid out in a Canvas with logic handling their positioning. A simple animation would not suffice it would seem.
This should give you the effect you want. This uses a gradient brush for the color, but it uses 3 gradient stops to make sure that the color changes immediately from one to the next with no gradient in between.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestingWPF"
mc:Ignorable="d"
x:Class="TestingWPF.TestWindow"
d:DesignWidth="477" d:DesignHeight="214"
Background="Black">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="74" FontWeight="Bold">
<TextBlock.Foreground>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="White" Offset="0"/>
<GradientStop x:Name="WhiteOffset" Color="White" Offset="1"/>
<GradientStop x:Name="GrayOffset" Color="Gray" Offset="1"/>
</LinearGradientBrush>
</TextBlock.Foreground>
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Offset" Duration="0:0:1" RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetName="WhiteOffset" From="0" To="1" />
<DoubleAnimation Storyboard.TargetName="GrayOffset" From="0" To="1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
Some Text
</TextBlock>
</Window>

Binding a line to the edge of an ellipse

On a canvas I have an ellipse rotated by a RotateTransform through animation. I wish to add a line with one end attached to a point on the ellipse. Can I somehow bind to a point on the ellipse?
You can animate both the Ellipse and the line together, like so:
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Canvas.Resources>
<PathGeometry x:Key="lineEndPath">
<PathFigure StartPoint="25,50">
<ArcSegment IsLargeArc="True" Point="100,50" Size="25,25" SweepDirection="Clockwise"/>
<ArcSegment IsLargeArc="True" Point="25,50" Size="25,25" SweepDirection="Clockwise"/>
</PathFigure>
</PathGeometry>
</Canvas.Resources>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:5" From="0" RepeatBehavior="Forever" Storyboard.TargetName="rotTF" Storyboard.TargetProperty="Angle" To="360"/>
<PointAnimationUsingPath Duration="0:0:5" PathGeometry="{StaticResource lineEndPath}" RepeatBehavior="Forever" Storyboard.TargetName="lineEndPoint" Storyboard.TargetProperty="Point"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<Ellipse Width="75" Height="50" Canvas.Left="25" Canvas.Top="25" Stroke="Black">
<Ellipse.RenderTransform>
<RotateTransform x:Name="rotTF" CenterX="37.5" CenterY="25"/>
</Ellipse.RenderTransform>
</Ellipse>
<Path Stroke="Black" StrokeThickness="1.0">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment x:Name="lineEndPoint"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<Path Data="{StaticResource lineEndPath}" Stroke="Black" StrokeDashArray="2,0,0" StrokeThickness="1.0"/>
</Canvas>
We animate one end of a LineSegment with a PointAnimationUsingPath, and set the path to a circle (shown by the dotted line).
I'm not sure what the problem is. You can add another element into your Canvas that lines up correctly and apply the transform to the canvas which will rotate both elements?
If you're asking if there's any way to say "line up with this" on the line then no, you can't do that as far as I now. For complicated layouts like this you can either trial and error with KaXaml/Bland, or use Illustrator to lay it out and then export to XAML.
Assuming I understand correctly, you're going to have to figure out the math to change your line's end point. Sorry, don't know the formula offhand, but you'll basically find your point on the ellipse, then figure out it's position given the angle of the rotation, and then change your line's end point using that information.
In order to connect two elements at their edges by a Line, I use a bounding box method as explained in Connecting two WPF canvas elements by a line, without using anchors?.

Resources