V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yanjinhua
V2EX  ›  .NET

老板加薪!看我做的 WPF Loading!

  •  
  •   yanjinhua · 53 天前 · 3678 次点击
    这是一个创建于 53 天前的主题,其中的信息可能已经有所发展或是发生改变。

    老板加薪!看我做的 WPF Loading !!!

    控件名:RingLoading

    作者:WPFDevelopersOrg

    原文链接: https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal

    • 框架使用大于等于.NET40
    • Visual Studio 2022;
    • 项目使用 MIT 开源许可协议;

    👉视频点我效果预览⬅⬅

    • 最外层使用Viewbox为父控件内部嵌套创建三组 Grid -> Ellipse 、Border 分别给它们指定不同的Angle从左侧开始 -135 225 54,做永久 Angle 动画;
    • PART_Ring1.RotateTransform.AngleFrom -135-495
    • PART_Ring2.RotateTransform.AngleFrom 225-585
    • PART_Ring3.RotateTransform.AngleFrom -54-315
    • 如何绘制;

    • EllipseStrokeDashArray进行设置23 100就能达到效果;

    • Border 做为圆设置 Effect 可实现阴影效果;

    1 )RingLoading.cs代码如下;

    using System.Windows;
    using System.Windows.Controls;
    
    namespace WPFDevelopers.Controls
    {
        public class RingLoading : Control
        {
            // Using a DependencyProperty as the backing store for IsStart.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty IsStartProperty =
                DependencyProperty.Register("IsStart", typeof(bool), typeof(RingLoading), new PropertyMetadata(default));
    
            // Using a DependencyProperty as the backing store for ProgressValue.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty ProgressValueProperty =
                DependencyProperty.Register("ProgressValue", typeof(double), typeof(RingLoading),
                    new PropertyMetadata(0d, OnProgressValueChangedCallBack));
    
            // Using a DependencyProperty as the backing store for Progress.  This enables animation, styling, binding, etc...
            internal static readonly DependencyProperty ProgressProperty =
                DependencyProperty.Register("Progress", typeof(string), typeof(RingLoading), new PropertyMetadata(default));
    
            // Using a DependencyProperty as the backing store for Maximum.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty MaximumProperty =
                DependencyProperty.Register("Maximum", typeof(double), typeof(RingLoading),
                    new PropertyMetadata(100d, OnMaximumPropertyChangedCallBack));
    
            // Using a DependencyProperty as the backing store for Description.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty DescriptionProperty =
                DependencyProperty.Register("Description", typeof(string), typeof(RingLoading),
                    new PropertyMetadata(default));
    
            static RingLoading()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(RingLoading),
                    new FrameworkPropertyMetadata(typeof(RingLoading)));
            }
    
            public bool IsStart
            {
                get => (bool)GetValue(IsStartProperty);
                set => SetValue(IsStartProperty, value);
            }
    
    
            public double ProgressValue
            {
                get => (double)GetValue(ProgressValueProperty);
                set => SetValue(ProgressValueProperty, value);
            }
    
    
            internal string Progress
            {
                get => (string)GetValue(ProgressProperty);
                set => SetValue(ProgressProperty, value);
            }
    
    
            public double Maximum
            {
                get => (double)GetValue(MaximumProperty);
                set => SetValue(MaximumProperty, value);
            }
    
            public string Description
            {
                get => (string)GetValue(DescriptionProperty);
                set => SetValue(DescriptionProperty, value);
            }
    
            private static void OnProgressValueChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                if (!(d is RingLoading control))
                    return;
    
                if (!double.TryParse(e.NewValue?.ToString(), out var value))
                    return;
    
                var progress = value / control.Maximum;
                control.SetCurrentValue(ProgressProperty, progress.ToString("P0"));
            }
    
            private static void OnMaximumPropertyChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                if (!(d is RingLoading control))
                    return;
    
                if (!double.TryParse(e.NewValue?.ToString(), out var maxValue))
                    return;
    
                if (maxValue <= 0)
                    return;
    
                var progress = control.ProgressValue / maxValue;
                control.SetCurrentValue(ProgressProperty, progress.ToString("P0"));
            }
        }
    }
    

    2 )RingLoading.xaml代码如下;

     <Style TargetType="controls:RingLoading" BasedOn="{StaticResource ControlBasicStyle}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="controls:RingLoading">
                        <ControlTemplate.Resources>
                            <Storyboard x:Key="PART_Resource_Storyboard" RepeatBehavior="Forever">
                                <DoubleAnimation To="-495" Duration="0:0:1.5" Storyboard.TargetName="PART_Ring1"  Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"/>
                                <DoubleAnimation To="585" Duration="0:0:1.5" Storyboard.TargetName="PART_Ring2"  Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"/>
                                <DoubleAnimation To="-315" Duration="0:0:1.5" Storyboard.TargetName="PART_Ring3"  Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"/>
                            </Storyboard>
                        </ControlTemplate.Resources>
    
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
    
                            <Viewbox HorizontalAlignment="Center" VerticalAlignment="Center" >
                                <Border Padding="10" Width="100" Height="100" >
                                    <Grid>
                                        <Grid x:Name="PART_Ring1" Width="60" Height="60" HorizontalAlignment="Center" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5">
                                            <Grid.RenderTransform>
                                                <TransformGroup>
                                                    <ScaleTransform/>
                                                    <SkewTransform/>
                                                    <RotateTransform Angle="-135"/>
                                                    <TranslateTransform/>
                                                </TransformGroup>
                                            </Grid.RenderTransform>
                                            <Ellipse Stroke="Red" StrokeThickness="2" StrokeDashArray="23 100" RenderTransformOrigin="0.5,0.5"/>
                                            <Border Width="10" Height="10" CornerRadius="10" Background="Red" HorizontalAlignment="Right" Margin="0,0,-4,0">
                                                <Border.Effect>
                                                    <DropShadowEffect BlurRadius="10" ShadowDepth="0" Color="Red"/>
                                                </Border.Effect>
                                            </Border>
                                        </Grid>
    
                                        <Grid x:Name="PART_Ring2" Width="60" Height="60" HorizontalAlignment="Left" VerticalAlignment="Bottom" RenderTransformOrigin="0.5,0.5">
                                            <Grid.RenderTransform>
                                                <TransformGroup>
                                                    <ScaleTransform/>
                                                    <SkewTransform/>
                                                    <RotateTransform Angle="225"/>
                                                    <TranslateTransform/>
                                                </TransformGroup>
                                            </Grid.RenderTransform>
                                            <Ellipse Stroke="Purple" StrokeThickness="2" StrokeDashArray="23 100"/>
                                            <Border Width="10" Height="10" CornerRadius="10" Background="Purple" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,-4">
                                                <Border.Effect>
                                                    <DropShadowEffect BlurRadius="10" ShadowDepth="0" Color="Purple"/>
                                                </Border.Effect>
                                            </Border>
                                        </Grid>
    
                                        <Grid x:Name="PART_Ring3" Width="60" Height="60" HorizontalAlignment="Right" VerticalAlignment="Bottom" RenderTransformOrigin="0.5,0.5">
                                            <Grid.RenderTransform>
                                                <TransformGroup>
                                                    <ScaleTransform/>
                                                    <SkewTransform/>
                                                    <RotateTransform Angle="45"/>
                                                    <TranslateTransform/>
                                                </TransformGroup>
                                            </Grid.RenderTransform>
                                            <Ellipse Stroke="#0fb8b2" StrokeThickness="2" StrokeDashArray="23 100"/>
                                            <Border Width="10" Height="10" CornerRadius="10" Background="#0fb8b2" HorizontalAlignment="Right" Margin="0,0,-4,0">
                                                <Border.Effect>
                                                    <DropShadowEffect BlurRadius="10" ShadowDepth="0" Color="#0fb8b2"/>
                                                </Border.Effect>
                                            </Border>
                                        </Grid>
                                    </Grid>
                                </Border>
                            </Viewbox>
    
                            <StackPanel Grid.Row="1" Grid.ColumnSpan="2" Margin="10">
                                <TextBlock HorizontalAlignment="Center" Text="Loading..." Margin="0,0,0,15"/>
                                <TextBlock HorizontalAlignment="Center" Text="{TemplateBinding Description}" Margin="0,0,0,15"/>
                                <TextBlock HorizontalAlignment="Center" Text="{TemplateBinding Progress}" FontSize="{StaticResource TitleFontSize}" 
                                           FontWeight="Bold"/>
                            </StackPanel>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsStart" Value="True">
                                <Trigger.EnterActions>
                                    <BeginStoryboard Storyboard="{StaticResource PART_Resource_Storyboard}" x:Name="PART_BeginStoryboard"/>
                                </Trigger.EnterActions>
                                <Trigger.ExitActions>
                                    <StopStoryboard BeginStoryboardName="PART_BeginStoryboard"/>
                                </Trigger.ExitActions>
                            </Trigger>
    
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    

    3 )RingLoadingExample.xaml代码如下;

    <UserControl x:Class="WPFDevelopers.Samples.ExampleViews.RingLoadingExample"
                 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:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"
                 xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
        <Grid>
            <wpfdev:RingLoading IsStart="true" 
                                Width="400" Height="400"
                                Description="WPFDevelopers" Foreground="Black" ProgressValue="50"/>
        </Grid>
    </UserControl>
    

    RingLoading|Github
    RingLoading|码云
    RingLoading.xaml|Github
    RingLoading.xaml|码云

    34 条回复    2022-08-22 10:39:01 +08:00
    Terry05
        1
    Terry05  
       53 天前
    emmmmmm……恕我没看懂
    DrX
        2
    DrX  
       53 天前   ❤️ 1
    老板:这么丑?撤掉,换回上一版那个 GIF !
    runningowl
        3
    runningowl  
       53 天前
    Lottie 不香么
    loopinfor
        4
    loopinfor  
       53 天前
    这个 loading 效果过于另类了,应该不会有人真的用吧
    yanjinhua
        5
    yanjinhua  
    OP
       53 天前
    yanjinhua
        6
    yanjinhua  
    OP
       53 天前
    @loopinfor 哈哈哈
    yanjinhua
        7
    yanjinhua  
    OP
       53 天前
    @DrX 没用 gif
    yanjinhua
        8
    yanjinhua  
    OP
       53 天前
    @Terry05 呃呃呃呃呃
    wangyzj
        9
    wangyzj  
       53 天前
    不够卷
    SeanTheSheep
        10
    SeanTheSheep  
       53 天前
    逛 V 站一年了,第二次看到 WPF 相关的帖子,泪目了。
    yanjinhua
        11
    yanjinhua  
    OP
       53 天前
    @wangyzj 正在努力
    yanjinhua
        12
    yanjinhua  
    OP
       53 天前
    @SeanTheSheep 那么我很关心第一次的帖子 i😊
    20015jjw
        13
    20015jjw  
       53 天前 via iPhone
    amazon 可能会喜欢你(雾
    sinnosong1
        14
    sinnosong1  
       53 天前
    每行都能看懂,但是连起来就完全看不懂了,虽然我也写过 WPF ,一直都是入门状态的新手。。。
    yanjinhua
        15
    yanjinhua  
    OP
       53 天前
    @sinnosong1 哈哈哈,好像懂了 但是又没完全懂
    yanjinhua
        16
    yanjinhua  
    OP
       53 天前
    @20015jjw 我不懂了
    vone
        17
    vone  
       53 天前   ❤️ 3
    当场开除!
    dcsuibian
        18
    dcsuibian  
       53 天前
    现在 Electron 越来越多,对原生开发者也越来越敬重
    villivateur
        19
    villivateur  
       53 天前
    star 了,准备在我丑得要命的上位机里面试下这个主题
    stoluoyu
        20
    stoluoyu  
       53 天前
    精子有了,load 完不该进入卵子了么(大雾
    marcong95
        21
    marcong95  
       53 天前
    其实只留一组,甚至把那个东西的头部删掉其实就好多了,最好圆弧的长度还能随着时间变长变短。。。。
    v2byy
        22
    v2byy  
       53 天前
    就用 windows start 那个 loading 不香吗
    NGXDLK
        23
    NGXDLK  
       53 天前
    @marcong95 还可以变粗变细,变直变弯
    lifeintools
        24
    lifeintools  
       53 天前
    真好玩~
    xyx0826
        25
    xyx0826  
       52 天前 via iPhone
    很酷诶...WPF 我用了一段时间但是完全没搞懂它的高级样式和动画,现在 winui 3 也出了,不知道该学哪个了
    yanjinhua
        26
    yanjinhua  
    OP
       52 天前
    @vone 哈哈哈哈哈
    yanjinhua
        27
    yanjinhua  
    OP
       52 天前
    @dcsuibian 现在确实都是使用 electron 。谢谢
    yanjinhua
        28
    yanjinhua  
    OP
       52 天前
    @villivateur 欢迎使用哈,https://github.com/WPFDevelopersOrg 这个组织下都是关于 WPF 和 MAUI 的项目。有问题及时反馈哈。
    yanjinhua
        29
    yanjinhua  
    OP
       52 天前
    @stoluoyu 哈哈哈哈,后期应该会增加任务完成关闭。
    yanjinhua
        30
    yanjinhua  
    OP
       52 天前
    @NGXDLK 是这样的。
    yanjinhua
        31
    yanjinhua  
    OP
       52 天前
    @lifeintools 可以复制源码魔改起来。
    yanjinhua
        32
    yanjinhua  
    OP
       52 天前
    @xyx0826 maui 跨平台,win10 之前的系统。动画和高级样式多写几次就熟悉了。“https://github.com/WPFDevelopersOrg” 这个组织下 也有 maui 项目。
    ragnaroks
        33
    ragnaroks  
       45 天前
    WPF 设置样式( StyleSetter )和动画( StoryBorad )太累了,我已经放弃了,现在都用网页做 UI ,通过 websocket 链接到本地
    yanjinhua
        34
    yanjinhua  
    OP
       41 天前
    @ragnaroks 熟悉了就还好,也是不错的选择。👍
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1674 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 43ms · UTC 01:19 · PVG 09:19 · LAX 18:19 · JFK 21:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.