自适应XAML布局经验总结 (三) 局部布局设计模式2
本系列对实际项目中的XAML布局场景进行总结,给出了较优化的自适应布局解决方案,希望对大家有所帮助。
下面继续介绍局部布局设计模式。
5. 工具箱模式
绘图,三维模型操作等需要工具的情况,可以使用带分类的工具箱来放置工具,以达到较好的效果。实现工具箱效果的方法是使用ItemsControl的分组功能,并修改样式和布局。
首先,在最外层放置一个横向滚动条为Auto的ScrollViewer。里面放一个ItemsControl,修改布局为UniformGrid,分两行。ItemTemplate为Button,设置合适的背景色和固定长度宽度(和字体字号相关),并设置左和上的Margin。Button的Content为TextBlock,设置文字折行和截断,并设置横向和纵向对齐方式为居中。TextBlock可设置合适的ToolTip,可使用转换器控制只在文字多的时候显示。
然后,修改ItemTemplate的GroupStyle,GroupStyle的Panel为横向的StackPanel,ContainerStyle的Template放置一个横向的StackPanel,分别放一个Rectangle做为分割竖线和一个Grid放置真正的内容。Rectangle使用转换器控制在第一个位置时不显示。Grid分为两行,第0行为Auto,第1行为*,第0行为一个放在Border里的TextBlock,设置Margin和居中对齐。第1行是ItemsPresenter,设置Margin的右和下,以和前面呼应。
最后,绑定到合适的数据源,数据源为CollectionViewSource,其Source属性为一个数据集合,数据包含ClassName和Name属性,ClassName是分类名称,Name是条目名称。在CollectionViewSource的GroupDescriptions属性中加入名为ClassName的PropertyGroupDescription。在XAML中设置合适的数据绑定即可。
<Window x:Class="BlendDemo.DP5" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:BlendDemo" mc:Ignorable="d" Title="工具箱模式" Height="400" Width="500"> <Window.Resources> <local:SeparatorVisibilityConverter x:Key="SeparatorVisibilityConverter"/> <local:LengthVisibilityConverter x:Key="LengthVisibilityConverter"/> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Border Background="LightCyan"> <TextBlock Margin="5" Text="此处为标题" TextTrimming="WordEllipsis"/> </Border> <Grid Grid.Row="1" Background="AliceBlue"/> <Grid Grid.Row="2"> <ScrollViewer HorizontalScrollBarVisibility="Auto"> <ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Rows="2"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Button Background="AliceBlue" Width="70" MaxHeight="40" Margin="5,5,0,0"> <TextBlock Text="{Binding Name}" TextWrapping="Wrap" TextTrimming="WordEllipsis" HorizontalAlignment="Center" VerticalAlignment="Center" ToolTipService.Placement="Bottom" ToolTipService.InitialShowDelay="0" ToolTipService.BetweenShowDelay="0"> <TextBlock.ToolTip> <ToolTip Content="{Binding Name}" Visibility="{Binding Path=Name, Converter={StaticResource LengthVisibilityConverter}, ConverterParameter=10}"/> </TextBlock.ToolTip> </TextBlock> </Button> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.GroupStyle> <GroupStyle> <GroupStyle.Panel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal" /> </ItemsPanelTemplate> </GroupStyle.Panel> <GroupStyle.ContainerStyle> <Style TargetType="GroupItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <StackPanel Orientation="Horizontal"> <Rectangle Fill="#9ca1ae" Width="2" Visibility="{Binding Path=Name, Converter={StaticResource SeparatorVisibilityConverter}}"/> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Border Background="#e6e9f2"> <TextBlock Text="{Binding Name}" Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <ItemsPresenter Grid.Row="1" Margin="0,0,5,5"/> </Grid> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> </GroupStyle.ContainerStyle> </GroupStyle> </ItemsControl.GroupStyle> </ItemsControl> </ScrollViewer> </Grid> </Grid> </Window>
public class SeparatorVisibilityConverter : IValueConverter { public IList Data { get; set; } public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (Data == null || Data.Count == 0) { return Visibility.Collapsed; } dynamic item = Data[0]; return ((string)value) == item.ClassName ? Visibility.Collapsed : Visibility.Visible; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value; } } public class LengthVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value == null) { return Visibility.Collapsed; } return ((string)value).Length < int.Parse((string)parameter) ? Visibility.Collapsed : Visibility.Visible; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return value; } } public class DataItem { public string ClassName { get; set; } public string Name { get; set; } } public partial class DP5 : Window { public DP5() { InitializeComponent(); var cvs = new CollectionViewSource(); cvs.Source = new List<DataItem> { new DataItem { ClassName="分类1",Name="条目1.1"}, new DataItem { ClassName="分类1",Name="条目1.2"}, new DataItem { ClassName="分类1",Name="条目1.3为长条目"}, new DataItem { ClassName="分类1",Name="条目1.4"}, new DataItem { ClassName="分类1",Name="条目1.5"}, new DataItem { ClassName="分类2",Name="条目2.1"}, new DataItem { ClassName="分类2",Name="条目2.2"}, new DataItem { ClassName="分类2",Name="条目2.3"}, new DataItem { ClassName="分类2",Name="条目2.4"}, new DataItem { ClassName="分类2",Name="条目2.5"}, new DataItem { ClassName="分类2",Name="条目2.6为长条目长条目长条目长条目"}, new DataItem { ClassName="分类3",Name="条目3.1"}, new DataItem { ClassName="分类3",Name="条目3.2"}, new DataItem { ClassName="分类3",Name="条目3.3"}, new DataItem { ClassName="分类3",Name="条目3.4"}, }; cvs.GroupDescriptions.Add(new PropertyGroupDescription("ClassName")); DataContext = cvs; ((SeparatorVisibilityConverter)FindResource("SeparatorVisibilityConverter")).Data = (IList)cvs.Source; } }
6. 选择题模式
制作选择题考试界面时,需要达到纸质选择题考试的效果。选项的排列方式分几种情况,选项文字少的时候排在一行上,文字不太多的时候分两列排,文字较多的时候按一列排。
首先,构造合适的数据结构,并加入一些示例数据。编写一个模板选择器,用于根据选项的数据情况选择合适的显示模板。全部选项的文字都小于4个字时,使用折行选项模板。全部选项的文字都小于10个字时,使用两列选项模板。其他情况使用一列选项模板。
然后,分别编写模板。折行选项模板使用ItemsControl,布局改为WrapPanel,数据模板使用StackPanel,其中放置序号,点和内容。两列选项模板使用ItemsControl,布局改为UniformGrid,列数为2,数据模板使用Grid,分为3列,前2列为Auto,放置序号和点,最后1列为*,放置显示Content的TextBlock,并设置折行。一列选项模板使用ItemsControl,不修改布局,数据模板和两列的情况相同。
最后,选择题的整体显示使用放置在ScrollViewer中的ItemsControl,不修改布局。数据模板使用StackPanel,首先放置一个Grid,分为3列,前2列为Auto,放置序号和点,最后1列为*,放置显示问题的TextBlock,并设置折行。下面放一个ContentControl,Content绑定到合适的数据,并设置ContentTemplateSelector为前文所述的模板选择器。
<Window x:Class="BlendDemo.DP6" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:BlendDemo" mc:Ignorable="d" Title="选择题模式" Height="400" Width="650"> <Window.Resources> <DataTemplate x:Key="WrapOptionTemplate"> <ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="0,4,70,4"> <TextBlock Text="{Binding Index}"/> <TextBlock Text="." Margin="3,0,3,0"/> <TextBlock Text="{Binding Content}" TextWrapping="Wrap"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <WrapPanel/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </DataTemplate> <DataTemplate x:Key="TwoColumnOptionTemplate"> <ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="0,4,0,4"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Index}"/> <TextBlock Grid.Column="1" Text="." Margin="3,0,3,0"/> <TextBlock Grid.Column="2" Text="{Binding Content}" TextWrapping="Wrap"/> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Rows="2"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </DataTemplate> <DataTemplate x:Key="OneColumnOptionTemplate"> <ItemsControl ItemsSource="{Binding}"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="0,4,0,4"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Index}"/> <TextBlock Grid.Column="1" Text="." Margin="3,0,3,0"/> <TextBlock Grid.Column="2" Text="{Binding Content}" TextWrapping="Wrap"/> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </DataTemplate> </Window.Resources> <Grid> <ScrollViewer VerticalScrollBarVisibility="Auto"> <ItemsControl ItemsSource="{Binding}" Margin="20,20,20,3"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Margin="0,0,0,7"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Index}" TextWrapping="Wrap"/> <TextBlock Grid.Column="1" Text="." TextWrapping="Wrap" Margin="0,0,5,0"/> <TextBlock Grid.Column="2" TextWrapping="Wrap" Text="{Binding Question}"/> </Grid> <ContentControl Content="{Binding OptionList}" Margin="33,10,33,10"> <ContentControl.ContentTemplateSelector> <local:OptionTemplateSelector/> </ContentControl.ContentTemplateSelector> </ContentControl> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Grid> </Window>
public class OptionItem { public string Index { get; set; } public string Content { get; set; } } public class ObjectiveTestItem { public int Index { get; set; } public string Question { get; set; } public List<OptionItem> OptionList { get; set; } } public class OptionTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { var element = container as FrameworkElement; if (element == null) return null; var optionAnswerItems = item as List<OptionItem>; if (optionAnswerItems == null) return null; if (optionAnswerItems.All(i => i.Content.Length <= 4)) { return element.FindResource("WrapOptionTemplate") as DataTemplate; } if (optionAnswerItems.All(i => i.Content.Length <= 10)) { return element.FindResource("TwoColumnOptionTemplate") as DataTemplate; } return element.FindResource("OneColumnOptionTemplate") as DataTemplate; } } public partial class DP6 : Window { public DP6() { InitializeComponent(); var data = new List<ObjectiveTestItem>(); var item1 = new ObjectiveTestItem { Index = 1, Question = "短的选项", OptionList = new List<OptionItem> { new OptionItem { Index="A",Content="选项1" }, new OptionItem { Index="B",Content="选项2" }, new OptionItem { Index="C",Content="选项2" }, new OptionItem { Index="D",Content="选项2" } } }; data.Add(item1); var item2 = new ObjectiveTestItem { Index = 1, Question = "中等长度的选项", OptionList = new List<OptionItem> { new OptionItem { Index="A",Content="中等长度的选项1" }, new OptionItem { Index="B",Content="选项2" }, new OptionItem { Index="C",Content="选项2" }, new OptionItem { Index="D",Content="选项2" } } }; data.Add(item2); var item3 = new ObjectiveTestItem { Index = 1, Question = "长的选项长的选项长的选项长的选项长的选项长的选项长的选项长的选项长的选项长的选项长的选项长的选项长的选项", OptionList = new List<OptionItem> { new OptionItem { Index="A",Content="长的选项长的选项长的选项长的选项长的选项长的选项长的选项1" }, new OptionItem { Index="B",Content="选项2" }, new OptionItem { Index="C",Content="选项2" }, new OptionItem { Index="D",Content="选项2" } } }; data.Add(item3); DataContext = data; } }