WPF中ListBox的总结
背景
在很多时候我们都需要通过一个列表进行数据展示,而且通常需要对其中的每一个项都具有点击的效果,这个时候ListBox就是一个非常理想的选择,我们可以根据我们的需要对这个ListBox进行各种定制以达到我们需要的各种效果,今天我就这个控件的时候来做一个全面的梳理和讲解。
重点内容
说道这类控件我们用到使用到最多的就是定义控件的ItemsPanel、ItemContainerStyle、ItemTemplate、ListBoxItem这些内容,下面我就这些内容来一一进行分析从而让我们对整个控件有一个更加清晰和系统的理解。
一 定义ItemsPanel
定义ItemsPanel主要是用于定义当前ListBox内部Items到底使用什么类型的容器进行承载,我们来看看下面的例子,我们将使用一个StackPanel作为项的布局的面板。
<Style TargetType="ListBox"> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"/> </ItemsPanelTemplate> </Setter.Value> </Setter> </Style>
在定义ItemsPanel的时候我们有时候可以在定义ListBox的ControlTemplate的时候一步到位去进行定义,比如使用下面的方式。
<Style TargetType="ListBox"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBox"> <Border CornerRadius="5" Background="{TemplateBinding ListBox.Background}"> <ScrollViewer HorizontalScrollBarVisibility="Auto"> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" IsItemsHost="True"/> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
通过直接在ControlTemplate中设置某一个容器的IsItemsHost属性来一步到位设置具体的ItemsPanel,其实上面的代码类似于下面的效果。
<Style TargetType="{x:Type ListBox}"> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"/> </ItemsPanelTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBox}"> <Border CornerRadius="5" Background="{TemplateBinding ListBox.Background}"> <ScrollViewer HorizontalScrollBarVisibility="Auto"> <ItemsPresenter/> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
二 定义ItemContainerStyle
这个属性官方的解释是:获取或设置应用于为每个项生成的容器元素的 Style,也就是为具体的每一个ListBoxItem设置一些统一的外观表现,比如下面的代码将为ListBox的每一个Item定义固定的宽度、高度以及一个简单鼠标移动上去设置ToolTip的触发器。
<ListBox.ItemContainerStyle> <Style> <Setter Property="Control.Width" Value="100"/> <Setter Property="Control.Margin" Value="5"/> <Style.Triggers> <Trigger Property="Control.IsMouseOver" Value="True"> <Setter Property="Control.ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Content.Description}"/> </Trigger> </Style.Triggers> </Style> </ListBox.ItemContainerStyle>
三 定义ListBoxItem样式
ListBoxItem是 ContentControl ,这意味着它可以包含任何类型的单个对象 (例如字符串、图像或面板) ,在这里我们定义一个ListBoxItem的样式,用来显示相应的图片信息
<Style TargetType="{x:Type ListBoxItem}" > <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border x:Name="border" Height="64" Width="64" Margin="5,1,5,1" BorderThickness="2" VerticalAlignment="Center" Background="Aqua"> <Image Margin="5" Cursor="Hand" Stretch="Fill" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding Photo}" ToolTip="{Binding Name}"> </Image> </Border> <ControlTemplate.Triggers> <Trigger Property="IsFocused" Value="true" > <Setter Property="BorderBrush" Value="Beige" TargetName="border"/> </Trigger> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="BorderBrush" Value="BlanchedAlmond" TargetName="border"/> </Trigger> <Trigger Property="IsSelected" Value="true"> <Setter Property="BorderBrush" Value="BurlyWood" TargetName="border"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
不能上面定义了Key而下面不使用ItemContainerStyle来进行引用,这样的话最终是不会为ListBoxItem添加样式的,所以必须成双成对的引用,这点我们需要注意,以上是自己使用的一点点心得体会。
另外关于ListBoxItem,常需要注意的是我们更多的时候是定义ListBox的ItemTemplate的,这个属性是用于获取或设置用于显示每个项的DataTemplate。这个属性是从ItemsControl继承而来的,这个需要注意,下面举一个具体的例子。
<ListBox.ItemTemplate> <DataTemplate> <Border Margin="2" Padding="2" CornerRadius="2" Background="White"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <Label Grid.Column="0" VerticalAlignment="Center" Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplatedParent.(ItemsControl.AlternationIndex),Converter={StaticResource convMath},ConverterParameter=x+1}"> <Label.Tag> <MultiBinding Converter="{StaticResource RecipeStepIndexConverter}"> <Binding Path="."></Binding> <Binding Path="TemplatedParent.(ItemsControl.AlternationIndex)" RelativeSource="{RelativeSource TemplatedParent}"></Binding> </MultiBinding> </Label.Tag> </Label> <Label Grid.Column="1" Margin="2 1" Content="StepName" FontSize="11" DockPanel.Dock="Left" Height="24" HorizontalAlignment="Center" VerticalAlignment="Center" /> <TextBox Grid.Column="2" Margin="2 1" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" ToolTip="{Binding Name}" Width="{Binding ActualWidth,ElementName=recipeList,Converter={StaticResource convMath}, ConverterParameter=x-440}" Height="24"> <TextBox.Text> <Binding Path="Name" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <validator:InputStringEmptyValidation ValidatesOnTargetUpdated="True"></validator:InputStringEmptyValidation> </Binding.ValidationRules> </Binding> </TextBox.Text> <Validation.ErrorTemplate> <ControlTemplate> <Border BorderBrush="Red" BorderThickness="1" Width="{Binding ActualWidth,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox} ,Converter={StaticResource convMath}, ConverterParameter=x-440}" Height="24"> <!-- Placeholder for the TextBox itself --> <Label Content="{Binding [0].ErrorContent}" Foreground="Red" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="10"/> </Border> </ControlTemplate> </Validation.ErrorTemplate> </TextBox> <Label Grid.Column="3" Margin="2 1" Content="PlaceRobotId" FontSize="11" DockPanel.Dock="Left" Height="24" HorizontalAlignment="Center" VerticalAlignment="Center" ToolTip="Select which robot to place wafer,it's a digital number"/> <TextBox Grid.Column="4" Margin="2 1" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" ToolTip="{Binding PlaceRobotSequenceId}" Text="{Binding PlaceRobotSequenceId}" Width="64" Height="24"> </TextBox> <Label Grid.Column="5" Margin="2 1" Content="PickRobotId" FontSize="11" DockPanel.Dock="Left" Height="24" HorizontalAlignment="Center" VerticalAlignment="Center" ToolTip="Select which robot to pick wafer,it's a digital number"/> <TextBox Grid.Column="6" Margin="2 1" HorizontalAlignment="Stretch" VerticalContentAlignment="Center" ToolTip="{Binding PlaceRobotSequenceId}" Text="{Binding PickRobotSequenceId}" Width="64" Height="24"> </TextBox> <Button Grid.Column="7" Margin="1" Width="24" Height="24" ToolTip="Delete RecipeStep" Command="{Binding DataContext.DeleteRecipeStep,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}" CommandParameter="{Binding DataContext,RelativeSource={RelativeSource Self}}"> <Button.Template> <ControlTemplate TargetType="Button"> <Border x:Name="Border" Padding="2" Background="#FAFAFA" BorderBrush="Gray" BorderThickness="1"> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"></ContentPresenter> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Background" Value="Red" TargetName="Border"></Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Button.Template> <Button.Content> <Path Data="M 0,0 8,8 M 8,0 0,8" HorizontalAlignment="Center" VerticalAlignment="Center" Stroke="#b1c1ce" StrokeThickness="1"></Path> </Button.Content> </Button> </Grid> </Border> </DataTemplate> </ListBox.ItemTemplate>
这段示例代码中有几个地方需要注意的是,首先看下面的一部分。
<Label Grid.Column="0" VerticalAlignment="Center" Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplatedParent.(ItemsControl.AlternationIndex),Converter={StaticResource convMath},ConverterParameter=x+1}"/>
这个是非常常见的一部分,就是我们需要显示每一个ListBoxItem的序号,这里我们绑定的Path是TemplatedParent.(ItemsControl.AlternationIndex),这里定义在DataTemplate里面的元素获取到的TemplatedParent是一个ContentPresenter,这个可以具体调试代码知道,这里需要注意的是使用了ItemsControl.AlternationIndex的时候,一定要在ListBox中绑定AlternationCount属性否则这个是不能够生效的,绑定的方式如下:其中ActiveRecipe是我们定义在DataContext的一个数据对象。
AlternationCount="{Binding Path=ActiveRecipe.Steps.Count}"
另外在每一个Item项后面增加一个删除的按钮也是一个非常常规的操作,这个在使用的时候需要特别注意。