dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.05k stars 1.17k forks source link

Controlling UIElement refresh rate #9326

Open control0forver opened 3 months ago

control0forver commented 3 months ago

I noticed via the System.Windows.Media.CompositionTarget.Rendering event that the app's frames per second are very irregular, when I use the mouse to interact with a visual control, the frames per second instantly goes from around 60 to over 400, and it occurred to me that this is most likely due to my customizing the style of the control. So I wondered if I could limit the frames per second of a UIElement through code; I found this code, but it seems to only limit the framerate of the final rendering of the entire app, and it still doesn't limit the frames per second of a UIElement

private void Application_Startup(object sender, StartupEventArgs e)
{
    Timeline.DesiredFrameRateProperty.OverrideMetadata(
       typeof(Timeline), new FrameworkPropertyMetadata(
       new FrameworkPropertyMetadata { DefaultValue = 60 }
       );
}

Here's the frame rate monitor I mentioned above along with the custom control styling code:

ulong __frameCounter = 0;
readonly Stopwatch __stopwatch = new();
private void CompositionTarget_Rendering(object? sender, EventArgs e)
{
    if (__frameCounter++ == 0)
    {
        // Starting timing.
        __stopwatch.Start();
    }

    // Determine frame rate in fps (frames per second).
    if (__frameCounter >= 60)
    {
        long frameRate = (long)(__frameCounter / __stopwatch.Elapsed.TotalSeconds);
        DebugLabel_FPS.Content = $"FPS: {frameRate}";
        __frameCounter = 0;
        __stopwatch.Restart();
    }
}
<Style x:Key="ButtonStyle" TargetType="Button">
    <Setter Property="Background" Value="{DynamicResource Palette_500}"/>
    <Setter Property="Foreground" Value="{DynamicResource Palette_50}"/>
    <Setter Property="BorderBrush" Value="{x:Null}"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="FontSize" Value="15.6"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border Effect="{StaticResource EffectShadow3}" Background="{TemplateBinding Background}" CornerRadius="{DynamicResource CornerRadiusCornerRadius}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="Button" BasedOn="{StaticResource ButtonStyle}">
    <Setter Property="Template">
    <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <Border Effect="{StaticResource EffectShadow3}" Background="{TemplateBinding Background}" CornerRadius="{DynamicResource CornerRadiusCornerRadius}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
        public static class UIAnimation
        {
            public static void Refresh()
            {
                MouseDownColorAnimation = new() { To = (Color)((ResourceDictionary)App.Current.Resources["CurrentColorPalette"])["Palette_500"], Duration = TimeSpan.FromMilliseconds(100) };
                MouseUpColorAnimation = new() { To = (Color)((ResourceDictionary)App.Current.Resources["CurrentColorPalette"])["Palette_300"], Duration = TimeSpan.FromMilliseconds(300) };
                MouseEnterColorAnimation = new() { To = (Color)((ResourceDictionary)App.Current.Resources["CurrentColorPalette"])["Palette_300"], Duration = TimeSpan.FromMilliseconds(120) };
                MouseLeaveColorAnimation = new() { To = (Color)((ResourceDictionary)App.Current.Resources["CurrentColorPalette"])["Palette_500"], Duration = TimeSpan.FromMilliseconds(170) };
            }

            public static ColorAnimation MouseDownColorAnimation { get; private set; } = new() { To = (Color)((ResourceDictionary)App.Current.Resources["CurrentColorPalette"])["Palette_500"], Duration = TimeSpan.FromMilliseconds(100) };
            public static ColorAnimation MouseUpColorAnimation { get; private set; } = new() { To = (Color)((ResourceDictionary)App.Current.Resources["CurrentColorPalette"])["Palette_300"], Duration = TimeSpan.FromMilliseconds(300) };
            public static ColorAnimation MouseEnterColorAnimation { get; private set; } = new() { To = (Color)((ResourceDictionary)App.Current.Resources["CurrentColorPalette"])["Palette_200"], Duration = TimeSpan.FromMilliseconds(120) };
            public static ColorAnimation MouseLeaveColorAnimation { get; private set; } = new() { To = (Color)((ResourceDictionary)App.Current.Resources["CurrentColorPalette"])["Palette_300"], Duration = TimeSpan.FromMilliseconds(170) };

            public static DoubleAnimation SmallerScaleAnimation { get; private set; } = new() { To = 0.97, Duration = TimeSpan.FromSeconds(0.15), EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut } };
            public static DoubleAnimation SmallSmallerScaleAnimation { get; private set; } = new() { To = 0.92, Duration = TimeSpan.FromSeconds(0.15), EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut } };
            public static DoubleAnimation NormalScaleAnimation { get; private set; } = new() { To = 1, Duration = TimeSpan.FromSeconds(0.25), EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut } };
            public static DoubleAnimation BiggerScaleAnimation { get; private set; } = new() { To = 1.03, Duration = TimeSpan.FromSeconds(0.15), EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut } };

            public static void Button_MouseUp(object sender, MouseEventArgs? e)
            {
                if ((bool)((Control)sender).Resources["UIA_Locked"])
                    return;

                ((Control)sender).Background.BeginAnimation(SolidColorBrush.ColorProperty, MouseUpColorAnimation);

                TransformGroup TG = (TransformGroup)((Control)sender).RenderTransform;
                ScaleTransform st = (ScaleTransform)TG.Children[0];

                st.BeginAnimation(ScaleTransform.ScaleXProperty, NormalScaleAnimation);
                st.BeginAnimation(ScaleTransform.ScaleYProperty, NormalScaleAnimation);

                var ue = (UIElement)sender;
                var uers = ue.RenderSize;
                if (e != null)
                {
                    var rpos = e.GetPosition(ue);
                    var rpx = rpos.X; var rpy = rpos.Y;
                    if (!(
                        (rpx > uers.Width || rpy > uers.Height) ||
                        (rpx < 0 || rpy < 0)
                        ))
                    { Button_MouseEnter(sender, e); }
                }
            }
            public static void Button_MouseDown(object sender, MouseEventArgs? e)
            {
                if ((bool)((Control)sender).Resources["UIA_Locked"])
                    return;

                ((Control)sender).Background.BeginAnimation(SolidColorBrush.ColorProperty, MouseDownColorAnimation);

                TransformGroup TG = (TransformGroup)((Control)sender).RenderTransform;
                ScaleTransform st = (ScaleTransform)TG.Children[0];

                if (e != null)
                {
                    var p = e.MouseDevice.GetPosition((Control)sender);

                    st.CenterX = p.X;
                    st.CenterY = p.Y;
                }

                st.BeginAnimation(ScaleTransform.ScaleXProperty, SmallSmallerScaleAnimation);
                st.BeginAnimation(ScaleTransform.ScaleYProperty, SmallSmallerScaleAnimation);
            }
            public static void Button_MouseLeave(object sender, MouseEventArgs? e)
            {
                if ((bool)((Control)sender).Resources["UIA_Locked"])
                    return;

                ((Control)sender).Background.BeginAnimation(SolidColorBrush.ColorProperty, MouseLeaveColorAnimation);
            }
            public static void Button_MouseEnter(object sender, MouseEventArgs? e)
            {
                if ((bool)((Control)sender).Resources["UIA_Locked"])
                    return;

                ((Control)sender).Background.BeginAnimation(SolidColorBrush.ColorProperty, MouseEnterColorAnimation);
            }
            public static void Button_MouseMove(object sender, MouseEventArgs? e)
            {
                if (e != null)
                {
                    var p = e.MouseDevice.GetPosition((Control)sender);

                    TransformGroup TG = (TransformGroup)((Control)sender).RenderTransform;
                    ScaleTransform st = (ScaleTransform)TG.Children[0];
                    // Inverted
                    st.CenterX = p.X;
                    st.CenterY = p.Y;
                }
            }
            public static void InitButton(Control button)
            {
                try
                {
                    button.Resources["UIA_Locked"] = false;
                    button.RenderTransform = new TransformGroup { Children = new TransformCollection(new Transform[] { new ScaleTransform(1, 1, 1, 1) }) };
                    var color = (SolidColorBrush)button.Background == null ? Colors.Transparent : ((SolidColorBrush)button.Background).Color;
                    button.Background = new SolidColorBrush(color);

                    button.MouseEnter -= Button_MouseEnter;
                    button.MouseLeave -= Button_MouseLeave;
                    button.PreviewMouseDown -= Button_MouseDown;
                    button.PreviewMouseUp -= Button_MouseUp;
                    button.PreviewMouseMove -= Button_MouseMove;

                    button.MouseEnter += Button_MouseEnter;
                    button.MouseLeave += Button_MouseLeave;
                    button.PreviewMouseDown += Button_MouseDown;
                    button.PreviewMouseUp += Button_MouseUp;
                    button.PreviewMouseMove += Button_MouseMove;

                    Button_MouseLeave(button, null);
                }
                catch (Exception)
                {
                    InitCard(button);
                }
            }
            public static void InitButtons(IEnumerable<Button> buttons)
            {
                foreach (var button in buttons)
                {
                    InitButton(button);
                }
            }

            public static void Card_MouseUp(object sender, MouseEventArgs? e)
            {
                if ((bool)((Control)sender).Resources["UIA_Locked"])
                    return;

                TransformGroup TG = (TransformGroup)((Control)sender).RenderTransform;
                ScaleTransform st = (ScaleTransform)TG.Children[0];

                st.BeginAnimation(ScaleTransform.ScaleXProperty, NormalScaleAnimation);
                st.BeginAnimation(ScaleTransform.ScaleYProperty, NormalScaleAnimation);
            }
            public static void Card_MouseDown(object sender, MouseEventArgs? e)
            {
                if ((bool)((Control)sender).Resources["UIA_Locked"])
                    return;

                TransformGroup TG = (TransformGroup)((Control)sender).RenderTransform;
                ScaleTransform st = (ScaleTransform)TG.Children[0];

                if (e != null)
                {
                    var p = e.MouseDevice.GetPosition((Control)sender);

                    st.CenterX = p.X;
                    st.CenterY = p.Y;
                }

                st.BeginAnimation(ScaleTransform.ScaleXProperty, SmallerScaleAnimation);
                st.BeginAnimation(ScaleTransform.ScaleYProperty, SmallerScaleAnimation);
            }
            public static void Card_MouseLeave(object sender, MouseEventArgs? e)
            {
                if ((bool)((Control)sender).Resources["UIA_Locked"])
                    return;

                TransformGroup TG = (TransformGroup)((Control)sender).RenderTransform;
                ScaleTransform st = (ScaleTransform)TG.Children[0];

                st.BeginAnimation(ScaleTransform.ScaleXProperty, NormalScaleAnimation);
                st.BeginAnimation(ScaleTransform.ScaleYProperty, NormalScaleAnimation);

                //var anim = MouseLeaveColorAnimation;
                //anim.To = (Color)((Control)sender).Resources["_UIA_Color"];
                //((Control)sender).Background.BeginAnimation(SolidColorBrush.ColorProperty, anim);
            }
            public static void Card_MouseEnter(object sender, MouseEventArgs? e)
            {
                if ((bool)((Control)sender).Resources["UIA_Locked"])
                    return;

                TransformGroup TG = (TransformGroup)((Control)sender).RenderTransform;
                ScaleTransform st = (ScaleTransform)TG.Children[0];

                if (e != null)
                {
                    var p = e.MouseDevice.GetPosition((Control)sender);

                    st.CenterX = p.X;
                    st.CenterY = p.Y;
                }

                st.BeginAnimation(ScaleTransform.ScaleXProperty, BiggerScaleAnimation);
                st.BeginAnimation(ScaleTransform.ScaleYProperty, BiggerScaleAnimation);
                //((Control)sender).Background.BeginAnimation(SolidColorBrush.ColorProperty, MouseEnterColorAnimation);
            }
            public static void Card_MouseMove(object sender, MouseEventArgs? e)
            {
                if (e != null)
                {
                    var p = e.MouseDevice.GetPosition((Control)sender);

                    TransformGroup TG = (TransformGroup)((Control)sender).RenderTransform;
                    ScaleTransform st = (ScaleTransform)TG.Children[0];
                    st.CenterX = p.X;
                    st.CenterY = p.Y;
                }
            }
            public static void InitCard(Control card)
            {
                try
                {
                    //InitButton(card);
                    card.Resources["UIA_Locked"] = false;
                    card.RenderTransform = new TransformGroup { Children = new TransformCollection(new Transform[] { new ScaleTransform(1, 1, .5, .5) }) };
                    //var color = (SolidColorBrush)card.Background == null ? Colors.Transparent : ((SolidColorBrush)card.Background).Color;
                    //card.Background = new SolidColorBrush(color);

                    //card.Resources["_UIA_Color"] = color;
                    card.MouseEnter -= Card_MouseEnter;
                    card.MouseLeave -= Card_MouseLeave;
                    card.PreviewMouseDown -= Card_MouseDown;
                    card.PreviewMouseUp -= Card_MouseUp;
                    card.PreviewMouseMove -= Card_MouseMove;

                    card.MouseEnter += Card_MouseEnter;
                    card.MouseLeave += Card_MouseLeave;
                    card.PreviewMouseDown += Card_MouseDown;
                    card.PreviewMouseUp += Card_MouseUp;
                    card.PreviewMouseMove += Card_MouseMove;

                    Card_MouseLeave(card, null);
                }
                catch (Exception)
                { }
            }
            public static void InitCards(IEnumerable<Control> cards)
            {
                foreach (var card in cards)
                {
                    InitCard(card);
                }
            }
        }
MichaeIDietrich commented 3 months ago

Does it help you to check whether the RenderingTime has increased after the previous CompositionTarget.Rendering event?

As far as I understand CompositionTarget.Rendering might be raised more than once per frame.

private void CompositionTarget_Rendering(object? sender, EventArgs e)
{
    var args = (RenderingEventArgs)e;

     if (_lastRenderingTime == args.RenderingTime)
        return;

    _lastRenderingTime = args.RenderingTime;
    _currentFps++;
}