我试图创建一个可以在一棵树而TreeItem加载用于例如动态圆形进度条。
不幸的是,控制是不是真的运行平稳。
这是16×16,16×32 32×16和400%,大小为32×32。在GIF年初其运行,因为捕获工具的有点laggy多数民众赞成。正如你所看到的圆圈围绕有点wiggeling,这就是我想要删除的内容。
这是我的自定义控制:
代码隐藏:
/// <summary>
/// Interaction logic for CircularProgressBar.xaml
/// </summary>
public partial class CircularProgressBar
{
public static readonly DependencyProperty DeferedVisibilityProperty = DependencyProperty.Register(nameof(DeferedVisibility), typeof(bool),
typeof(CircularProgressBar), new PropertyMetadata
{
PropertyChangedCallback = OnDeferedVisibilityChanged,
DefaultValue = false
});
private readonly (Ellipse, int)[] _circlesWithOffset;
private Stopwatch _stopwatch;
public CircularProgressBar()
{
InitializeComponent();
DefaultStyleKey = typeof(CircularProgressBar);
_circlesWithOffset = new[] {(C0, 0), (C1, 1), (C2, 2), (C3, 3), (C4, 4), (C5, 5), (C6, 6), (C7, 7), (C8, 8)};
}
#region Animation
private void Start()
{
//Mouse.OverrideCursor = Cursors.Wait;
if(_stopwatch == null)
_stopwatch = new Stopwatch();
_stopwatch.Start();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void Stop()
{
//Mouse.OverrideCursor = Cursors.Arrow;
_stopwatch.Stop();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
_circlesWithOffset.ToList().ForEach(x => SetCircle(x.Item1, x.Item2));
}
private void SetCircle(Ellipse circle, int offset)
{
var posOnCircle = _stopwatch.Elapsed.TotalSeconds * Math.PI - Math.PI / 5 * offset;
var halfWidth = (Width - circle.Width) / 2;
var halfHeight = (Height - circle.Height) / 2;
circle.SetValue(Canvas.LeftProperty, halfWidth + Math.Sin(posOnCircle) * halfWidth);
circle.SetValue(Canvas.TopProperty, halfHeight + -Math.Cos(posOnCircle) * halfHeight);
}
private void HandleUnloaded(object sender, RoutedEventArgs e)
{
Stop();
}
private void HandleVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var isVisible = (bool)e.NewValue;
if(isVisible)
Start();
else
Stop();
}
#endregion Animation
#region Visibility
public bool DeferedVisibility
{
get => (bool)GetValue(DeferedVisibilityProperty);
set => SetValue(DeferedVisibilityProperty, value);
}
[Obsolete("Please use DeferedVisibility")]
public new Visibility Visibility
{
get => base.Visibility;
set => base.Visibility = value;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
OnDeferedVisibilityChanged();
}
private static void OnDeferedVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((CircularProgressBar)d).OnDeferedVisibilityChanged();
}
private void OnDeferedVisibilityChanged()
{
if(DeferedVisibility)
{
VisualStateManager.GoToState(this, "Visible", true);
#pragma warning disable 618
Visibility = Visibility.Visible;
#pragma warning restore 618
} else
{
VisualStateManager.GoToState(this, "Collapsed", true);
#pragma warning disable 618
Visibility = Visibility.Collapsed;
#pragma warning restore 618
}
}
#endregion Visibility
}
XAML:
<UserControl x:Class="MyProject.Views.Controls.Util.CircularProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:util="clr-namespace:MyProject.Views.Controls.Util"
Background="Transparent"
IsVisibleChanged="HandleVisibleChanged">
<UserControl.Resources>
<util:PercentageValueConverter x:Key="PercentageValueConverter"
Scaling="0.2" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot"
Background="Transparent"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Canvas RenderTransformOrigin="0.5, 0.5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Unloaded="HandleUnloaded">
<Ellipse x:Name="C0"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.9" />
<Ellipse x:Name="C1"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.8" />
<Ellipse x:Name="C2"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.7" />
<Ellipse x:Name="C3"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.6" />
<Ellipse x:Name="C4"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.5" />
<Ellipse x:Name="C5"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.4" />
<Ellipse x:Name="C6"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.3" />
<Ellipse x:Name="C7"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.2" />
<Ellipse x:Name="C8"
SnapsToDevicePixels="False"
Width="{Binding Width,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="{Binding Height,
Mode=OneWay,
Converter={StaticResource PercentageValueConverter},
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Stretch="Fill"
Fill="Black"
Opacity="0.1" />
</Canvas>
</Grid>
</UserControl>
该转换器是只那里到圆的直径设定为控制的大小的20%。
这是如何在任何地方使用的控制
<util:CircularProgressBar Grid.Row="1"
DeferedVisibility="True"
Width="32"
Height="32" />
正如你所看到的圆圈位置获取与CompositionTarget.Rendering事件更新。
我已经尝试设置SnapsToDevicePixels为false,但这并没有改变任何东西。对于位置计算有使用双所以不应该有任何舍入误差。
这个答案可能与当前的代码解决您的问题,是相当的建议。该XAML
有一些巧妙的功能和使用得当,可以实现很多毫不费力。
例如:可以定义一个Style
为其设置一个UIElement
的位置,优选与初始旋转一个中StartPosition一个Ellipse
。然后用这个Style
元素添加到某种集装箱和旋转整个容器,使它看起来像一个Ellipse
s在移动。理想的情况下使用它处理Ellipse
的缩放的容器。
风格LoadingCircles
<!-- LoadingCircles Style for a Control-Element-->
<Style TargetType="{x:Type Control}" x:Key="LoadingCircles">
<!-- Set default values (can be overridden) -->
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Tag" Value="20"/>
<!-- Hide Control when its not enabled -->
<Setter Property="Visibility" Value="Collapsed"/>
<!-- Define the lok of the Control -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- Use ViewBox for auto-scaling -->
<Viewbox Stretch="Fill">
<!-- Set Grid Size to absolute value to scale on 100% (like Circle Size = 20 -> 20%) -->
<Grid Height="100" Width="100" RenderTransformOrigin="0.5,0.5">
<Grid.Resources>
<!-- Define Template for Circle on a circular path whereas the Tag defines the initial Rotation (0 = top, 180 = bottom) -->
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="DataContext" Value="{Binding}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border Height="100">
<Border.LayoutTransform>
<RotateTransform Angle="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"/>
</Border.LayoutTransform>
<Ellipse Width="{Binding Tag, RelativeSource={RelativeSource AncestorType=Control}}" Height="{Binding Tag, RelativeSource={RelativeSource AncestorType=Control}}" Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Control}}" VerticalAlignment="Top"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<!-- Add Circles to the circular Path with their start-roation and opacity -->
<ContentPresenter Opacity="0.1" Tag="36"/>
<ContentPresenter Opacity="0.2" Tag="72"/>
<ContentPresenter Opacity="0.3" Tag="108"/>
<ContentPresenter Opacity="0.4" Tag="144"/>
<ContentPresenter Opacity="0.5" Tag="180"/>
<ContentPresenter Opacity="0.6" Tag="216"/>
<ContentPresenter Opacity="0.7" Tag="252"/>
<ContentPresenter Opacity="0.8" Tag="288"/>
<ContentPresenter Opacity="0.9" Tag="324"/>
<ContentPresenter Opacity="1" Tag="0"/>
<!-- Define Roation for all the Circles in the "Container" -->
<Grid.RenderTransform>
<RotateTransform Angle="0" x:Name="AngleEverything"/>
</Grid.RenderTransform>
</Grid>
</Viewbox>
<!-- Define Trigger when the Control is enabled -->
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="True">
<!-- When set, start the "Container" rotation -->
<Trigger.EnterActions>
<BeginStoryboard x:Name="Rotation">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Storyboard.TargetName="AngleEverything" Storyboard.TargetProperty="Angle" From="0" To="359" Duration="00:00:03"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<!-- when unset, stop the "Container" rotation -->
<Trigger.ExitActions>
<StopStoryboard Storyboard.TargetName="Rotation"/>
</Trigger.ExitActions>
<!-- Show control when it is enabled (otherwise hide, see Setter at the top) -->
<Setter Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
笔记
移动圈的Size
设置与Tag
Property
为百分比(20对应于20%)。
圆的Color
设置与Foreground
Property
。
控制的Visibility
与IsEnabled
Property
处理。当设置为False
的LoadingCircles被隐藏。
用法示例
<!-- LoadingCircles with 15% size and purple color -->
<Control Style="{DynamicResource LoadingCircles}" IsEnabled="{Binding YourSource}" Foreground="Purple" Tag="15" ... />
个人说明
正如你所看到的,它运行流畅而无需CustomControl
or的任何代码隐藏(这是一个有点自定义)。我强烈建议你采取的XAML
功能细看得到有效的结果。