超炫酷的WPF实现Loading控件效果
用WPF自定义一个Loading控件实现类似的效果,并可以让用户对Loading的颗粒(Particle)背景颜色进行自定义,话不多说,直接上代码:
1、用VS2012新建一个WPF的用户控件库项目WpfControlLibraryDemo,VS自动生成如下结构:
2、删除UserControl1.xaml,并新建一个Loading的CustomControl(不是UserControl),如下图所示:
3、如果报错找不到Loading类型,请编译,下面在Generic.xaml主题文件中对Loading的样式和内容进行定义(注意添加 xmlns:system = "clr-namespace:System;assembly=mscorlib"),代码如下:
1 <ResourceDictionary 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:system = "clr-namespace:System;assembly=mscorlib" 5 xmlns:local="clr-namespace:WpfControlLibraryDemo"> 6 7 8 <Style TargetType="{x:Type local:Loading}"> 9 <Setter Property="Template"> 10 <Setter.Value> 11 <ControlTemplate TargetType="{x:Type local:Loading}"> 12 <Border Background="{TemplateBinding Background}" 13 BorderBrush="{TemplateBinding BorderBrush}" 14 BorderThickness="{TemplateBinding BorderThickness}"> 15 <Grid Width = "50" Height = "50"> 16 <Grid.Resources> 17 <!-- Value Converters --> 18 19 <!-- Particle Styling ,must to has RelativeSource --> 20 <SolidColorBrush x:Key = "ParticleColor" Color = "{Binding Path=FillColor,RelativeSource={RelativeSource TemplatedParent}}" /> 21 <SolidColorBrush x:Key = "ParticleBackgroundColor" Color = "Transparent"/> 22 <system:Double x:Key = "ParticleOpacity">1</system:Double> 23 <system:Double x:Key = "ParticleRadius">5</system:Double> 24 25 <system:Double x:Key = "StartingPointX">0</system:Double> 26 <system:Double x:Key = "StartingPointY">-20</system:Double> 27 28 <system:Double x:Key = "RotationPointX">0.5</system:Double> 29 <system:Double x:Key = "RotationPointY">0.5</system:Double> 30 31 <!-- StoryBoard --> 32 <system:TimeSpan x:Key = "StoryBoardBeginTimeP0">00:00:00.000</system:TimeSpan> 33 <system:TimeSpan x:Key = "StoryBoardBeginTimeP1">00:00:00.100</system:TimeSpan> 34 <system:TimeSpan x:Key = "StoryBoardBeginTimeP2">00:00:00.200</system:TimeSpan> 35 <system:TimeSpan x:Key = "StoryBoardBeginTimeP3">00:00:00.300</system:TimeSpan> 36 <system:TimeSpan x:Key = "StoryBoardBeginTimeP4">00:00:00.400</system:TimeSpan> 37 <Duration x:Key = "StoryBoardDuration">00:00:01.800</Duration> 38 39 <!-- Particle Origin Angles --> 40 <system:Double x:Key = "ParticleOriginAngleP0">0</system:Double> 41 <system:Double x:Key = "ParticleOriginAngleP1">-10</system:Double> 42 <system:Double x:Key = "ParticleOriginAngleP2">-20</system:Double> 43 <system:Double x:Key = "ParticleOriginAngleP3">-30</system:Double> 44 <system:Double x:Key = "ParticleOriginAngleP4">-40</system:Double> 45 46 <!-- Particle Position & Timing 1 --> 47 <system:Double x:Key = "ParticleBeginAngle1">0</system:Double> 48 <system:Double x:Key = "ParticleEndAngle1">90</system:Double> 49 <system:TimeSpan x:Key = "ParticleBeginTime1">00:00:00.000</system:TimeSpan> 50 <Duration x:Key = "ParticleDuration1">00:00:00.750</Duration> 51 52 <!-- Particle Position & Timing 2 --> 53 <system:Double x:Key = "ParticleBeginAngle2">90</system:Double> 54 <system:Double x:Key = "ParticleEndAngle2">270</system:Double> 55 <system:TimeSpan x:Key = "ParticleBeginTime2">00:00:00.751</system:TimeSpan> 56 <Duration x:Key = "ParticleDuration2">00:00:00.300</Duration> 57 58 <!-- Particle Position & Timing 3 --> 59 <system:Double x:Key = "ParticleBeginAngle3">270</system:Double> 60 <system:Double x:Key = "ParticleEndAngle3">360</system:Double> 61 <system:TimeSpan x:Key = "ParticleBeginTime3">00:00:01.052</system:TimeSpan> 62 <Duration x:Key = "ParticleDuration3">00:00:00.750</Duration> 63 64 <Style x:Key = "EllipseStyle" TargetType = "Ellipse"> 65 <Setter Property = "Width" Value = "{StaticResource ParticleRadius}"/> 66 <Setter Property = "Height" Value = "{StaticResource ParticleRadius}"/> 67 <Setter Property = "Fill" Value = "{StaticResource ParticleColor}"/> 68 <Setter Property = "RenderTransformOrigin" Value = "0.5, 0.5"/> 69 <Setter Property = "Opacity" Value = "{StaticResource ParticleOpacity}"/> 70 </Style> 71 </Grid.Resources> 72 <Canvas Width = "1" Height = "1" Margin="0,0,0,0"> 73 <Canvas.Triggers> 74 <EventTrigger RoutedEvent = "Canvas.Loaded"> 75 <EventTrigger.Actions> 76 <BeginStoryboard> 77 <Storyboard 78 79 BeginTime = "{StaticResource StoryBoardBeginTimeP0}" 80 Duration = "{StaticResource StoryBoardDuration}" 81 RepeatBehavior = "Forever"> 82 <DoubleAnimation 83 Storyboard.TargetName = "p0" 84 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 85 From = "{StaticResource ParticleBeginAngle1}" 86 To = "{StaticResource ParticleEndAngle1}" 87 BeginTime = "{StaticResource ParticleBeginTime1}" 88 Duration = "{StaticResource ParticleDuration1}"/> 89 <DoubleAnimation 90 Storyboard.TargetName = "p0" 91 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 92 From = "{StaticResource ParticleBeginAngle2}" 93 To = "{StaticResource ParticleEndAngle2}" 94 BeginTime = "{StaticResource ParticleBeginTime2}" 95 Duration = "{StaticResource ParticleDuration2}"/> 96 <DoubleAnimation 97 Storyboard.TargetName = "p0" 98 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 99 From = "{StaticResource ParticleBeginAngle3}" 100 To = "{StaticResource ParticleEndAngle3}" 101 BeginTime = "{StaticResource ParticleBeginTime3}" 102 Duration = "{StaticResource ParticleDuration3}"/> 103 </Storyboard> 104 </BeginStoryboard> 105 <BeginStoryboard> 106 <Storyboard 107 108 BeginTime = "{StaticResource StoryBoardBeginTimeP1}" 109 Duration = "{StaticResource StoryBoardDuration}" 110 RepeatBehavior = "Forever"> 111 112 <DoubleAnimation 113 Storyboard.TargetName = "p1" 114 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 115 From = "{StaticResource ParticleBeginAngle1}" 116 To = "{StaticResource ParticleEndAngle1}" 117 BeginTime = "{StaticResource ParticleBeginTime1}" 118 Duration = "{StaticResource ParticleDuration1}"/> 119 <DoubleAnimation 120 Storyboard.TargetName = "p1" 121 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 122 From = "{StaticResource ParticleBeginAngle2}" 123 To = "{StaticResource ParticleEndAngle2}" 124 BeginTime = "{StaticResource ParticleBeginTime2}" 125 Duration = "{StaticResource ParticleDuration2}"/> 126 <DoubleAnimation 127 Storyboard.TargetName = "p1" 128 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 129 From = "{StaticResource ParticleBeginAngle3}" 130 To = "{StaticResource ParticleEndAngle3}" 131 BeginTime = "{StaticResource ParticleBeginTime3}" 132 Duration = "{StaticResource ParticleDuration3}"/> 133 </Storyboard> 134 </BeginStoryboard> 135 <BeginStoryboard> 136 <Storyboard 137 138 BeginTime = "{StaticResource StoryBoardBeginTimeP2}" 139 Duration = "{StaticResource StoryBoardDuration}" 140 RepeatBehavior = "Forever"> 141 142 <DoubleAnimation 143 Storyboard.TargetName = "p2" 144 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 145 From = "{StaticResource ParticleBeginAngle1}" 146 To = "{StaticResource ParticleEndAngle1}" 147 BeginTime = "{StaticResource ParticleBeginTime1}" 148 Duration = "{StaticResource ParticleDuration1}"/> 149 <DoubleAnimation 150 Storyboard.TargetName = "p2" 151 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 152 From = "{StaticResource ParticleBeginAngle2}" 153 To = "{StaticResource ParticleEndAngle2}" 154 BeginTime = "{StaticResource ParticleBeginTime2}" 155 Duration = "{StaticResource ParticleDuration2}"/> 156 <DoubleAnimation 157 Storyboard.TargetName = "p2" 158 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 159 From = "{StaticResource ParticleBeginAngle3}" 160 To = "{StaticResource ParticleEndAngle3}" 161 BeginTime = "{StaticResource ParticleBeginTime3}" 162 Duration = "{StaticResource ParticleDuration3}"/> 163 </Storyboard> 164 </BeginStoryboard> 165 166 <BeginStoryboard> 167 <Storyboard 168 169 BeginTime = "{StaticResource StoryBoardBeginTimeP3}" 170 Duration = "{StaticResource StoryBoardDuration}" 171 RepeatBehavior = "Forever"> 172 173 <DoubleAnimation 174 Storyboard.TargetName = "p3" 175 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 176 From = "{StaticResource ParticleBeginAngle1}" 177 To = "{StaticResource ParticleEndAngle1}" 178 BeginTime = "{StaticResource ParticleBeginTime1}" 179 Duration = "{StaticResource ParticleDuration1}"/> 180 <DoubleAnimation 181 Storyboard.TargetName = "p3" 182 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 183 From = "{StaticResource ParticleBeginAngle2}" 184 To = "{StaticResource ParticleEndAngle2}" 185 BeginTime = "{StaticResource ParticleBeginTime2}" 186 Duration = "{StaticResource ParticleDuration2}"/> 187 <DoubleAnimation 188 Storyboard.TargetName = "p3" 189 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 190 From = "{StaticResource ParticleBeginAngle3}" 191 To = "{StaticResource ParticleEndAngle3}" 192 BeginTime = "{StaticResource ParticleBeginTime3}" 193 Duration = "{StaticResource ParticleDuration3}"/> 194 </Storyboard> 195 </BeginStoryboard> 196 197 <BeginStoryboard> 198 <Storyboard 199 200 BeginTime = "{StaticResource StoryBoardBeginTimeP4}" 201 Duration = "{StaticResource StoryBoardDuration}" 202 RepeatBehavior = "Forever"> 203 204 <DoubleAnimation 205 Storyboard.TargetName = "p4" 206 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 207 From = "{StaticResource ParticleBeginAngle1}" 208 To = "{StaticResource ParticleEndAngle1}" 209 BeginTime = "{StaticResource ParticleBeginTime1}" 210 Duration = "{StaticResource ParticleDuration1}"/> 211 <DoubleAnimation 212 Storyboard.TargetName = "p4" 213 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 214 From = "{StaticResource ParticleBeginAngle2}" 215 To = "{StaticResource ParticleEndAngle2}" 216 BeginTime = "{StaticResource ParticleBeginTime2}" 217 Duration = "{StaticResource ParticleDuration2}"/> 218 <DoubleAnimation 219 Storyboard.TargetName = "p4" 220 Storyboard.TargetProperty = "(UIElement.RenderTransform).(RotateTransform.Angle)" 221 From = "{StaticResource ParticleBeginAngle3}" 222 To = "{StaticResource ParticleEndAngle3}" 223 BeginTime = "{StaticResource ParticleBeginTime3}" 224 Duration = "{StaticResource ParticleDuration3}"/> 225 </Storyboard> 226 </BeginStoryboard> 227 </EventTrigger.Actions> 228 </EventTrigger> 229 </Canvas.Triggers> 230 <Border 231 x:Name = "p0" 232 Background = "{StaticResource ParticleBackgroundColor}" 233 Opacity = "{StaticResource ParticleOpacity}"> 234 <Border.RenderTransform> 235 <RotateTransform/> 236 </Border.RenderTransform> 237 <Border.RenderTransformOrigin> 238 <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/> 239 </Border.RenderTransformOrigin> 240 <Ellipse Style = "{StaticResource EllipseStyle}"> 241 <Ellipse.RenderTransform> 242 <TransformGroup> 243 <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/> 244 <RotateTransform Angle = "{StaticResource ParticleOriginAngleP0}"/> 245 </TransformGroup> 246 </Ellipse.RenderTransform> 247 </Ellipse> 248 </Border> 249 <Border 250 x:Name = "p1" 251 Background = "{StaticResource ParticleBackgroundColor}" 252 Opacity = "{StaticResource ParticleOpacity}"> 253 <Border.RenderTransform> 254 <RotateTransform/> 255 </Border.RenderTransform> 256 <Border.RenderTransformOrigin> 257 <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/> 258 </Border.RenderTransformOrigin> 259 <Ellipse Style = "{StaticResource EllipseStyle}"> 260 <Ellipse.RenderTransform> 261 <TransformGroup> 262 <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/> 263 <RotateTransform Angle = "{StaticResource ParticleOriginAngleP1}"/> 264 </TransformGroup> 265 </Ellipse.RenderTransform> 266 </Ellipse> 267 </Border> 268 <Border 269 x:Name = "p2" 270 Background = "{StaticResource ParticleBackgroundColor}" 271 Opacity = "{StaticResource ParticleOpacity}"> 272 <Border.RenderTransform> 273 <RotateTransform/> 274 </Border.RenderTransform> 275 <Border.RenderTransformOrigin> 276 <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/> 277 </Border.RenderTransformOrigin> 278 <Ellipse Style = "{StaticResource EllipseStyle}"> 279 <Ellipse.RenderTransform> 280 <TransformGroup> 281 <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/> 282 <RotateTransform Angle = "{StaticResource ParticleOriginAngleP2}"/> 283 </TransformGroup> 284 </Ellipse.RenderTransform> 285 </Ellipse> 286 </Border> 287 <Border 288 x:Name = "p3" 289 Background = "{StaticResource ParticleBackgroundColor}" 290 Opacity = "{StaticResource ParticleOpacity}"> 291 <Border.RenderTransform> 292 <RotateTransform/> 293 </Border.RenderTransform> 294 <Border.RenderTransformOrigin> 295 <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/> 296 </Border.RenderTransformOrigin> 297 <Ellipse Style = "{StaticResource EllipseStyle}"> 298 <Ellipse.RenderTransform> 299 <TransformGroup> 300 <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/> 301 <RotateTransform Angle = "{StaticResource ParticleOriginAngleP3}"/> 302 </TransformGroup> 303 </Ellipse.RenderTransform> 304 </Ellipse> 305 </Border> 306 <Border 307 x:Name = "p4" 308 Background = "{StaticResource ParticleBackgroundColor}" 309 Opacity = "{StaticResource ParticleOpacity}"> 310 <Border.RenderTransform> 311 <RotateTransform/> 312 </Border.RenderTransform> 313 <Border.RenderTransformOrigin> 314 <Point X = "{StaticResource RotationPointX}" Y = "{StaticResource RotationPointY}"/> 315 </Border.RenderTransformOrigin> 316 <Ellipse Style = "{StaticResource EllipseStyle}"> 317 <Ellipse.RenderTransform> 318 <TransformGroup> 319 <TranslateTransform X = "{StaticResource StartingPointX}" Y = "{StaticResource StartingPointY}"/> 320 <RotateTransform Angle = "{StaticResource ParticleOriginAngleP4}"/> 321 </TransformGroup> 322 </Ellipse.RenderTransform> 323 </Ellipse> 324 </Border> 325 </Canvas> 326 </Grid> 327 328 329 330 </Border> 331 </ControlTemplate> 332 </Setter.Value> 333 </Setter> 334 </Style> 335 336 337 338 </ResourceDictionary>
在构建中发现,一开始在设定绑定时,写成<SolidColorBrush x:Key = "ParticleColor" Color = "{Binding Path=FillColor}" />一直都无法绑定成功,后来查了资料,改成<SolidColorBrush x:Key = "ParticleColor" Color = "{Binding Path=FillColor,RelativeSource={RelativeSource TemplatedParent}}" /> 后成功。
4、编辑Loading.cs文件,对自定义属性FillColor和逻辑进行编码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Windows; 7 using System.Windows.Controls; 8 using System.Windows.Data; 9 using System.Windows.Documents; 10 using System.Windows.Input; 11 using System.Windows.Media; 12 using System.Windows.Media.Imaging; 13 using System.Windows.Navigation; 14 using System.Windows.Shapes; 15 16 namespace WpfControlLibraryDemo 17 { 18 using System.ComponentModel; 19 /// <summary> 20 /// 按照步骤 1a 或 1b 操作,然后执行步骤 2 以在 XAML 文件中使用此自定义控件。 21 /// 22 /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。 23 /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根 24 /// 元素中: 25 /// 26 /// xmlns:MyNamespace="clr-namespace:WpfControlLibraryDemo" 27 /// 28 /// 29 /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。 30 /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根 31 /// 元素中: 32 /// 33 /// xmlns:MyNamespace="clr-namespace:WpfControlLibraryDemo;assembly=WpfControlLibraryDemo" 34 /// 35 /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用, 36 /// 并重新生成以避免编译错误: 37 /// 38 /// 在解决方案资源管理器中右击目标项目,然后依次单击 39 /// “添加引用”->“项目”->[浏览查找并选择此项目] 40 /// 41 /// 42 /// 步骤 2) 43 /// 继续操作并在 XAML 文件中使用控件。 44 /// 45 /// <MyNamespace:Loading/> 46 /// 47 /// </summary> 48 public class Loading : Control 49 { 50 static Loading() 51 { 52 //重载默认样式 53 DefaultStyleKeyProperty.OverrideMetadata(typeof(Loading), new FrameworkPropertyMetadata(typeof(Loading))); 54 //DependencyProperty 注册 FillColor 55 FillColorProperty = DependencyProperty.Register("FillColor", 56 typeof(Color), 57 typeof(Loading), 58 new UIPropertyMetadata(Colors.DarkBlue, 59 new PropertyChangedCallback(OnUriChanged)) 60 ); 61 //Colors.DarkBlue为控件初始化默认值 62 63 } 64 //属性变更回调函数 65 private static void OnUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 66 { 67 //Border b = (Border)d; 68 //MessageBox.Show(e.NewValue.ToString()); 69 70 } 71 #region 自定义Fields 72 // DependencyProperty属性定义 FillColorProperty=FillColor+Property组成 73 public static readonly DependencyProperty FillColorProperty; 74 #endregion 75 //VS设计器属性支持 76 [Description("背景色"), Category("个性配置"), DefaultValue("#FF668899")] 77 public Color FillColor 78 { 79 //GetValue,SetValue为固定写法,此处一般不建议处理其他逻辑 80 get { return (Color)GetValue(FillColorProperty); } 81 set { SetValue(FillColorProperty, value); } 82 } 83 } 84 }
5、编译,如果无误后,可以添加WPF应用程序WpfAppLoadingTest进行测试(添加项目引用)。
打开MainWindow.xaml,将Loading控件拖放到设计界面上,如下图所示:
6、控件颜色修改,选中控件,在属性栏中进行配置即可:
7.总结
可以看到WPF自定义控件还是比较容易的,但是难点在于UI的设计,如果需要做的美观,需要美工的参与,而且需要转换成XAML。