C# ItemsControl中除第一个项目外的所有项目的统一网格作为面板模板
我正在做一个进度向导。我将其定义为基于ItemsControl的样式。我有一个ItemTemplateSelector和两个DataTemplates,一个用于第一个项目,另一个用于其余项目。我有它正常工作,除了一个非常小的问题,是超级棘手的修复。第一项和第二项之间有差距。 控件应该是这样的:出现间隙是因为我使用的是统一的网格,所以所有列的大小都相同,即使第一列没有线条。但是,使用统一的网格很重要,因为我希望所有内容都在一行上,并且希望控件在增长时拉伸以填充可用空间。我尝试过不使用统一的网格,但最终我要么在边距上有问题,要么没有填满可用的空间。我怎样才能弥补这个差距C# ItemsControl中除第一个项目外的所有项目的统一网格作为面板模板,c#,wpf,xaml,user-interface,itemscontrol,C#,Wpf,Xaml,User Interface,Itemscontrol,我正在做一个进度向导。我将其定义为基于ItemsControl的样式。我有一个ItemTemplateSelector和两个DataTemplates,一个用于第一个项目,另一个用于其余项目。我有它正常工作,除了一个非常小的问题,是超级棘手的修复。第一项和第二项之间有差距。 控件应该是这样的:出现间隙是因为我使用的是统一的网格,所以所有列的大小都相同,即使第一列没有线条。但是,使用统一的网格很重要,因为我希望所有内容都在一行上,并且希望控件在增长时拉伸以填充可用空间。我尝试过不使用统一的网格,但
代码如下:
<Style x:Key="WizardProgressBar" TargetType="{x:Type ItemsControl}">
<Style.Resources>
<DataTemplate x:Key="FirstItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse Name="ellipse" HorizontalAlignment="Left" Height="32" Width="32" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Completed}" Value="False">
<Setter TargetName="ellipse" Property="Stroke" Value="{DynamicResource DisabledBrush}" />
</DataTrigger>
<DataTrigger Binding="{Binding InProgress}" Value="True">
<Setter TargetName="ellipse" Property="Stroke" Value="{DynamicResource PrimaryTextBrush}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="OtherItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse Name="ellipse" Grid.Column="1" HorizontalAlignment="Left" Height="32" Width="32" />
<Line Name="leftPath" Grid.Column="0" X1="0" Y1="16"
X2="{Binding ActualWidth, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" Y2="16" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Completed}" Value="False">
<Setter TargetName="ellipse" Property="Stroke" Value="{DynamicResource DisabledBrush}" />
<Setter TargetName="leftPath" Property="Stroke" Value="{DynamicResource DisabledBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding InProgress}" Value="True">
<Setter TargetName="ellipse" Property="Stroke" Value="{DynamicResource PrimaryTextBrush}"/>
<Setter TargetName="leftPath" Property="Stroke" Value="{DynamicResource PrimaryTextBrush}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Style.Resources>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplateSelector">
<Setter.Value>
<wpf:ItemsDataTemplateSelector FirstItem="{StaticResource FirstItem}" OtherItem="{StaticResource OtherItem}" />
</Setter.Value>
</Setter>
</Style>
一个快速的解决方案是从ItemsControl中删除第一个
<Grid VerticalAlignment="Top">
<Grid.Resources>
<Style x:Key="WizardProgressBar" TargetType="{x:Type ItemsControl}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse Name="ellipse" Grid.Column="1" HorizontalAlignment="Left" Height="32" Width="32" />
<Line Name="leftPath" Grid.Column="0" X1="0" Y1="16"
X2="{Binding ActualWidth, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" Y2="16" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Completed}" Value="False">
<Setter TargetName="ellipse" Property="Stroke" Value="Gray" />
<Setter TargetName="leftPath" Property="Stroke" Value="Gray"/>
</DataTrigger>
<DataTrigger Binding="{Binding InProgress}" Value="True">
<Setter TargetName="ellipse" Property="Stroke" Value="Black"/>
<Setter TargetName="leftPath" Property="Stroke" Value="Black"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Name="ellipse" HorizontalAlignment="Left" Height="32" Width="32" Stroke="Black"/>
<ItemsControl Grid.Column="1" Style="{StaticResource WizardProgressBar}"
x:Name="otherItemsGrid">
</ItemsControl>
</Grid>
主要问题是,与其他项目相比,设置中的第一个项目实际上必须具有不同的宽度。对于
UniformGrid
,这是不可能的
我可以向你推荐以下解决方案
目标配置如下所示:
|··O––|––O––|––O––|––O··|
左边和右边各有一个半单元格宽的边距(由上面的点表示)。如果需要,可以通过指定控件的边距来利用它们
此外,我们还可以简化您的数据模板。实际上,我们不需要为第一项和其他项使用单独的模板。见下文
安装程序
我们将在这里使用三个技巧:
- 使用
属性获取ItemsControl.AlternationIndex
ItemsControl
- 一个
,允许在相应的Canvas
单元格外部绘制连接线UniforGrid
- 一个特殊的
,它将帮助我们计算所需的线路位置IValueConverter
项控件的样式:
<Style x:Key="WizardProgressBar" TargetType="{x:Type ItemsControl}">
<Style.Resources>
<local:LinearConverter x:Key="Multiplier" Scale="-0.5" Offset="16"/>
<DataTemplate DataType="{x:Type local:YourItemType}">
<Grid>
<Canvas>
<Rectangle x:Name="leftPath" Height="2" Stroke="Blue" Canvas.Top="16"
Canvas.Left="{Binding Width, RelativeSource={RelativeSource Self}, Converter={StaticResource Multiplier}}"
Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}"/>
</Canvas>
<Ellipse Name="ellipse" HorizontalAlignment="Center" Height="32" Width="32" Stroke="Blue"/>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter TargetName="leftPath" Property="Visibility" Value="Collapsed"/>
</Trigger>
<DataTrigger Binding="{Binding Completed}" Value="False">
<Setter TargetName="ellipse" Property="Stroke" Value="{DynamicResource DisabledBrush}" />
<Setter TargetName="leftPath" Property="Stroke" Value="{DynamicResource DisabledBrush}" />
</DataTrigger>
<DataTrigger Binding="{Binding InProgress}" Value="True">
<Setter TargetName="ellipse" Property="Stroke" Value="{DynamicResource PrimaryTextBrush}"/>
<Setter TargetName="leftPath" Property="Stroke" Value="{DynamicResource PrimaryTextBrush}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Style.Resources>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Rows="1"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="AlternationCount" Value="100"/>
</Style>
解释
每个椭圆
表示一个向导页面,并放置在相应的UniformGrid
单元格的中心。这些线放置在椭圆的左侧。行的宽度设置为UnifiormGrid
的单个单元格的宽度,它们在画布中的水平位置根据以下公式设置:WidthOfEllipse/2-widthofecell/2
。这确保了正确的位置
对于第一个向导页面,该行将被隐藏
请注意,您可能希望使用省略号的Fill
来隐藏底层行。引用Steve Jobs的话:“这不仅仅是它的外观和感觉。设计是它的工作方式。”
通过将前一个节点、中间路径和下一个节点放置在一个网格单元上,我们可以可视化过程中的一个步骤。下面是五个单元格,以及用户必须完成的五个相应阶段
___________________________________________________________
| | | | | |
O - - - - - O - - - - - O - - - - - O - - - - - O - - - - - O
|___________|___________|___________|___________|___________|
中间路径完成后,上一个节点已完成(完全绘制)。完成后,还可以完全绘制路径和下一个节点
这种方法的优点在于它的简单性,并且没有特殊情况或例外。无论是在视图中还是在其背后的模型中,每个阶段的处理都完全相同
第一个节点是我们开始的地方(前进到第二个节点)。想一想,你不想从tin air开始,而是从链中的第一个节点开始,然后移动到下一个节点。如果有五个进程,将有六个节点
就您引用的笔刷而言:
Default In progress Completed
Left node DisabledBrush PrimaryTextBrush PrimaryTextBrush
Intermediate path DisabledBrush DisabledBrush PrimaryTextBrush
Right node DisabledBrush DisabledBrush PrimaryTextBrush
唯一的缺点是,除了第一个和最后一个节点外,每个节点都要绘制两次。这不能正确形成我需要创建的控件。我对描述进行了编辑,以使我的需求更加清晰。UniformGrid在这里不是一个合适的ItemsPanel,因为它的行为不是您想要的(它没有“除第一个之外”的概念)。但是,您可以编写自己的面板类。请注意,您不需要DataTemplateSelector。相反,添加一个ItemContainerStyle(对于ContentPresenter),它将ContentTemplate属性设置为OtherItem,并且在ItemsControl.AlternationalIndex上有一个触发器,当触发器值为零时,它将ContentTemplate设置为FirstItem。在ItemsControl上将AlternationCount设置为一个高值(如2147483647)。此解决方案非常有用。我在考虑做这样的事情。您知道有什么方法可以让我删除或隐藏左侧和右侧的半单元格宽边距吗?@user2481095,正如我在回答中提到的,您可以使用margin
属性来利用这些不需要的空闲空间。将ItemControl
的Margin
设置为左右负偏移量,类似于Margin=“-50,0,-50,0”
。如果需要,您甚至可以直接在样式中设置此属性,并动态计算此边距
___________________________________________________________
| | | | | |
O - - - - - O - - - - - O - - - - - O - - - - - O - - - - - O
|___________|___________|___________|___________|___________|
Default In progress Completed
Left node DisabledBrush PrimaryTextBrush PrimaryTextBrush
Intermediate path DisabledBrush DisabledBrush PrimaryTextBrush
Right node DisabledBrush DisabledBrush PrimaryTextBrush