如何实现圆进度条(WPF)?

我需要一些非常简单的东西,例如只是显示加载过程而没有进度。就像用户单击按钮并启动流程一样,这次需要5秒钟,我想向用户展示这样的进度

enter image description here

I tried to find how to implement this, but actually what I found is people build a complete libraries or heavy xaml animation implementation. Actually I thought it should be something simple out of the box option like put in xaml Progress bar and in cs file when you need call myProgressBar.Show() or myProgressBar.Hide().

就这样,我不需要在xaml中实现库或200百行。

如何使这个简单的实现?

评论
  • dsit
    dsit 回复

    没有本机WPF控件可以产生这种显示,因此您将需要一些代码(在库中),可以根据需要使用它们。代码的数量取决于控件所需的灵活性。

    这是我的版本-旨在为显示提供一些选项,但确保最小化系统资源的使用,并在不再使用时停止任何处理。

    <UserControl
        x:Class="Peregrine.WPF.View.Controls.perBusySpinner"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ctrl="clr-namespace:Peregrine.WPF.View.Controls"
        Width="{Binding RelativeSource={RelativeSource Self}, Path=Height, Mode=TwoWay}"
        Height="120">
    
        <Viewbox
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch">
            <Grid Background="{Binding Path=Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctrl:perBusySpinner}}}">
                <Canvas
                    Width="{Binding Path=Diameter, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctrl:perBusySpinner}}}"
                    Height="{Binding Path=Diameter, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctrl:perBusySpinner}}}"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Background="Transparent"
                    RenderTransformOrigin="0.5,0.5"
                    UseLayoutRounding="False">
    
                    <Canvas.Resources>
                        <Style TargetType="Ellipse">
                            <Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctrl:perBusySpinner}}}" />
                            <Setter Property="Height" Value="{Binding Path=ItemDiameter, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctrl:perBusySpinner}}}" />
                            <Setter Property="Stretch" Value="Fill" />
                            <Setter Property="StrokeThickness" Value="0" />
                            <Setter Property="Width" Value="{Binding Path=ItemDiameter, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ctrl:perBusySpinner}}}" />
                        </Style>
                    </Canvas.Resources>
    
                    <Ellipse
                        x:Name="Item1"
                        Opacity="1.0" />
                    <Ellipse
                        x:Name="Item2"
                        Opacity="0.92" />
                    <Ellipse
                        x:Name="Item3"
                        Opacity="0.84" />
                    <Ellipse
                        x:Name="Item4"
                        Opacity="0.76" />
                    <Ellipse
                        x:Name="Item5"
                        Opacity="0.68" />
                    <Ellipse
                        x:Name="Item6"
                        Opacity="0.60" />
                    <Ellipse
                        x:Name="Item7"
                        Opacity="0.52" />
                    <Ellipse
                        x:Name="Item8"
                        Opacity="0.44" />
                    <Ellipse
                        x:Name="Item9"
                        Opacity="0.36" />
                    <Ellipse
                        x:Name="Item10"
                        Opacity="0.28" />
                    <Ellipse
                        x:Name="Item11"
                        Opacity="0.20" />
                    <Ellipse
                        x:Name="Item12"
                        Opacity="0.12" />
    
                    <Canvas.RenderTransform>
                        <RotateTransform x:Name="SpinnerRotateTransform" Angle="0" />
                    </Canvas.RenderTransform>
                </Canvas>
            </Grid>
        </Viewbox>
    
    </UserControl>
    

    public partial class perBusySpinner
    {
        private readonly DispatcherTimer _spinnerTimer;
    
        // the nominal size of the spinner - the actual size is determined by the Width / Height as the spinner is contained within a ViewBox
        public double Diameter => 100.0; 
        public double ItemDiameter => Diameter / 6.0;
        public double ItemRadius => ItemDiameter / 2.0;
        public double ItemPositionRadius => (Diameter - ItemDiameter) / 2.0;
    
        public perBusySpinner()
        {
            InitializeComponent();
    
            _spinnerTimer = new DispatcherTimer(DispatcherPriority.Normal, Dispatcher);
            _spinnerTimer.Tick += (s, e) => SpinnerRotateTransform.Angle = (SpinnerRotateTransform.Angle + 30) % 360;
    
            Loaded += (s, e) => OnLoaded();
            Unloaded += (s, e) => Stop();
            IsVisibleChanged += (s, e) => OnIsVisibleChanged((bool)e.NewValue);
        }   
    
        /// <summary>
        /// IsVisibleChanged also covers the case where the spinner is placed inside another control which itself is collapsed or hidden
        /// </summary>
        /// <param name="isVisible">
        /// </param>
        private void OnIsVisibleChanged(bool isVisible)
        {
            // disable spinning in the Visual Studio designer
            if (perViewModelHelper.IsInDesignMode)
                return;
    
            if (isVisible)
                Start();
            else
                Stop();
        }
    
        /// <summary>
        /// Rotations per minute
        /// </summary>
        public int Speed
        {
            get => (int)GetValue(SpeedProperty);
            set => SetValue(SpeedProperty, value);
        }
    
        public static readonly DependencyProperty SpeedProperty =
            DependencyProperty.Register("Speed", typeof(int), typeof(perBusySpinner), new PropertyMetadata(60));
    
        private void OnLoaded()
        {
            SetItemPosition(Item1, 0);
            SetItemPosition(Item2, 1);
            SetItemPosition(Item3, 2);
            SetItemPosition(Item4, 3);
            SetItemPosition(Item5, 4);
            SetItemPosition(Item6, 5);
            SetItemPosition(Item7, 6);
            SetItemPosition(Item8, 7);
            SetItemPosition(Item9, 8);
            SetItemPosition(Item10, 9);
            SetItemPosition(Item11, 10);
            SetItemPosition(Item12, 11);
        }
    
        private void SetItemPosition(DependencyObject item, int index)
        {
            item.SetValue(Canvas.LeftProperty, Diameter / 2.0 + (Math.Sin(Math.PI * (index / 6.0)) * ItemPositionRadius) - ItemRadius);
            item.SetValue(Canvas.TopProperty, Diameter / 2.0 + (Math.Cos(Math.PI * (index / 6.0)) * ItemPositionRadius) - ItemRadius);
        }
    
        private void Stop()
        {
            _spinnerTimer.Stop();
        }
    
        private void Start()
        {
            // each tick of the timer is 1 step of revolution
            _spinnerTimer.Interval = TimeSpan.FromMilliseconds(60000 / (12.0 * Speed));
            _spinnerTimer.Start();
        }
    }
    

    在库中定义后,使用此类控件仅需要最少的xaml。

    <vctrl:perBusySpinner
        Width="32"
        Background="Transparent"
        Foreground="Blue" />
    

    To show and activate the spinner, just set it's Visibilty property to Visible or bind to a boolean property in the ViewModel via a BooleanToVisibility converter.

    More detail at my blog post.