背景:WPF项目中,经常会处理一个或者多个耗时很久的任务,比如调用服务的数据查询然后把N条数据加载到列表控件。这种情况下如果采用一般的方式同步处理那么WPF的UI就会失去响应,卡死在那个地方,整个系统可能都无法操作,这对用户来说简直就是太不友好了,也得傻傻的等待任务完成才能干其他事件......
这个问题的解决方法都是采用多线程来处理,一般是开起一个后台线程去完成这些任务,这样UI线程仍然可以响应用户的其它操作,等待后台把任务处理完毕了在通知UI、通知用户。这样不仅提高了效率、也让系统的体验更好。
WPF的WPFToolKit、WPFToolKitExtended里面给我提供了一个BusyIndicator的控件(具体用法这里不讲了,有兴趣的朋友自己去查吧)。使用它可以很好的提示用户系统正在处理的任务和进度等。。。但是它的样式外观个人觉得不大好,还是比较喜欢那种转圈的。于是自己模仿它重新写了一个控件,用法和BusyIndicator一样,在这里分享一下,希望对大家有用。下面是效果图:
下面贴出控件后台代码及其实现动画的代码仅供参考:
后台代码及动画实现
1 using System; 2 using System.Windows; 3 using System.Windows.Controls; 4 using System.Windows.Shapes; 5 using System.Windows.Threading; 6 using System.Windows.Media; 7 using System.Windows.Media.Animation; 8 9 namespace BusyIndicator 10 { 11 /// <summary> 12 /// A control to provide a visual indicator when an application is busy. 13 /// </summary> 14 [TemplateVisualState(Name = VisualStates.StateIdle, GroupName = VisualStates.GroupBusyStatus)] 15 [TemplateVisualState(Name = VisualStates.StateBusy, GroupName = VisualStates.GroupBusyStatus)] 16 [TemplateVisualState(Name = VisualStates.StateVisible, GroupName = VisualStates.GroupVisibility)] 17 [TemplateVisualState(Name = VisualStates.StateHidden, GroupName = VisualStates.GroupVisibility)] 18 [StyleTypedProperty(Property = "OverlayStyle", StyleTargetType = typeof(Rectangle))] 19 [StyleTypedProperty(Property = "ProgressBarStyle", StyleTargetType = typeof(ProgressBar))] 20 public class BusyIndicator : ContentControl 21 { 22 /// <summary> 23 /// Gets or sets a value indicating whether the BusyContent is visible. 24 /// </summary> 25 protected bool IsContentVisible { get; set; } 26 27 /// <summary> 28 /// Timer used to delay the initial display and avoid flickering. 29 /// </summary> 30 private DispatcherTimer _displayAfterTimer; 31 32 /// <summary> 33 /// Instantiates a new instance of the BusyIndicator control. 34 /// </summary> 35 public BusyIndicator() 36 { 37 DefaultStyleKey = typeof(BusyIndicator); 38 _displayAfterTimer = new DispatcherTimer(); 39 _displayAfterTimer.Tick += new EventHandler(DisplayAfterTimerElapsed); 40 this.BusyContent = InitBusyContent(); 41 } 42 43 /// <summary> 44 /// Overrides the OnApplyTemplate method. 45 /// </summary> 46 public override void OnApplyTemplate() 47 { 48 base.OnApplyTemplate(); 49 ChangeVisualState(false); 50 } 51 52 /// <summary> 53 /// Handler for the DisplayAfterTimer. 54 /// </summary> 55 /// <param name="sender">Event sender.</param> 56 /// <param name="e">Event arguments.</param> 57 private void DisplayAfterTimerElapsed(object sender, EventArgs e) 58 { 59 _displayAfterTimer.Stop(); 60 IsContentVisible = true; 61 ChangeVisualState(true); 62 } 63 64 /// <summary> 65 /// Changes the control's visual state(s). 66 /// </summary> 67 /// <param name="useTransitions">True if state transitions should be used.</param> 68 protected virtual void ChangeVisualState(bool useTransitions) 69 { 70 VisualStateManager.GoToState(this, IsBusy ? VisualStates.StateBusy : VisualStates.StateIdle, useTransitions); 71 VisualStateManager.GoToState(this, IsContentVisible ? VisualStates.StateVisible : VisualStates.StateHidden, useTransitions); 72 } 73 74 /// <summary> 75 /// Gets or sets a value indicating whether the busy indicator should show. 76 /// </summary> 77 public bool IsBusy 78 { 79 get { return (bool)GetValue(IsBusyProperty); } 80 set { SetValue(IsBusyProperty, value); } 81 } 82 83 /// <summary> 84 /// Identifies the IsBusy dependency property. 85 /// </summary> 86 public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register( 87 "IsBusy", 88 typeof(bool), 89 typeof(BusyIndicator), 90 new PropertyMetadata(false, new PropertyChangedCallback(OnIsBusyChanged))); 91 92 /// <summary> 93 /// IsBusyProperty property changed handler. 94 /// </summary> 95 /// <param name="d">BusyIndicator that changed its IsBusy.</param> 96 /// <param name="e">Event arguments.</param> 97 private static void OnIsBusyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 98 { 99 ((BusyIndicator)d).OnIsBusyChanged(e); 100 } 101 102 /// <summary> 103 /// IsBusyProperty property changed handler. 104 /// </summary> 105 /// <param name="e">Event arguments.</param> 106 protected virtual void OnIsBusyChanged(DependencyPropertyChangedEventArgs e) 107 { 108 if (IsBusy) 109 { 110 if (DisplayAfter.Equals(TimeSpan.Zero)) 111 { 112 // Go visible now 113 IsContentVisible = true; 114 } 115 else 116 { 117 // Set a timer to go visible 118 _displayAfterTimer.Interval = DisplayAfter; 119 _displayAfterTimer.Start(); 120 } 121 } 122 else 123 { 124 // No longer visible 125 _displayAfterTimer.Stop(); 126 IsContentVisible = false; 127 } 128 ChangeVisualState(true); 129 } 130 131 /// <summary> 132 /// Gets or sets a value indicating the busy content to display to the user. 133 /// </summary> 134 public object BusyContent 135 { 136 get { return (object)GetValue(BusyContentProperty); } 137 set { SetValue(BusyContentProperty, value); } 138 } 139 140 /// <summary> 141 /// Identifies the BusyContent dependency property. 142 /// </summary> 143 public static readonly DependencyProperty BusyContentProperty = DependencyProperty.Register( 144 "BusyContent", 145 typeof(object), 146 typeof(BusyIndicator), 147 new PropertyMetadata(null)); 148 149 /// <summary> 150 /// Gets or sets a value indicating the template to use for displaying the busy content to the user. 151 /// </summary> 152 public DataTemplate BusyContentTemplate 153 { 154 get { return (DataTemplate)GetValue(BusyContentTemplateProperty); } 155 set { SetValue(BusyContentTemplateProperty, value); } 156 } 157 158 /// <summary> 159 /// Identifies the BusyTemplate dependency property. 160 /// </summary> 161 public static readonly DependencyProperty BusyContentTemplateProperty = DependencyProperty.Register( 162 "BusyContentTemplate", 163 typeof(DataTemplate), 164 typeof(BusyIndicator), 165 new PropertyMetadata(null)); 166 167 /// <summary> 168 /// Gets or sets a value indicating how long to delay before displaying the busy content. 169 /// </summary> 170 public TimeSpan DisplayAfter 171 { 172 get { return (TimeSpan)GetValue(DisplayAfterProperty); } 173 set { SetValue(DisplayAfterProperty, value); } 174 } 175 176 /// <summary> 177 /// Identifies the DisplayAfter dependency property. 178 /// </summary> 179 public static readonly DependencyProperty DisplayAfterProperty = DependencyProperty.Register( 180 "DisplayAfter", 181 typeof(TimeSpan), 182 typeof(BusyIndicator), 183 new PropertyMetadata(TimeSpan.FromSeconds(0.1))); 184 185 /// <summary> 186 /// Gets or sets a value indicating the style to use for the overlay. 187 /// </summary> 188 public Style OverlayStyle 189 { 190 get { return (Style)GetValue(OverlayStyleProperty); } 191 set { SetValue(OverlayStyleProperty, value); } 192 } 193 194 /// <summary> 195 /// Identifies the OverlayStyle dependency property. 196 /// </summary> 197 public static readonly DependencyProperty OverlayStyleProperty = DependencyProperty.Register( 198 "OverlayStyle", 199 typeof(Style), 200 typeof(BusyIndicator), 201 new PropertyMetadata(null)); 202 203 /// <summary> 204 /// Gets or sets a value indicating the style to use for the progress bar. 205 /// </summary> 206 public Style ProgressBarStyle 207 { 208 get { return (Style)GetValue(ProgressBarStyleProperty); } 209 set { SetValue(ProgressBarStyleProperty, value); } 210 } 211 212 /// <summary> 213 /// Identifies the ProgressBarStyle dependency property. 214 /// </summary> 215 public static readonly DependencyProperty ProgressBarStyleProperty = DependencyProperty.Register( 216 "ProgressBarStyle", 217 typeof(Style), 218 typeof(BusyIndicator), 219 new PropertyMetadata(null)); 220 221 #region BusyContent 222 223 private Grid InitBusyContent() 224 { 225 var g = new Grid(); 226 #region AddPaths 227 int counter = 12; 228 for (int i = 0; i < counter; i++) 229 { 230 Path path = new Path(); 231 path.Data = Geometry.Parse("M 0,0 L -1,0 L -1,-12 L 0,-13 L 1,-12 L 1,0 Z"); 232 path.Stroke = new SolidColorBrush(Colors.SkyBlue); 233 path.Fill = new SolidColorBrush(Colors.SkyBlue); 234 path.Opacity = 0.1; 235 236 TransformGroup tg = new TransformGroup(); 237 path.RenderTransform = tg; 238 TranslateTransform tt = new TranslateTransform(); 239 tt.Y = -8; tt.X = 1; 240 241 RotateTransform rt = new RotateTransform(); 242 rt.Angle = i * 30; 243 tg.Children.Add(tt); 244 tg.Children.Add(rt); 245 246 DoubleAnimation da = new DoubleAnimation(); 247 da.From = 1.0; 248 da.To = 0.1; 249 da.Duration = new Duration(TimeSpan.FromSeconds(1)); 250 251 da.BeginTime = TimeSpan.FromMilliseconds(i * 1000 / counter); 252 da.RepeatBehavior = RepeatBehavior.Forever; 253 254 path.VerticalAlignment = System.Windows.VerticalAlignment.Center; 255 path.HorizontalAlignment = System.Windows.HorizontalAlignment.Center; 256 g.Children.Add(path); 257 path.BeginAnimation(Path.OpacityProperty, da); 258 } 259 return g; 260 261 #endregion 262 } 263 264 #endregion 265 } 266 }
下面是实现的整个工程代码: