关于 Busy 控件,网上可以找到一大把,但是学习是学习,娱乐是娱乐,总不能总拿别人的东西“自娱愚人”,你会的,刻死在你脑子里,你不会的,那啥。。。我就不说了。
这个东西用到了 Adorner , 一开始总感觉到这个东西很神秘莫测,看别人讲的云里雾里,总之,不动手实现一遍,是永远不会理解的。
写这个东西,参考了网上的代码,如有雷同,笑一笑就行了,谢谢。
写这个软件,基本都是基于CM的,Register 都很少用,偶然看到这个方法,费了我老长时间。
<Grid ac:Busy.MaskType="Adorned" ac:Busy.Show="{Binding IsBusy, Mode=TwoWay}" ac:Busy.Text="{Binding BusyText, Mode=TwoWay}">
如上所示,怎样写代码才能将 Busy.Show / Text / MaskType 做为附加属性添到 Grid 里?
看一下实现代码:
1 public class Busy { 2 3 #region Text 4 public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(Busy), new PropertyMetadata(TextChanged)); 5 6 private static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { 7 Update((FrameworkElement)d); 8 } 9 10 public static void SetText(FrameworkElement target, string value) { 11 target.SetValue(TextProperty, value); 12 } 13 14 public static string GetText(FrameworkElement target) { 15 return (string)target.GetValue(TextProperty); 16 } 17 #endregion 18 。。。 19 。。。
static / RegisterAttached / SetXXX / GetXXX 及 PropertyMetadata 都是不可忽略的部分。
上面只是依赖属性,当各依赖属性变化的时候,都会执行 Update 方法,在 Update 方法里会去取 target 的 Adorner ,是实例化还是销毁都在 Update 方法里。
1 private static void Update(FrameworkElement target) { 2 var layer = AdornerLayer.GetAdornerLayer(target); 3 if (layer == null) { 4 Dispatcher.CurrentDispatcher.BeginInvoke(new Action<FrameworkElement>(o => Update(o)), DispatcherPriority.Loaded, target); 5 return; 6 } 7 8 var text = GetText(target); 9 var show = GetShow(target); 10 var maskType = GetMaskType(target); 11 var template = GetContentControlTemplate(target); 12 13 var adorner = GetAdorner(target); 14 15 if (show) { 16 if (adorner == null) { 17 adorner = new BusyIndicatorAdorner(target) { 18 MaskType = maskType, 19 ContentControlTemplate = template, 20 Text = text 21 }; 22 layer.Add(adorner); 23 SetAdorner(target, adorner); 24 } else { 25 adorner.MaskType = maskType; 26 adorner.ContentControlTemplate = template; 27 adorner.Text = text; 28 } 29 //adorner.Visibility = Visibility.Visible; 30 } else { 31 if (adorner != null) { 32 layer.Remove(adorner); 33 //如果不 Remove 并设置为 null, 在 使用AvalonDock的程序里,切换标签会使 adorner 的 Parent 丢失 34 //如果设置为 null ,会在再一次显示的时候,重建 35 //adorner.Visibility = Visibility.Collapsed; 36 SetAdorner(target, null); 37 } 38 } 39 }
显示时,将得到的 adorner 加到 AdornerLayer (Add) 里,不显示时,从 AdornerLayer 里移除它 (Remove)。
写这个件软,用了 AvalonDock 。如果不显示时,只是简单的将 adorner 设为不可见,在切换文档(标签)时,整个 Busy 都将不能正常工作,因为 adorer 还在,但是 adorner 的 Parent 却变为 null 了。不了解 AvalonDock 是怎样处理的,但是简单的修正方法是在不显示的时候,将 adorner 从 AdornerLayer 里移除,这样 在再一次显示的时候,就会重新实例一次。
1 public BusyIndicatorAdorner(FrameworkElement adornered) 2 : base(adornered) { 3 4 this.Indicator = new BusyIndicator(); 5 this.AddVisualChild(this.Indicator); 6 }
这里的 BusyIndicator 是一个自定义控件,由此可以看出整个过程如下:
将 Busy 以附加属性的方法附加到 FrameworkElement 上,当附加属性的值改变时,调用 Update 方法,Update 方法在将 Adorner 添加到 AdornerLayer 里, Adorner 负责实例化一个自定义控件,并将这个控件添加到该 Adorner 的可视树里,自定义控件负责这个 Busy 的外观。
Busy 顾名思义,就是将某一部分界面设为不可操作。为了实现这个目的,我加了一个 MaskType : None, Adornered, Window.
为None 的时候,不显示遮罩层,UI还可以继续操作。
Window 即指将整个窗口加一个遮罩,这样速个窗口在忙的时候,都不能操作。
Adornered 只将遮罩添加到Busy 所在的 FrameworkElement 上。
为None 和 Adornered 都好办,不用调整 adorner 的位置。但是为 Window 的时候,就要调整 Adornered 的位置了,因为 Adorner 的起点默认为 AdorneredElement 的起点。
1 public override GeneralTransform GetDesiredTransform(GeneralTransform transform) { 2 if (this.MaskType == MaskTypes.Window) { 3 //变换Adorner 的起点 4 var w = GetParentWindow(this.AdornedElement); 5 var a = this.AdornedElement.TransformToAncestor(w); 6 var b = a.Transform(new Point(0, 0)); 7 8 TransformGroup group = new TransformGroup(); 9 var t = new TranslateTransform(-b.X, -b.Y + ((SystemParameters.ResizeFrameHorizontalBorderHeight * 2) + SystemParameters.WindowCaptionHeight)); 10 group.Children.Add(t); 11 12 group.Children.Add(transform as Transform); 13 return group; 14 } else { 15 return base.GetDesiredTransform(transform); 16 } 17 }
这里,先获取 AdorneredElement 在其所在窗口上的位置(起点),在跟据这个点做位移(这里一并排除标题栏的高度,这就就不会出现不能关闭窗口的情况了)。
1 protected override Size MeasureOverride(Size constraint) { 2 Size s = Size.Empty; 3 switch (this.MaskType) { 4 case MaskTypes.None: 5 case MaskTypes.Adorned: 6 this.AdornedElement.Measure(constraint); 7 var ele = this.AdornedElement as FrameworkElement; 8 s = new Size(ele.ActualWidth, ele.ActualHeight); 9 //s = this.AdornedElement.DesiredSize; 10 break; 11 case MaskTypes.Window: 12 var w = GetParentWindow(this.AdornedElement); 13 s = new Size(w.ActualWidth, w.ActualHeight - ((SystemParameters.ResizeFrameHorizontalBorderHeight * 2) + SystemParameters.WindowCaptionHeight)); 14 break; 15 } 16 return s; 17 }
这个没什么好说的,就是计算 Adorner 所需的显示区域的大小。
在做一个需要长时间的操作之前,我希望将这个 Busy 显示出来,于是我将 Busy 设为 Show, 在操作完成时,将它关闭。想的很好。
但是:现实是在这个操作完成后,Busy 才显示,接着又关闭!即使用了 Dispatcher 也一样!Dispatcher 被阻止了,我相信可以用多线程来解决这个问题,但是简单的操作,着实没有必要用多线程来做!
搜了一下,有个叫 DispatcherFrame 的东西。
1 public static class DispatcherHelper { 2 3 public static void DoEvents() { 4 DispatcherFrame frame = new DispatcherFrame(); 5 Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, 6 new DispatcherOperationCallback(ExitFrame), frame); 7 Dispatcher.PushFrame(frame); 8 } 9 10 private static object ExitFrame(object f) { 11 ((DispatcherFrame)f).Continue = false; 12 13 return null; 14 } 15 16 }
1 public void Search(QueryEx<Order> ex) { 2 3 this.IsBusy = true; 4 this.NotifyOfPropertyChange(() => this.IsBusy); 5 6 DispatcherHelper.DoEvents(); 7 ... 8 ... 9 需要长时间的操作 10 ... 11 ...
好了,一切看似完美了。但是在有 AvalongDock 的应用了,当切换文档(标签)的时候,这个Busy 还是会消失不见,比较了一下,貌似是因为 adorner 的 Parent 被置为 null 引起的,如何解决,待探索。
附上源码:
Busy.cs
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.Documents; 9 using System.Windows.Threading; 10 11 namespace AsNum.WPF.Controls { 12 13 public enum MaskTypes { 14 None, 15 Adorned, 16 Window 17 } 18 public class Busy { 19 20 #region Text 21 public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(Busy), new PropertyMetadata(TextChanged)); 22 23 private static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { 24 Update((FrameworkElement)d); 25 } 26 27 public static void SetText(FrameworkElement target, string value) { 28 target.SetValue(TextProperty, value); 29 } 30 31 public static string GetText(FrameworkElement target) { 32 return (string)target.GetValue(TextProperty); 33 } 34 #endregion 35 36 #region MaskType 37 public static readonly DependencyProperty MaskTypeProperty = DependencyProperty.RegisterAttached("MaskType", typeof(MaskTypes), typeof(Busy), new PropertyMetadata(MaskTypes.Adorned, MaskTypeChanged)); 38 39 private static void MaskTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { 40 Update((FrameworkElement)d); 41 } 42 43 public static void SetMaskType(FrameworkElement target, MaskTypes type) { 44 target.SetValue(MaskTypeProperty, type); 45 } 46 47 public static MaskTypes GetMaskType(FrameworkElement target) { 48 return (MaskTypes)target.GetValue(MaskTypeProperty); 49 } 50 #endregion 51 52 #region Show 53 public static readonly DependencyProperty ShowProperty = DependencyProperty.RegisterAttached("Show", typeof(bool), typeof(Busy), new PropertyMetadata(false, ShowChanged)); 54 private static void ShowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { 55 Update((FrameworkElement)d); 56 } 57 58 public static void SetShow(FrameworkElement target, bool value) { 59 target.SetValue(ShowProperty, value); 60 } 61 62 public static bool GetShow(FrameworkElement target) { 63 return (bool)target.GetValue(ShowProperty); 64 } 65 #endregion 66 67 #region adorner 68 public static readonly DependencyProperty AdornerProperty = DependencyProperty.RegisterAttached("Adorner", typeof(BusyIndicatorAdorner), typeof(Busy)); 69 70 public static void SetAdorner(FrameworkElement target, BusyIndicatorAdorner adorner) { 71 target.SetValue(AdornerProperty, adorner); 72 } 73 74 public static BusyIndicatorAdorner GetAdorner(FrameworkElement target) { 75 return (BusyIndicatorAdorner)target.GetValue(AdornerProperty); 76 } 77 78 #endregion 79 80 #region 81 public static readonly DependencyProperty ContentControlTemplateProperty = DependencyProperty.Register("ContentControlTemplate", typeof(ControlTemplate), typeof(Busy)); 82 83 public static ControlTemplate GetContentControlTemplate(FrameworkElement target) { 84 return (ControlTemplate)target.GetValue(ContentControlTemplateProperty); 85 } 86 87 public static void SetContentControlTemplate(FrameworkElement target, ControlTemplate ctrl) { 88 target.SetValue(ContentControlTemplateProperty, ctrl); 89 } 90 91 #endregion 92 93 private static void Update(FrameworkElement target) { 94 var layer = AdornerLayer.GetAdornerLayer(target); 95 if (layer == null) { 96 Dispatcher.CurrentDispatcher.BeginInvoke(new Action<FrameworkElement>(o => Update(o)), DispatcherPriority.Loaded, target); 97 return; 98 } 99 100 var text = GetText(target); 101 var show = GetShow(target); 102 var maskType = GetMaskType(target); 103 var template = GetContentControlTemplate(target); 104 105 var adorner = GetAdorner(target); 106 107 if (show) { 108 if (adorner == null) { 109 adorner = new BusyIndicatorAdorner(target) { 110 MaskType = maskType, 111 ContentControlTemplate = template, 112 Text = text 113 }; 114 layer.Add(adorner); 115 SetAdorner(target, adorner); 116 } else { 117 adorner.MaskType = maskType; 118 adorner.ContentControlTemplate = template; 119 adorner.Text = text; 120 } 121 //adorner.Visibility = Visibility.Visible; 122 } else { 123 if (adorner != null) { 124 layer.Remove(adorner); 125 //如果不 Remove 并设置为 null, 在 使用AvalonDock的程序里,切换标签会使 adorner 的 Parent 丢失 126 //如果设置为 null ,会在再一次显示的时候,重建 127 //adorner.Visibility = Visibility.Collapsed; 128 SetAdorner(target, null); 129 } 130 } 131 } 132 } 133 }
BusyAdorner.cs
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.Documents; 9 using System.Windows.Media; 10 11 namespace AsNum.WPF.Controls { 12 public class BusyIndicatorAdorner : Adorner { 13 14 #region maskType 15 private MaskTypes maskType; 16 public MaskTypes MaskType { 17 get { 18 return this.maskType; 19 } 20 set { 21 this.maskType = value; 22 this.Indicator.MaskType = value; 23 } 24 } 25 #endregion 26 27 #region Text 28 private string text; 29 public string Text { 30 get { 31 return this.text; 32 } 33 set { 34 this.text = value; 35 this.Indicator.Text = value; 36 } 37 } 38 #endregion 39 40 #region contentControlTemplate 41 private ControlTemplate contentControlTemplate; 42 public ControlTemplate ContentControlTemplate { 43 get { 44 return this.contentControlTemplate; 45 } 46 set { 47 this.contentControlTemplate = value; 48 this.Indicator.ContentControlTemplate = value; 49 } 50 } 51 #endregion 52 53 private BusyIndicator Indicator; 54 55 protected override int VisualChildrenCount { 56 get { 57 return 1; 58 } 59 } 60 61 protected override System.Windows.Media.Visual GetVisualChild(int index) { 62 return this.Indicator; 63 } 64 65 public BusyIndicatorAdorner(FrameworkElement adornered) 66 : base(adornered) { 67 68 this.Indicator = new BusyIndicator(); 69 this.AddVisualChild(this.Indicator); 70 } 71 72 protected override Size MeasureOverride(Size constraint) { 73 Size s = Size.Empty; 74 switch (this.MaskType) { 75 case MaskTypes.None: 76 case MaskTypes.Adorned: 77 this.AdornedElement.Measure(constraint); 78 var ele = this.AdornedElement as FrameworkElement; 79 s = new Size(ele.ActualWidth, ele.ActualHeight); 80 //s = this.AdornedElement.DesiredSize; 81 break; 82 case MaskTypes.Window: 83 var w = GetParentWindow(this.AdornedElement); 84 s = new Size(w.ActualWidth, w.ActualHeight - ((SystemParameters.ResizeFrameHorizontalBorderHeight * 2) + SystemParameters.WindowCaptionHeight)); 85 break; 86 } 87 return s; 88 } 89 90 protected override Size ArrangeOverride(Size finalSize) { 91 this.Indicator.Measure(finalSize); 92 this.Indicator.Arrange(new Rect(finalSize)); 93 return finalSize; 94 } 95 96 public override GeneralTransform GetDesiredTransform(GeneralTransform transform) { 97 if (this.MaskType == MaskTypes.Window) { 98 //变换Adorner 的起点 99 var w = GetParentWindow(this.AdornedElement); 100 var a = this.AdornedElement.TransformToAncestor(w); 101 var b = a.Transform(new Point(0, 0)); 102 103 TransformGroup group = new TransformGroup(); 104 var t = new TranslateTransform(-b.X, -b.Y + ((SystemParameters.ResizeFrameHorizontalBorderHeight * 2) + SystemParameters.WindowCaptionHeight)); 105 group.Children.Add(t); 106 107 group.Children.Add(transform as Transform); 108 return group; 109 } else { 110 return base.GetDesiredTransform(transform); 111 } 112 } 113 114 public static Window GetParentWindow(DependencyObject child) { 115 DependencyObject parentObject = VisualTreeHelper.GetParent(child); 116 117 if (parentObject == null) { 118 return null; 119 } 120 121 Window parent = parentObject as Window; 122 if (parent != null) { 123 return parent; 124 } else { 125 return GetParentWindow(parentObject); 126 } 127 } 128 } 129 }
BusyIndicator.cs
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 AsNum.WPF.Controls { 17 18 [TemplatePart(Name = "CNT", Type = typeof(Control))] 19 [TemplatePart(Name = "MASK", Type = typeof(Border))] 20 public class BusyIndicator : ContentControl { 21 22 public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(BusyIndicator), new PropertyMetadata("Waiting...")); 23 public static DependencyProperty MaskTypeProperty = DependencyProperty.Register("MaskType", typeof(MaskTypes), typeof(BusyIndicator), new PropertyMetadata(MaskTypes.Adorned)); 24 public static DependencyProperty ContentControlTemplateProperty = DependencyProperty.Register("ContentControlTemplate", typeof(ControlTemplate), typeof(BusyIndicator)); 25 26 public string Text { 27 get { 28 return (string)this.GetValue(TextProperty); 29 } 30 set { 31 this.SetValue(TextProperty, value); 32 } 33 } 34 35 public MaskTypes MaskType { 36 get { 37 return (MaskTypes)this.GetValue(MaskTypeProperty); 38 } 39 set { 40 this.SetValue(MaskTypeProperty, value); 41 } 42 } 43 44 public ControlTemplate ContentControlTemplate { 45 get { 46 return (ControlTemplate)this.GetValue(ContentControlTemplateProperty); 47 } 48 set { 49 this.SetValue(ContentControlTemplateProperty, value); 50 } 51 } 52 53 static BusyIndicator() { 54 DefaultStyleKeyProperty.OverrideMetadata(typeof(BusyIndicator), new FrameworkPropertyMetadata(typeof(BusyIndicator))); 55 } 56 57 public BusyIndicator() { 58 this.DataContext = this; 59 } 60 public override void OnApplyTemplate() { 61 var mask = this.Template.FindName("MASK", this) as Border; 62 mask.Visibility = this.MaskType != MaskTypes.None ? Visibility.Visible : Visibility.Collapsed; 63 if (this.ContentControlTemplate != null) { 64 var tp = this.Template.FindName("CNT", this) as Control; 65 tp.Template = this.ContentControlTemplate; 66 } 67 } 68 } 69 }
Busy.xaml
1 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 2 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 3 xmlns:local="clr-namespace:AsNum.WPF.Controls"> 4 5 6 7 <ControlTemplate x:Key="DEFAULTTEMPLATE"> 8 <Grid> 9 <Border BorderBrush="Black" BorderThickness="1" CornerRadius="10" Margin="-1"> 10 <Border.Effect> 11 <BlurEffect /> 12 </Border.Effect> 13 </Border> 14 <Border Background="#FF10a8ab" CornerRadius="15" Padding="10, 5" Width="auto" Height="auto"> 15 <TextBlock Text="{Binding Text}" FontSize="16" FontWeight="Bold" Foreground="White" Width="auto" HorizontalAlignment="Center" Height="auto" /> 16 </Border> 17 </Grid> 18 </ControlTemplate> 19 20 <Style TargetType="{x:Type local:BusyIndicator}"> 21 <Setter Property="Template"> 22 <Setter.Value> 23 <ControlTemplate TargetType="{x:Type local:BusyIndicator}"> 24 <Grid> 25 <Border x:Name="MASK" Background="#66000000"> 26 <!--<Border.Triggers> 27 <EventTrigger RoutedEvent="Border.IsVisibleChanged"> 28 <BeginStoryboard> 29 <Storyboard> 30 <DoubleAnimation To="0" Duration="0:0:1" 31 Storyboard.TargetProperty="Opacity" /> 32 </Storyboard> 33 </BeginStoryboard> 34 </EventTrigger> 35 </Border.Triggers>--> 36 </Border> 37 <Control x:Name="CNT" Template="{StaticResource DEFAULTTEMPLATE}" HorizontalAlignment="Center" VerticalAlignment="Center" /> 38 </Grid> 39 </ControlTemplate> 40 </Setter.Value> 41 </Setter> 42 </Style> 43 44 </ResourceDictionary>
谢绝不加出处的转载!