Is it possible to make animations, like the transitions generated by Bunifu, with standard WinForms Controls in the VB.NET language?
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
PanelForm.Visible = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BunifuTransition1.Show(PanelForm)
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
BunifuTransition1.Hide(PanelForm)
End Sub
End Class
To add some animation effects to a window (Controls are windows), you can use the Win32 AnimateWindow function.
This function allows to generate some predefined effects: roll, slide, collapse or expand and alpha-blended fade (which can only be applied to a top-level window, though).
It's missing any effect that scale the content of the window, as shown in the question.
I've added this missing effect (along with the already supported ones) in an extension method, Animate()
that is applied to all classes that derive from Control.
Which means you can also animate a Form that is not a top-level window (.TopLevel = False
)
For example, to animate a Panel (and the Controls it hosts), write:
SomePanel.Animate(AnimationMode.OpenScale, 500)
to add an opening animation that lasts 500 milliseconds. Or, e.g.,
SomePanel.Animate(AnimationMode.CloseScale, 500)
to add a closing animation with the same duration.
Try out the other effects you can select, settings a different AnimationMode
.
This is how it works:
The generated GIF animation makes it a little sluggish
Add this Module to any WinForms Project.
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Module ExtensionMethods
''' <summary>
''' Add opening or closing effects to a Control
''' </summary>
''' <param name="window">The Control to animate</param>
''' <param name="mode">The animation effect</param>
''' <param name="animationSpeed">The overall time, in milliseconds, of the animation</param>
<Extension()>
Public Sub Animate(window As Control, mode As AnimationMode, animationSpeed As Integer)
If isAnimating Then Return
isAnimating = True
If mode.HasFlag(AnimationMode.OpenScale) OrElse mode.HasFlag(AnimationMode.CloseScale) Then
AnimateScale(window, mode, animationSpeed)
Else
window.Show()
If Not mode.HasFlag(AnimationMode.Hide) Then window.Hide()
AnimateWindow(window.Handle, CType(animationSpeed, UInteger), mode)
If Not mode.HasFlag(AnimationMode.Hide) Then window.Show()
End If
End Sub
Private isAnimating As Boolean = False
Private animateBitmap As Bitmap = Nothing
Private animateBitmapBounds As Rectangle = Rectangle.Empty
Private Sub AnimateScale(window As Control, mode As AnimationMode, Optional animationSpeed As Integer = 200)
Dim steps = 10
If animationSpeed < 200 Then animationSpeed = 200
Dim timerSteps = animationSpeed \ (steps + 1)
Dim animationFrames As List(Of Bitmap) = Nothing
Using bmpSource = New Bitmap(window.Width, window.Height, PixelFormat.Format32bppArgb)
window.Show()
window.DrawToBitmap(bmpSource, New Rectangle(Point.Empty, window.Size))
window.Hide()
animationFrames = GetAnimationFrames(steps, bmpSource, mode.HasFlag(AnimationMode.CloseScale))
End Using
Dim windowParent = If(window.Parent Is Nothing, window, window.Parent)
animateBitmapBounds = window.Bounds
Dim timer = New System.Windows.Forms.Timer() With {.Interval = timerSteps}
AddHandler timer.Tick,
Sub()
If steps < 0 Then
timer.Stop()
RemoveHandler windowParent.Paint, AddressOf AnimateWindowParent
animationFrames.ForEach(Sub(img) img.Dispose())
animationFrames = Nothing
animateBitmap = Nothing
timer.Dispose()
isAnimating = False
Else
animateBitmap = animationFrames(steps)
windowParent.Invalidate(animateBitmapBounds)
If steps = 0 AndAlso mode.HasFlag(AnimationMode.OpenScale) Then window.Show()
End If
steps -= 1
End Sub
AddHandler windowParent.Paint, AddressOf AnimateWindowParent
window.Hide()
timer.Start()
End Sub
Private Function GetAnimationFrames(framesCount As Integer, sourceImage As Image, collapse As Boolean) As List(Of Bitmap)
Dim animationFrames As New List(Of Bitmap)(framesCount)
Dim scaleWidth = sourceImage.Width \ framesCount
Dim scaleHeight = sourceImage.Height \ framesCount
For frame As Integer = 1 To framesCount
Dim imageSize = New Size(sourceImage.Width - frame * scaleWidth, sourceImage.Height - frame * scaleHeight)
Dim imagePos = New Point((sourceImage.Width - imageSize.Width) \ 2, (sourceImage.Height - imageSize.Height) \ 2)
Dim imageFrame = New Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format32bppArgb)
Using g = Graphics.FromImage(imageFrame)
g.DrawImage(sourceImage, New RectangleF(imagePos, imageSize),
sourceImage.GetBounds(GraphicsUnit.Pixel), GraphicsUnit.Pixel)
If collapse Then
animationFrames.Insert(0, imageFrame)
If frame = framesCount Then animationFrames.Insert(0, New Bitmap(2, 2))
Else
animationFrames.Add(imageFrame)
If frame = framesCount Then animationFrames.Add(New Bitmap(2, 2))
End If
End Using
Next
Return animationFrames
End Function
Private Sub AnimateWindowParent(sender As Object, e As PaintEventArgs)
If animateBitmap Is Nothing Then Return
e.Graphics.DrawImage(animateBitmap, animateBitmapBounds)
End Sub
<Flags()>
Public Enum AnimationMode As UInteger
CloseCentered = AW_CENTER Or AW_HIDE
CloseDownwards = AW_VER_POSITIVE Or AW_HOR_POSITIVE Or AW_HIDE
CloseUpwards = AW_VER_NEGATIVE Or AW_HOR_NEGATIVE Or AW_HIDE
OpenCentered = AW_CENTER
OpenDownwards = AW_HOR_POSITIVE Or AW_VER_POSITIVE
OpenUpwards = AW_HOR_NEGATIVE Or AW_VER_NEGATIVE
OpenScale = &H20
CloseScale = &H40 Or Hide
SlideToRight = AW_SLIDE Or AW_HOR_POSITIVE
SlideToLeft = AW_SLIDE Or AW_HOR_NEGATIVE
SlideDownwards = AW_SLIDE Or AW_VER_POSITIVE
SlideUpwards = AW_SLIDE Or AW_VER_NEGATIVE
Hide = AW_HIDE
End Enum
Private Const AW_HOR_POSITIVE As UInteger = &H1
Private Const AW_HOR_NEGATIVE As UInteger = &H2
Private Const AW_VER_POSITIVE As UInteger = &H4
Private Const AW_VER_NEGATIVE As UInteger = &H8
Private Const AW_CENTER As UInteger = &H10
Private Const AW_HIDE As UInteger = &H10000
Private Const AW_ACTIVATE As UInteger = &H20000
Private Const AW_SLIDE As UInteger = &H40000
Private Const AW_BLEND As UInteger = &H80000
<DllImport("user32.dll", SetLastError:=True)>
Private Function AnimateWindow(hwnd As IntPtr, time As UInteger, flags As AnimationMode) As Boolean
End Function
End Module
C# version, just in case:
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public static class ExtensionMethods {
public static void Animate(this Control window, AnimationMode mode, int animationSpeed) {
if (isAnimating) return;
isAnimating = true;
if (mode.HasFlag(AnimationMode.OpenScale) || mode.HasFlag(AnimationMode.CloseScale)) {
AnimateScale(window, mode, animationSpeed);
}
else {
window.Show();
if (!mode.HasFlag(AnimationMode.Hide)) window.Hide();
AnimateWindow(window.Handle, (uint)animationSpeed, mode);
if (!mode.HasFlag(AnimationMode.Hide)) window.Show();
}
}
private static bool isAnimating = false;
private static Bitmap? animateBitmap = null;
private static Rectangle animateBitmapBounds = Rectangle.Empty;
private static void AnimateScale(Control window, AnimationMode mode, int animationSpeed = 200) {
int steps = 10;
if (animationSpeed < 200) animationSpeed = 200;
int timerSteps = animationSpeed / (steps + 1);
List<Bitmap>? animationFrames = null;
using (var bmpSource = new Bitmap(window.Width, window.Height, PixelFormat.Format32bppArgb)) {
window.Show();
window.DrawToBitmap(bmpSource, new Rectangle(Point.Empty, window.Size));
window.Hide();
animationFrames = GetAnimationFrames(steps, bmpSource, mode.HasFlag((Enum)AnimationMode.CloseScale));
}
var windowParent = window.Parent is null ? window : window.Parent;
animateBitmapBounds = window.Bounds;
var timer = new System.Windows.Forms.Timer() { Interval = timerSteps };
timer.Tick += (_, _) =>
{
if (steps < 0) {
timer.Stop();
windowParent.Paint -= AnimateWindowParent;
animationFrames.ForEach(img => img.Dispose());
animationFrames = null;
animateBitmap = null;
timer.Dispose();
isAnimating = false;
}
else {
animateBitmap = animationFrames[steps];
windowParent.Invalidate(animateBitmapBounds);
if (steps == 0 && mode.HasFlag(AnimationMode.OpenScale))
window.Show();
}
steps -= 1;
};
windowParent.Paint += AnimateWindowParent;
window.Hide();
timer.Start();
}
private static List<Bitmap> GetAnimationFrames(int framesCount, Image sourceImage, bool collapse) {
var animationFrames = new List<Bitmap>(framesCount);
var scaleWidth = sourceImage.Width / framesCount;
var scaleHeight = sourceImage.Height / framesCount;
for (int frame = 1, loopTo = framesCount; frame <= loopTo; frame++) {
var imageSize = new Size(sourceImage.Width - frame * scaleWidth, sourceImage.Height - frame * scaleHeight);
var imagePos = new Point((sourceImage.Width - imageSize.Width) / 2, (sourceImage.Height - imageSize.Height) / 2);
var imageFrame = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(imageFrame)) {
var unit = GraphicsUnit.Pixel;
g.DrawImage(sourceImage, new RectangleF(imagePos, imageSize), sourceImage.GetBounds(ref unit), unit);
if (collapse) {
animationFrames.Insert(0, imageFrame);
if (frame == framesCount) animationFrames.Insert(0, new Bitmap(2, 2));
}
else {
animationFrames.Add(imageFrame);
if (frame == framesCount) animationFrames.Add(new Bitmap(2, 2));
}
}
}
return animationFrames;
}
private static void AnimateWindowParent(object? sender, PaintEventArgs e) {
if (animateBitmap is null) return;
e.Graphics.DrawImage(animateBitmap, animateBitmapBounds);
}
[Flags()]
public enum AnimationMode : uint {
CloseCentered = AW_CENTER | AW_HIDE,
CloseDownwards = AW_VER_POSITIVE | AW_HOR_POSITIVE | AW_HIDE,
CloseUpwards = AW_VER_NEGATIVE | AW_HOR_NEGATIVE | AW_HIDE,
OpenCentered = AW_CENTER,
OpenDownwards = AW_HOR_POSITIVE | AW_VER_POSITIVE,
OpenUpwards = AW_HOR_NEGATIVE | AW_VER_NEGATIVE,
OpenScale = 0x20U,
CloseScale = 0x40U | Hide,
SlideToRight = AW_SLIDE | AW_HOR_POSITIVE,
SlideToLeft = AW_SLIDE | AW_HOR_NEGATIVE,
SlideDownwards = AW_SLIDE | AW_VER_POSITIVE,
SlideUpwards = AW_SLIDE | AW_VER_NEGATIVE,
Hide = AW_HIDE
}
#region NativeMethods
private const uint AW_HOR_POSITIVE = 0x1U;
private const uint AW_HOR_NEGATIVE = 0x2U;
private const uint AW_VER_POSITIVE = 0x4U;
private const uint AW_VER_NEGATIVE = 0x8U;
private const uint AW_CENTER = 0x10U;
private const uint AW_HIDE = 0x10000U;
private const uint AW_ACTIVATE = 0x20000U;
private const uint AW_SLIDE = 0x40000U;
private const uint AW_BLEND = 0x80000U;
[DllImport("user32.dll", SetLastError = true)]
private static extern bool AnimateWindow(IntPtr hwnd, uint time, AnimationMode flags);
#endregion
}