自定义ListBox,实现单多选切换(复选框)
今天花了一天的时间收集了一些关于ListBox多选功能的文章研究了一下,并实现了自己的ListBox多选效果。
下面先分享一下我收集的几篇ListBox多选功能的文章:
(1)卤面网:a68215812 的文章:玩转控件之ListBox 多选功能,实现批量操作
(2)卤面网:chenxx08 的文章:[原创开发教程] 卤面网&亿动智道教程大赛+【原创特效列表控件系列之多选】
(3)博客园:Terry_龙 的文章:【WP7进阶】——分享一个可供切换状态的ListBox组件
接着说下自己所做的Listbox多选控件,我现在把它命名为SinOrMulListBox:
(1)声明一个类SinOrMulListBox,让它继承自ListBox。
同时定义一个依赖属性IsMultipleSelect便于我们在单选和多选之间切换。
由于ListBox的默认容器是ListBoxItem,而我们要在容器中添加一个复选框,所以为了方便我们定义一个自己的默认容器暂命名为SinOrMulListBoxItem。要在我们的SinOrMulListBox使用自己的容器SinOrMulListBoxItem则需要重写父类ListBox里的两个函数:GetContainerForItemOverride()和IsItemItsOwnContainerOverride(object item)
主要代码:
Generic.xaml 默认样式如下:
1 <ResourceDictionary 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:Locat="clr-namespace:SinOrMulListBox"> 5 6 <!--在SinOrMulListBoxItem容器中我们在原有的ListBoxItem样式中添加了一个复选框,并添加一个状态分组MultiSelectionStates让SinOrMulListBox在单多选中切换,为了切换效果平滑过渡,我们添加了一个过渡动画--> 7 <Style TargetType="Locat:SinOrMulListBoxItem"> 8 <Setter Property="Background" Value="Transparent"/> 9 <Setter Property="BorderThickness" Value="0"/> 10 <Setter Property="BorderBrush" Value="Transparent"/> 11 <Setter Property="Padding" Value="0"/> 12 <Setter Property="HorizontalContentAlignment" Value="Left"/> 13 <Setter Property="VerticalContentAlignment" Value="Top"/> 14 <Setter Property="Template"> 15 <Setter.Value> 16 <ControlTemplate TargetType="Locat:SinOrMulListBoxItem"> 17 <Border x:Name="LayoutRoot" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"> 18 <VisualStateManager.VisualStateGroups> 19 <VisualStateGroup x:Name="CommonStates"> 20 <VisualState x:Name="Normal"/> 21 <VisualState x:Name="MouseOver"/> 22 <VisualState x:Name="Disabled"> 23 <Storyboard> 24 <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="LayoutRoot"> 25 <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource TransparentBrush}"/> 26 </ObjectAnimationUsingKeyFrames> 27 <DoubleAnimation Duration="0" To=".5" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ContentContainer"/> 28 </Storyboard> 29 </VisualState> 30 </VisualStateGroup> 31 <VisualStateGroup x:Name="SelectionStates"> 32 <VisualState x:Name="Unselected"/> 33 <VisualState x:Name="Selected"> 34 <Storyboard> 35 <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentContainer"> 36 <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneAccentBrush}"/> 37 </ObjectAnimationUsingKeyFrames> 38 </Storyboard> 39 </VisualState> 40 </VisualStateGroup> 41 <VisualStateGroup x:Name="MultiSelectionStates"> 42 <VisualState x:Name="EnableSelection"> 43 <Storyboard> 44 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="SelecterCheckBox"> 45 <EasingDoubleKeyFrame KeyTime="0" Value="0"/> 46 <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/> 47 </DoubleAnimationUsingKeyFrames> 48 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="SelecterCheckBox"> 49 <EasingDoubleKeyFrame KeyTime="0" Value="0"/> 50 <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"> 51 <EasingDoubleKeyFrame.EasingFunction> 52 <CircleEase EasingMode="EaseOut"/> 53 </EasingDoubleKeyFrame.EasingFunction> 54 </EasingDoubleKeyFrame> 55 </DoubleAnimationUsingKeyFrames> 56 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="ContentContainer"> 57 <EasingDoubleKeyFrame KeyTime="0" Value="-62"/> 58 <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"> 59 <EasingDoubleKeyFrame.EasingFunction> 60 <CircleEase EasingMode="EaseOut"/> 61 </EasingDoubleKeyFrame.EasingFunction> 62 </EasingDoubleKeyFrame> 63 </DoubleAnimationUsingKeyFrames> 64 </Storyboard> 65 </VisualState> 66 <VisualState x:Name="DisableSelection"> 67 <Storyboard> 68 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="SelecterCheckBox"> 69 <EasingDoubleKeyFrame KeyTime="0" Value="1"/> 70 <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/> 71 </DoubleAnimationUsingKeyFrames> 72 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="ContentContainer"> 73 <EasingDoubleKeyFrame KeyTime="0" Value="0"/> 74 <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="-62"> 75 <EasingDoubleKeyFrame.EasingFunction> 76 <CircleEase EasingMode="EaseOut"/> 77 </EasingDoubleKeyFrame.EasingFunction> 78 </EasingDoubleKeyFrame> 79 </DoubleAnimationUsingKeyFrames> 80 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="SelecterCheckBox"> 81 <EasingDoubleKeyFrame KeyTime="0" Value="1"/> 82 <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"> 83 <EasingDoubleKeyFrame.EasingFunction> 84 <CircleEase EasingMode="EaseOut"/> 85 </EasingDoubleKeyFrame.EasingFunction> 86 </EasingDoubleKeyFrame> 87 </DoubleAnimationUsingKeyFrames> 88 </Storyboard> 89 </VisualState> 90 </VisualStateGroup> 91 </VisualStateManager.VisualStateGroups> 92 <Grid> 93 <Grid.ColumnDefinitions> 94 <ColumnDefinition Width="Auto"/> 95 <ColumnDefinition Width="*"/> 96 </Grid.ColumnDefinitions> 97 98 <CheckBox x:Name="SelecterCheckBox" Width="62" VerticalAlignment="Top" Margin="5,-12,0,0" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" Opacity="0"> 99 <CheckBox.RenderTransform> 100 <CompositeTransform ScaleX="0"/> 101 </CheckBox.RenderTransform> 102 </CheckBox> 103 <ContentControl Grid.Column="1" x:Name="ContentContainer" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"> 104 <ContentControl.RenderTransform> 105 <CompositeTransform TranslateX="-62"/> 106 </ContentControl.RenderTransform> 107 </ContentControl> 108 </Grid> 109 </Border> 110 </ControlTemplate> 111 </Setter.Value> 112 </Setter> 113 </Style> 114 115 <!--SinOrMulListBox的样式其实和ListBox是一样的,我们主要修改是继承ListBoxItem的容器控件SinOrMulListBoxItem的样式--> 116 <Style TargetType="Locat:SinOrMulListBox"> 117 <Setter Property="Background" Value="Transparent"/> 118 <Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/> 119 <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/> 120 <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> 121 <Setter Property="BorderThickness" Value="0"/> 122 <Setter Property="BorderBrush" Value="Transparent"/> 123 <Setter Property="Padding" Value="0"/> 124 <Setter Property="Template"> 125 <Setter.Value> 126 <ControlTemplate TargetType="Locat:SinOrMulListBox"> 127 <ScrollViewer x:Name="ScrollViewer" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Padding="{TemplateBinding Padding}"> 128 <ItemsPresenter/> 129 </ScrollViewer> 130 </ControlTemplate> 131 </Setter.Value> 132 </Setter> 133 </Style> 134 135 </ResourceDictionary>
其中SinOrMulListBox的样式其实和ListBox是一样的,我们主要修改是继承ListBoxItem的容器控件SinOrMulListBoxItem的样式;而在SinOrMulListBoxItem容器中我们在原有的ListBoxItem样式中添加了一个复选框,并添加一个状态分组MultiSelectionStates让SinOrMulListBox在单多选中切换,为了切换效果平滑过渡,我们添加了一个过渡动画
SinOrMulListBox.cs 主要代码如下:
1 using tool; 2 using System; 3 using System.Windows; 4 using System.Windows.Controls; 5 6 namespace SinOrMulListBox 7 { 8 [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(SinOrMulListBoxItem))] 9 public class SinOrMulListBox : ListBox 10 { 11 public SinOrMulListBox() 12 { 13 DefaultStyleKey = typeof(SinOrMulListBox); 14 } 15 16 #region Rewrite the superclass method 17 18 protected override DependencyObject GetContainerForItemOverride() 19 { 20 var item = new SinOrMulListBoxItem(); 21 if (ItemContainerStyle != null) 22 item.Style = ItemContainerStyle; 23 return item; 24 } 25 protected override bool IsItemItsOwnContainerOverride(object item) 26 { 27 bool nBool = item is SinOrMulListBoxItem; 28 29 return nBool; 30 } 31 32 public override void OnApplyTemplate() 33 { 34 base.OnApplyTemplate(); 35 IsMultipleSelect = SelectionMode != SelectionMode.Single; 36 } 37 38 #endregion 39 40 #region Custom fuction 41 42 /// <summary> 43 /// Don't all selected . 44 /// </summary> 45 public void UnSelectAll() 46 { 47 var items = this.Descendants<SinOrMulListBoxItem>(); 48 SelectedItem = null; 49 items.ForEachEx(t => t.IsSelected = false);//t.CanCheck 50 } 51 52 #endregion 53 54 #region Custom DependencyObject 55 /// <summary> 56 /// Whether can choose more options? 57 /// </summary> 58 public bool IsMultipleSelect 59 { 60 get { return (bool)GetValue(IsMultipleSelectProperty); } 61 set { SetValue(IsMultipleSelectProperty, value); } 62 } 63 64 public static readonly DependencyProperty IsMultipleSelectProperty = 65 DependencyProperty.Register("IsMultipleSelect", typeof(bool), typeof(SinOrMulListBox), new PropertyMetadata(false, OnIsMultipleSelectPropertyChanged)); 66 67 private static void OnIsMultipleSelectPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 68 { 69 var listBox = sender as SinOrMulListBox; 70 //Debug.Assert(listBox != null); 71 listBox.OnIsMultipleSelectChanged(); 72 } 73 74 private void OnIsMultipleSelectChanged() 75 { 76 var items = this.Descendants<SinOrMulListBoxItem>(); 77 if (IsMultipleSelect) 78 { 79 SelectionMode = SelectionMode.Multiple; 80 items.ForEachEx(t => t.CanCheck = true); 81 } 82 else 83 { 84 SelectionMode = SelectionMode.Single; 85 SelectedItem = null; 86 items.ForEachEx(t => t.CanCheck = false); 87 } 88 } 89 #endregion 90 } 91 }
这里面我额外添加了一个方法UnSelectAll(),用于对应继承ListBox的方法SelectAll(),实现全部不选择。
在OnIsMultipleSelectChanged()中items.ForEachEx(t => t.CanCheck = false)来启动SinOrMulListBoxItem中复选框的显示与不显示动画
(2)声明一个类SinOrMulListBoxItem,让它继承自ListBoxItem。
同时定义一个依赖属性CanCheck便于我们在单选和多选之间切换,并启动过渡动画。
CheckBox的IsChecked绑定ListBoxItem的IsSelected属性,通过ListBox.SelectItems取出选中集合
SinOrMulListBoxItem.cs 主要代码如下:
1 using tool; 2 using System; 3 using System.Linq; 4 using System.Windows; 5 using System.Windows.Media; 6 using System.Windows.Controls; 7 8 namespace SinOrMulListBox 9 { 10 11 public class SinOrMulListBoxItem : ListBoxItem 12 { 13 public SinOrMulListBoxItem() 14 { 15 DefaultStyleKey = typeof(SinOrMulListBoxItem); 16 } 17 18 public override void OnApplyTemplate() 19 { 20 base.OnApplyTemplate(); 21 } 22 23 #region Custom DependencyProperty 24 /// <summary> 25 /// Is used check box ? 26 /// </summary> 27 internal bool CanCheck 28 { 29 get { return (bool)GetValue(CanCheckProperty); } 30 set { SetValue(CanCheckProperty, value); } 31 } 32 33 internal static readonly DependencyProperty CanCheckProperty = 34 DependencyProperty.Register("CanCheck", typeof(bool), typeof(SinOrMulListBoxItem), new PropertyMetadata(false, OnCanCheckPropertyChanged)); 35 36 private static void OnCanCheckPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 37 { 38 var item = sender as SinOrMulListBoxItem; 39 //Debug.Assert(item != null); 40 item.OnCanCheckChanged(); 41 } 42 43 private void OnCanCheckChanged() 44 { 45 VisualStateManager.GoToState(this, CanCheck ? "EnableSelection" : "DisableSelection", true); 46 } 47 #endregion 48 } 49 50 public class StaticFunction 51 { 52 /// <summary> 53 /// Find father control 54 /// </summary> 55 /// <typeparam name="T"></typeparam> 56 /// <param name="obj"></param> 57 /// <returns></returns> 58 public static T FindParentOfType<T>(DependencyObject obj) where T : FrameworkElement 59 { 60 DependencyObject parent = VisualTreeHelper.GetParent(obj); 61 while (parent != null) 62 { 63 if (parent is T) 64 { 65 return (T)parent; 66 } 67 parent = VisualTreeHelper.GetParent(parent); 68 } 69 return null; 70 } 71 } 72 }
注意:到这其实应该说所有所有功能都OK了,可运行后发现个问题~~~~(>_<)~~~~ 在显示区内的SinOrMulListBoxItem没有问题,但在显示区外的SinOrMulListBoxItem有的显示复选框有的不显示复选框,而功能什么运行使用又都正常,很郁闷。根据这些现象我猜测问题出现在ListBox本身的虚拟化机制上,也就是说,当SinOrMulListBoxItem在显示区时候才会new而不在显示区的SinOrMulListBoxItem是不会new出来的。因此我们应该让在显示区外的SinOrMulListBoxItem被new的时候知道自己是否应该显示复选框,为此我们添加了一些代码解决这一问题。下面是解决后的完整代码:
1 using tool; 2 using System; 3 using System.Linq; 4 using System.Windows; 5 using System.Windows.Media; 6 using System.Windows.Controls; 7 8 namespace SinOrMulListBox 9 { 10 11 public class SinOrMulListBoxItem : ListBoxItem 12 { 13 public SinOrMulListBoxItem() 14 { 15 DefaultStyleKey = typeof(SinOrMulListBoxItem); 16 17 Loaded += SinOrMulListBoxItem_Loaded; 18 } 19 20 public override void OnApplyTemplate() 21 { 22 base.OnApplyTemplate(); 23 } 24 25 #region Custom DependencyProperty 26 /// <summary> 27 /// Is used check box ? 28 /// </summary> 29 internal bool CanCheck 30 { 31 get { return (bool)GetValue(CanCheckProperty); } 32 set { SetValue(CanCheckProperty, value); } 33 } 34 35 internal static readonly DependencyProperty CanCheckProperty = 36 DependencyProperty.Register("CanCheck", typeof(bool), typeof(SinOrMulListBoxItem), new PropertyMetadata(false, OnCanCheckPropertyChanged)); 37 38 private static void OnCanCheckPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 39 { 40 var item = sender as SinOrMulListBoxItem; 41 //Debug.Assert(item != null); 42 item.OnCanCheckChanged(); 43 } 44 45 private void OnCanCheckChanged() 46 { 47 VisualStateManager.GoToState(this, CanCheck ? "EnableSelection" : "DisableSelection", true); 48 } 49 #endregion 50 51 private SinOrMulListBox _listBox; 52 private SinOrMulListBox ListBox 53 { 54 get { return _listBox ?? (_listBox = this.Ancestors<SinOrMulListBox>().FirstOrDefault()); } 55 } 56 57 void SinOrMulListBoxItem_Loaded(object sender, RoutedEventArgs e) 58 { 59 CanCheck = ListBox.IsMultipleSelect; 60 } 61 } 62 63 public class StaticFunction 64 { 65 /// <summary> 66 /// Find father control 67 /// </summary> 68 /// <typeparam name="T"></typeparam> 69 /// <param name="obj"></param> 70 /// <returns></returns> 71 public static T FindParentOfType<T>(DependencyObject obj) where T : FrameworkElement 72 { 73 DependencyObject parent = VisualTreeHelper.GetParent(obj); 74 while (parent != null) 75 { 76 if (parent is T) 77 { 78 return (T)parent; 79 } 80 parent = VisualTreeHelper.GetParent(parent); 81 } 82 return null; 83 } 84 } 85 }
在这里再附上子父控件的查找类,这个是从[原创开发教程] 卤面网&亿动智道教程大赛+【原创特效列表控件系列之多选】中拷贝的。
TreeExtensions.cs 所有代码:
1 using System; 2 using System.Linq; 3 using System.Windows; 4 using System.Windows.Data; 5 using System.Windows.Media; 6 using System.Collections.Generic; 7 8 namespace tool 9 { 10 public static class TreeExtensions 11 { 12 /// <summary> 13 /// 返回可视树中所有子代元素集合(不包括本身) 14 /// </summary> 15 public static IEnumerable<DependencyObject> Descendants(this DependencyObject item) 16 { 17 foreach (var child in item.ChildrenEx()) 18 { 19 yield return child; 20 21 foreach (var grandChild in child.Descendants()) 22 { 23 yield return grandChild; 24 } 25 } 26 } 27 28 /// <summary> 29 /// 返回可视树中所有子代元素集合(包括本身) 30 /// </summary> 31 public static IEnumerable<DependencyObject> DescendantsAndSelf(this DependencyObject item) 32 { 33 yield return item; 34 35 foreach (var child in item.Descendants()) 36 { 37 yield return child; 38 } 39 } 40 41 /// <summary> 42 /// 返回可视树中所有父代元素集合(不包括本身) 43 /// </summary> 44 public static IEnumerable<DependencyObject> Ancestors(this DependencyObject item) 45 { 46 var parent = item.ParentEx(); 47 while (parent != null) 48 { 49 yield return parent; 50 parent = parent.ParentEx(); 51 } 52 } 53 54 /// <summary> 55 /// 返回可视树中所有父代元素集合(包括本身) 56 /// </summary> 57 public static IEnumerable<DependencyObject> AncestorsAndSelf(this DependencyObject item) 58 { 59 yield return item; 60 61 foreach (var ancestor in item.Ancestors()) 62 { 63 yield return ancestor; 64 } 65 } 66 67 /// <summary> 68 /// 返回可视树中下一代所有的子元素(不包括自身) 69 /// </summary> 70 public static IEnumerable<DependencyObject> Elements(this DependencyObject item) 71 { 72 return item.ChildrenEx(); 73 } 74 75 /// <summary> 76 /// 返回可视树中与该元素位于同一级别且文档顺序位于该元素前面的所有元素 77 /// </summary> 78 public static IEnumerable<DependencyObject> ElementsBeforeSelf(this DependencyObject item) 79 { 80 var parent = item.ParentEx(); 81 if (parent == null) 82 yield break; 83 foreach (var child in item.Elements().TakeWhile(child => !child.Equals(item))) 84 { 85 yield return child; 86 } 87 } 88 89 /// <summary> 90 /// 返回可视树中与该元素位于同一级别且文档顺序位于该元素后面的所有元素 91 /// </summary> 92 public static IEnumerable<DependencyObject> ElementsAfterSelf(this DependencyObject item) 93 { 94 var parent = item.ParentEx(); 95 if (parent == null) 96 yield break; 97 var afterSelf = false; 98 foreach (var child in parent.Elements()) 99 { 100 if (afterSelf) 101 yield return child; 102 if (child.Equals(item)) 103 afterSelf = true; 104 } 105 } 106 107 /// <summary> 108 /// 返回可视树中下一代所有的子元素(包括本身) 109 /// </summary> 110 public static IEnumerable<DependencyObject> ElementsAndSelf(this DependencyObject item) 111 { 112 yield return item; 113 114 foreach (var child in item.Elements()) 115 { 116 yield return child; 117 } 118 } 119 120 /// <summary> 121 /// 返回可视树中所有子代中类型符合要求的元素集合(不包括自身) 122 /// </summary> 123 public static IEnumerable<T> Descendants<T>(this DependencyObject item) 124 { 125 return item.Descendants().Where(i => i is T).Cast<T>(); 126 } 127 128 /// <summary> 129 /// 返回可视树中与该元素位于同一级别且文档顺序位于该元素前面的符合类型要求的所有元素 130 /// </summary> 131 public static IEnumerable<T> ElementsBeforeSelf<T>(this DependencyObject item) 132 { 133 return item.ElementsBeforeSelf().Where(i => i is T).Cast<T>(); 134 } 135 136 /// <summary> 137 /// 返回可视树中与该元素位于同一级别且文档顺序位于该元素后面的符合类型要求的所有元素 138 /// </summary> 139 public static IEnumerable<T> ElementsAfterSelf<T>(this DependencyObject item) 140 { 141 return item.ElementsAfterSelf().Where(i => i is T).Cast<T>(); 142 } 143 144 /// <summary> 145 /// 返回可视树中所有子代中类型符合要求的元素集合(包括自身) 146 /// </summary> 147 public static IEnumerable<T> DescendantsAndSelf<T>(this DependencyObject item) 148 { 149 return item.DescendantsAndSelf().Where(i => i is T).Cast<T>(); 150 } 151 152 /// <summary> 153 /// 返回可视树中所有父代中类型符合要求的元素集合(不包括自身) 154 /// </summary> 155 public static IEnumerable<T> Ancestors<T>(this DependencyObject item) 156 { 157 return item.Ancestors().Where(i => i is T).Cast<T>(); 158 } 159 160 /// <summary> 161 /// 返回可视树中所有父代中类型符合要求的元素集合(包括自身) 162 /// which match the given type. 163 /// </summary> 164 public static IEnumerable<T> AncestorsAndSelf<T>(this DependencyObject item) 165 { 166 return item.AncestorsAndSelf().Where(i => i is T).Cast<T>(); 167 } 168 169 /// <summary> 170 /// 返回可视树中下一代符合类型要求的所有子元素(不包括自身) 171 /// </summary> 172 public static IEnumerable<T> Elements<T>(this DependencyObject item) 173 { 174 return item.Elements().Where(i => i is T).Cast<T>(); 175 } 176 177 /// <summary> 178 /// 返回可视树中下一代符合类型要求的所有子元素(包括自身) 179 /// </summary> 180 public static IEnumerable<T> ElementsAndSelf<T>(this DependencyObject item) 181 { 182 return item.ElementsAndSelf().Where(i => i is T).Cast<T>(); 183 } 184 185 } 186 187 public static class EnumerableTreeExtensions 188 { 189 /// <summary> 190 /// 对元素集合应用相同的函数,并返回该函数的结果集合 191 /// </summary> 192 private static IEnumerable<DependencyObject> DrillDown(this IEnumerable<DependencyObject> items, 193 Func<DependencyObject, IEnumerable<DependencyObject>> function) 194 { 195 return items.SelectMany(function); 196 } 197 198 /// <summary> 199 /// 对元素集合应用相同的函数,并返回该函数结果符合类型要求的集合 200 /// </summary> 201 public static IEnumerable<T> DrillDown<T>(this IEnumerable<DependencyObject> items, 202 Func<DependencyObject, IEnumerable<DependencyObject>> function) 203 where T : DependencyObject 204 { 205 return items.SelectMany(item => function(item).OfType<T>()); 206 } 207 208 /// <summary> 209 /// 返回集合中所有的子代元素集合(不包括自身) 210 /// </summary> 211 public static IEnumerable<DependencyObject> Descendants(this IEnumerable<DependencyObject> items) 212 { 213 return items.DrillDown(i => i.Descendants()); 214 } 215 216 /// <summary> 217 /// 返回集合中所有的子代元素集合(包括自身) 218 /// </summary> 219 public static IEnumerable<DependencyObject> DescendantsAndSelf(this IEnumerable<DependencyObject> items) 220 { 221 return items.DrillDown(i => i.DescendantsAndSelf()); 222 } 223 224 /// <summary> 225 /// 返回集合中所有的父代元素集合(不包括自身) 226 /// </summary> 227 public static IEnumerable<DependencyObject> Ancestors(this IEnumerable<DependencyObject> items) 228 { 229 return items.DrillDown(i => i.Ancestors()); 230 } 231 232 /// <summary> 233 /// 返回集合中所有的父代元素集合(包括自身) 234 /// </summary> 235 public static IEnumerable<DependencyObject> AncestorsAndSelf(this IEnumerable<DependencyObject> items) 236 { 237 return items.DrillDown(i => i.AncestorsAndSelf()); 238 } 239 240 /// <summary> 241 /// Returns a collection of child elements. 242 /// </summary> 243 public static IEnumerable<DependencyObject> Elements(this IEnumerable<DependencyObject> items) 244 { 245 return items.DrillDown(i => i.Elements()); 246 } 247 248 /// <summary> 249 /// Returns a collection containing this element and all child elements. 250 /// </summary> 251 public static IEnumerable<DependencyObject> ElementsAndSelf(this IEnumerable<DependencyObject> items) 252 { 253 return items.DrillDown(i => i.ElementsAndSelf()); 254 } 255 256 /// <summary> 257 /// Returns a collection of descendant elements which match the given type. 258 /// </summary> 259 public static IEnumerable<T> Descendants<T>(this IEnumerable<DependencyObject> items) 260 where T : DependencyObject 261 { 262 return items.DrillDown<T>(i => i.Descendants()); 263 } 264 265 /// <summary> 266 /// Returns a collection containing this element and all descendant elements. 267 /// which match the given type. 268 /// </summary> 269 public static IEnumerable<T> DescendantsAndSelf<T>(this IEnumerable<DependencyObject> items) 270 where T : DependencyObject 271 { 272 return items.DrillDown<T>(i => i.DescendantsAndSelf()); 273 } 274 275 /// <summary> 276 /// Returns a collection of ancestor elements which match the given type. 277 /// </summary> 278 public static IEnumerable<T> Ancestors<T>(this IEnumerable<DependencyObject> items) 279 where T : DependencyObject 280 { 281 return items.DrillDown<T>(i => i.Ancestors()); 282 } 283 284 /// <summary> 285 /// Returns a collection containing this element and all ancestor elements. 286 /// which match the given type. 287 /// </summary> 288 public static IEnumerable<T> AncestorsAndSelf<T>(this IEnumerable<DependencyObject> items) 289 where T : DependencyObject 290 { 291 return items.DrillDown<T>(i => i.AncestorsAndSelf()); 292 } 293 294 /// <summary> 295 /// Returns a collection of child elements which match the given type. 296 /// </summary> 297 public static IEnumerable<T> Elements<T>(this IEnumerable<DependencyObject> items) 298 where T : DependencyObject 299 { 300 return items.DrillDown<T>(i => i.Elements()); 301 } 302 303 /// <summary> 304 /// Returns a collection containing this element and all child elements. 305 /// which match the given type. 306 /// </summary> 307 public static IEnumerable<T> ElementsAndSelf<T>(this IEnumerable<DependencyObject> items) 308 where T : DependencyObject 309 { 310 return items.DrillDown<T>(i => i.ElementsAndSelf()); 311 } 312 } 313 314 public static class CommonExtension 315 { 316 #region DependencyObject Extension 317 318 #region 监听依赖属性变化 319 private static readonly Dictionary<DependencyObject, List<PropertyCallbackInfo>> _callbackDic = new Dictionary<DependencyObject, List<PropertyCallbackInfo>>(); 320 private static readonly Dictionary<DependencyProperty, string> _propertyNames = new Dictionary<DependencyProperty, string>(); 321 322 /// <summary> 323 /// 添加依赖属性改变时的回调函数 324 /// </summary> 325 /// <param name="obj"></param> 326 /// <param name="propertyName"></param> 327 /// <param name="callback"></param> 328 public static void AddPropertyChangedCallback(this DependencyObject obj, string propertyName, PropertyChangedCallback callback) 329 { 330 try 331 { 332 var attachName = "ListenAttached" + propertyName; 333 if (!_callbackDic.ContainsKey(obj)) 334 { 335 _callbackDic.Add(obj, new List<PropertyCallbackInfo>()); 336 } 337 var infoList = _callbackDic[obj]; 338 var info = infoList.FirstOrDefault(t => t.PropertyName == attachName); 339 if (info == null) 340 { 341 info = new PropertyCallbackInfo { PropertyName = attachName }; 342 var binding = new Binding(propertyName) { Source = obj }; 343 var pro = DependencyProperty.RegisterAttached(attachName, typeof(object), obj.GetType(), new PropertyMetadata(OnPropertyChanged)); 344 BindingOperations.SetBinding(obj, pro, binding); 345 _propertyNames.Add(pro, attachName); 346 infoList.Add(info); 347 } 348 info.Callbacks.Add(callback); 349 } 350 catch (Exception e) 351 { 352 //Debug.WriteLine("执行CommonExtension中的AddPropertyChangedCallback函数出错:" + e.Message); 353 } 354 } 355 356 /// <summary> 357 /// 移除依赖属性改变时的回调函数 358 /// </summary> 359 /// <param name="obj"></param> 360 /// <param name="propertyName"></param> 361 /// <param name="callback"></param> 362 public static void RemovePropertyChangedCallback(this DependencyObject obj, string propertyName, PropertyChangedCallback callback) 363 { 364 List<PropertyCallbackInfo> infoList; 365 if (!_callbackDic.TryGetValue(obj, out infoList)) 366 return; 367 var attachName = "ListenAttached" + propertyName; 368 var info = infoList.FirstOrDefault(t => t.PropertyName == attachName); 369 if (info == null) 370 return; 371 info.Callbacks.Remove(callback); 372 } 373 374 private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 375 { 376 string name; 377 if (!_propertyNames.TryGetValue(e.Property, out name)) 378 return; 379 List<PropertyCallbackInfo> infoList; 380 if (!_callbackDic.TryGetValue(sender, out infoList)) 381 return; 382 var info = infoList.FirstOrDefault(t => t.PropertyName == name); 383 if (info == null) 384 return; 385 info.Callbacks.ForEach(t => t(sender, e)); 386 } 387 388 private class PropertyCallbackInfo 389 { 390 public string PropertyName { get; set; } 391 private readonly List<PropertyChangedCallback> _callbacks = new List<PropertyChangedCallback>(); 392 public List<PropertyChangedCallback> Callbacks 393 { 394 get { return _callbacks; } 395 } 396 } 397 #endregion 398 399 /// <summary> 400 /// 返回可视树中该元素的所有子元素 401 /// </summary> 402 /// <param name="item"></param> 403 /// <returns></returns> 404 public static IEnumerable<DependencyObject> ChildrenEx(this DependencyObject item) 405 { 406 var childrenCount = VisualTreeHelper.GetChildrenCount(item); 407 for (var i = 0; i < childrenCount; i++) 408 { 409 yield return VisualTreeHelper.GetChild(item, i); 410 } 411 } 412 413 /// <summary> 414 /// 返回可视树中该元素的父元素 415 /// </summary> 416 /// <param name="item"></param> 417 /// <returns></returns> 418 public static DependencyObject ParentEx(this DependencyObject item) 419 { 420 return VisualTreeHelper.GetParent(item); 421 } 422 #endregion 423 424 #region IEnumerable Extension 425 /// <summary> 426 /// 枚举中的每个对象执行相同的动作 427 /// </summary> 428 /// <typeparam name="T"></typeparam> 429 /// <param name="items"></param> 430 /// <param name="action"></param> 431 public static void ForEachEx<T>(this IEnumerable<T> items, Action<T> action) 432 { 433 foreach (var item in items) 434 action(item); 435 } 436 437 /// <summary> 438 /// 枚举中的每个对象执行相同的动作 439 /// </summary> 440 /// <typeparam name="T">枚举类型</typeparam> 441 /// <typeparam name="S">需要执行动作的类型</typeparam> 442 /// <param name="items"></param> 443 /// <param name="action"></param> 444 public static void ForEachEx<S, T>(this IEnumerable<T> items, Action<S> action) 445 where S : class 446 { 447 foreach (var item in items.OfType<S>()) 448 { 449 action(item); 450 } 451 } 452 453 #endregion 454 } 455 }
(3)下面是我们的测试代码。这个比较简单直接上代码
MainPage.xaml 主要代码
1 <phone:PhoneApplicationPage xmlns:my="clr-namespace:SinOrMulListBox;assembly=SinOrMulListBox" 2 x:Class="TestLstBoxDemo.MainPage" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" 6 xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" 7 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 8 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 9 mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696" 10 FontFamily="{StaticResource PhoneFontFamilyNormal}" 11 FontSize="{StaticResource PhoneFontSizeNormal}" 12 Foreground="{StaticResource PhoneForegroundBrush}" 13 SupportedOrientations="Portrait" Orientation="Portrait" 14 shell:SystemTray.IsVisible="True" d:DataContext="{d:DesignData Design/ViewModelSampleData.xaml}"> 15 16 <!--LayoutRoot is the root grid where all page content is placed--> 17 <Grid x:Name="LayoutRoot" Background="Transparent"> 18 <Grid.RowDefinitions> 19 <RowDefinition Height="Auto"/> 20 <RowDefinition Height="*"/> 21 </Grid.RowDefinitions> 22 23 <!--TitlePanel contains the name of the application and page title--> 24 <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> 25 <TextBlock x:Name="ApplicationTitle" Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/> 26 </StackPanel> 27 28 <!--ContentPanel - place additional content here--> 29 <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> 30 <my:SinOrMulListBox Name="listBoxWithBoxes" Margin="0,0,0,0" IsMultipleSelect="True" ItemsSource="{Binding SimpleModels}"> 31 <my:SinOrMulListBox.ItemTemplate> 32 <DataTemplate> 33 <StackPanel Orientation="Horizontal" Margin="0,0,0,20"> 34 <Rectangle Height="100" Width="100" Fill="#FFE5001b" Margin="0,0,9,0"/> 35 <StackPanel> 36 <TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextLargeStyle}"/> 37 <TextBlock Text="{Binding Description}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/> 38 </StackPanel> 39 </StackPanel> 40 </DataTemplate> 41 </my:SinOrMulListBox.ItemTemplate> 42 </my:SinOrMulListBox> 43 </Grid> 44 </Grid> 45 46 <!--Sample code showing usage of ApplicationBar--> 47 <phone:PhoneApplicationPage.ApplicationBar> 48 <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True"> 49 <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1" Click="ApplicationBarIconButton_Click"/> 50 <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1" Click="ApplicationBarIconButton_Click_1"/> 51 <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1" Click="ApplicationBarIconButton_Click_2"/> 52 53 <shell:ApplicationBar.MenuItems> 54 <shell:ApplicationBarMenuItem Text="开启单选" Click="ApplicationBarMenuItem_Click"/> 55 <shell:ApplicationBarMenuItem Text="开启多选" Click="ApplicationBarMenuItem_Click_1"/> 56 </shell:ApplicationBar.MenuItems> 57 </shell:ApplicationBar> 58 </phone:PhoneApplicationPage.ApplicationBar> 59 60 </phone:PhoneApplicationPage>
MainPage.xaml.cs 主要代码
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Net; 5 using System.Windows; 6 using System.Windows.Controls; 7 using System.Windows.Documents; 8 using System.Windows.Input; 9 using System.Windows.Media; 10 using System.Windows.Media.Animation; 11 using System.Windows.Shapes; 12 using Microsoft.Phone.Controls; 13 14 namespace TestLstBoxDemo 15 { 16 public partial class MainPage : PhoneApplicationPage 17 { 18 // Constructor 19 public MainPage() 20 { 21 InitializeComponent(); 22 23 this.Loaded += new RoutedEventHandler(MainPage_Loaded); 24 } 25 26 void MainPage_Loaded(object sender, RoutedEventArgs e) 27 { 28 DataContext = App.ViewModel; 29 } 30 31 protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 32 { 33 if (!App.ViewModel.IsDataLoaded) 34 { 35 App.ViewModel.LoadData(); 36 } 37 base.OnNavigatedTo(e); 38 } 39 40 //查看选中所有选项 41 private void ApplicationBarIconButton_Click(object sender, EventArgs e) 42 { 43 List<SimpleModel> SelectedItems = new List<SimpleModel>(); 44 foreach (var item in listBoxWithBoxes.SelectedItems) 45 { 46 SimpleModel nModel = item as SimpleModel; 47 if (nModel != null) 48 { 49 SelectedItems.Add(nModel); 50 } 51 } 52 } 53 //全选 54 private void ApplicationBarIconButton_Click_1(object sender, EventArgs e) 55 { 56 listBoxWithBoxes.SelectAll(); 57 } 58 //全部不选 59 private void ApplicationBarIconButton_Click_2(object sender, EventArgs e) 60 { 61 listBoxWithBoxes.UnSelectAll(); 62 } 63 //开启单选 64 private void ApplicationBarMenuItem_Click(object sender, EventArgs e) 65 { 66 listBoxWithBoxes.IsMultipleSelect = false; 67 } 68 //开启多选 69 private void ApplicationBarMenuItem_Click_1(object sender, EventArgs e) 70 { 71 listBoxWithBoxes.IsMultipleSelect = true; 72 } 73 } 74 }
ViewModel 主要代码
1 using System; 2 using System.Net; 3 using System.ComponentModel; 4 5 namespace TestLstBoxDemo 6 { 7 public class SimpleModel : INotifyPropertyChanged 8 { 9 protected string itsName; 10 protected string itsDescription; 11 12 public event PropertyChangedEventHandler PropertyChanged; 13 14 public string Name 15 { 16 get { return this.itsName; } 17 set { this.itsName = value; NotifyPropertyChanged("Name"); } 18 } 19 20 21 public string Description 22 { 23 get { return this.itsDescription; } 24 set { this.itsDescription = value; NotifyPropertyChanged("Description"); } 25 } 26 27 28 29 protected void NotifyPropertyChanged(string thePropertyName) 30 { 31 if (this.PropertyChanged != null) 32 { 33 this.PropertyChanged(this, new PropertyChangedEventArgs(thePropertyName)); 34 } 35 } 36 37 } 38 }
1 using System; 2 using System.ComponentModel; 3 using System.Collections.ObjectModel; 4 5 namespace TestLstBoxDemo 6 { 7 public class ListModel : INotifyPropertyChanged 8 { 9 public event PropertyChangedEventHandler PropertyChanged; 10 11 public ObservableCollection<SimpleModel> SimpleModels { get; private set; } 12 13 14 15 public bool IsDataLoaded { get; private set; } 16 17 public ListModel() 18 { 19 this.SimpleModels = new ObservableCollection<SimpleModel>(); 20 } 21 22 23 /// <summary> 24 /// 加载数据 25 /// </summary> 26 public void LoadData() 27 { 28 for (int i = 1; i < 30; i++) 29 { 30 this.SimpleModels.Add(new SimpleModel() { Name = "第" + i + "项", Description = "这是第" + i + "项数据" }); 31 } 32 this.IsDataLoaded = true; 33 } 34 35 36 protected void NotifyPropertyChanged(string thePropertyName) 37 { 38 if (this.PropertyChanged != null) 39 { 40 this.PropertyChanged(this, new PropertyChangedEventArgs(thePropertyName)); 41 } 42 } 43 44 } 45 }
这里使用了Terry_龙 的文章:【WP7进阶】——分享一个可供切换状态的ListBox组件中提到的小技巧,代码如下
1 <viewModels:ListModel 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:viewModels="clr-namespace:TestLstBoxDemo"> 5 6 <viewModels:ListModel.SimpleModels> 7 <viewModels:SimpleModel Name="测试第一项" Description="这是测试的第一个节点" /> 8 <viewModels:SimpleModel Name="测试第二项" Description="这是测试的第二个节点" /> 9 </viewModels:ListModel.SimpleModels> 10 11 </viewModels:ListModel>
完整代码如下:猛点击这里
效果预览: