WPF ListBox的进阶使用(二)
项目中经常使用需要根据搜索条件查询数据,然后用卡片来展示数据。用卡片展示数据时,界面的宽度发生变化,希望显示的卡片数量也跟随变化。WrapPanel虽然也可以实现这个功能,但是将多余的部分都留在行尾,十分不美观,最好是能够将多余的宽度平分在每个ListBoxItem之间,比较美观,也符合项目需求。如下便是我自己实现的Panel:
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 9 namespace WpfDemo 10 { 11 public class MyWrapPanel : Panel 12 { 13 protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) 14 { 15 Size currentLineSize = new Size(); 16 Size panelSize = new Size(); 17 18 foreach (UIElement element in base.InternalChildren) 19 { 20 element.Measure(availableSize); 21 Size desiredSize = element.DesiredSize; 22 23 if (currentLineSize.Width + desiredSize.Width > availableSize.Width) 24 { 25 panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width); 26 panelSize.Height += currentLineSize.Height; 27 currentLineSize = desiredSize; 28 29 if (desiredSize.Width > availableSize.Width) 30 { 31 panelSize.Width = Math.Max(desiredSize.Width, panelSize.Width); 32 panelSize.Height += desiredSize.Height; 33 currentLineSize = new Size(); 34 } 35 } 36 else 37 { 38 currentLineSize.Width += desiredSize.Width; 39 currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height); 40 } 41 } 42 43 panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width); 44 panelSize.Height += currentLineSize.Height; 45 46 return panelSize; 47 } 48 49 protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) 50 { 51 int firstInLine = 0; 52 int lineCount = 0; 53 54 Size currentLineSize = new Size(); 55 56 double accumulatedHeight = 0; 57 58 UIElementCollection elements = base.InternalChildren; 59 double interval = 0.0; 60 for (int i = 0; i < elements.Count; i++) 61 { 62 63 Size desiredSize = elements[i].DesiredSize; 64 65 if (currentLineSize.Width + desiredSize.Width > finalSize.Width) //need to switch to another line 66 { 67 interval = (finalSize.Width - currentLineSize.Width) / (i - firstInLine + 2); 68 arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, i, interval); 69 70 accumulatedHeight += currentLineSize.Height; 71 currentLineSize = desiredSize; 72 73 if (desiredSize.Width > finalSize.Width) //the element is wider then the constraint - give it a separate line 74 { 75 arrangeLine(accumulatedHeight, desiredSize.Height, i, ++i, 0); 76 accumulatedHeight += desiredSize.Height; 77 currentLineSize = new Size(); 78 } 79 firstInLine = i; 80 lineCount++; 81 } 82 else //continue to accumulate a line 83 { 84 currentLineSize.Width += desiredSize.Width; 85 currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height); 86 } 87 } 88 89 if (firstInLine < elements.Count) 90 { 91 if (lineCount == 0) 92 { 93 interval = (finalSize.Width - currentLineSize.Width) / (elements.Count - firstInLine + 1); 94 } 95 arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, elements.Count, interval); 96 } 97 98 99 return finalSize; 100 } 101 102 private void arrangeLine(double y, double lineHeight, int start, int end, double interval) 103 { 104 double x = 0; 105 UIElementCollection children = InternalChildren; 106 for (int i = start; i < end; i++) 107 { 108 x += interval; 109 UIElement child = children[i]; 110 child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineHeight)); 111 x += child.DesiredSize.Width; 112 } 113 } 114 } 115 }
接下来,便是将这个MyWrapPanel作为ListBox的ItemsPanelTemplate即可:
1 <Window x:Class="WpfDemo.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:comm="clr-namespace:WpfDemo.CommonControls;assembly=WpfDemo.CommonControls" 5 xmlns:local="clr-namespace:WpfDemo" 6 Title="MainWindow" Height="350" Width="525"> 7 8 <Grid> 9 <ListBox ItemsSource="{Binding DataSource}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" 10 VerticalAlignment="Center" BorderThickness="0"> 11 <ListBox.ItemsPanel> 12 <ItemsPanelTemplate> 13 <local:MyWrapPanel IsItemsHost="True"/> 14 </ItemsPanelTemplate> 15 </ListBox.ItemsPanel> 16 <ListBox.ItemContainerStyle> 17 <Style TargetType="{x:Type ListBoxItem}"> 18 <Setter Property="Template"> 19 <Setter.Value> 20 <ControlTemplate TargetType="{x:Type ListBoxItem}"> 21 <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Green" BorderBrush="Yellow" BorderThickness="1"> 22 <TextBlock Text="{Binding CameraName}" Width="100" HorizontalAlignment="Center" VerticalAlignment="Center"/> 23 </Border> 24 </ControlTemplate> 25 </Setter.Value> 26 </Setter> 27 </Style> 28 </ListBox.ItemContainerStyle> 29 <ListBox.Style> 30 <Style TargetType="{x:Type ListBox}"> 31 32 </Style> 33 </ListBox.Style> 34 </ListBox> 35 </Grid> 36 </Window>
界面对应的ViewModel:
1 using System; 2 using System.Collections.Generic; 3 using System.Collections.ObjectModel; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using System.Windows.Threading; 8 9 namespace WpfDemo 10 { 11 public class MainWindowVM : NotifyPropertyBase 12 { 13 private DispatcherTimer timer; 14 public MainWindowVM() 15 { 16 DataSource = new ObservableCollection<WndViewModel>(); 17 Colums = 1; 18 for(int i =0; i < 60; ++i) 19 { 20 var temp = new WndViewModel() 21 { 22 CameraName = string.Format("Camera {0}", ++count), 23 }; 24 DataSource.Add(temp); 25 } 26 //timer = new DispatcherTimer(); 27 //timer.Interval = new TimeSpan(0, 0, 1); 28 //timer.Tick += timer_Tick; 29 //timer.Start(); 30 } 31 32 private int count = 0; 33 void timer_Tick(object sender, EventArgs e) 34 { 35 var temp = new WndViewModel() 36 { 37 CameraName = string.Format("Camera {0}", ++count), 38 }; 39 DataSource.Add(temp); 40 Console.WriteLine(temp.CameraName); 41 if (count <= 6) 42 { 43 Colums = count; 44 } 45 else if (count > 100) 46 { 47 count = 0; 48 DataSource.Clear(); 49 Colums = 1; 50 } 51 } 52 53 private int colums; 54 public int Colums 55 { 56 get { return colums; } 57 set 58 { 59 SetProperty(ref colums, value); 60 } 61 } 62 63 private ObservableCollection<WndViewModel> dataSource; 64 public ObservableCollection<WndViewModel> DataSource 65 { 66 get { return dataSource; } 67 set 68 { 69 SetProperty(ref dataSource, value); 70 } 71 } 72 } 73 }
运行结果:
拉伸后: