WPF中如何为ItemsControl添加ScrollViewer并显示ScrollBar

    今天在开发的过程中突然碰到了一个问题,本来的意图是想当ItemsControl中加载的Item达到一定数量时,会出现ScrollViewer并出现垂直的滚动条,但是实际上并不能够达成目标,对于熟手来说这个问题非常简单,但是如果不了解WPF的模板的原理,可能并不清楚这些,这里举出一个例子来论证。

<Window x:Class="TestItemsControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="ItemsControlStyle1" TargetType="{x:Type ItemsControl}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ItemsControl}">
                        <Border BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                Background="{TemplateBinding Background}" 
                                Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
                                <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <ItemsControl Width="100" 
                      Height="100" 
                      Background="Teal"
                      Style="{DynamicResource ItemsControlStyle1}">
                <TextBox Text="1" Foreground="Red" Height="20" TextAlignment="Center"></TextBox>
                <TextBox Text="1" Foreground="Red" Height="20" TextAlignment="Center"></TextBox>
              <TextBox Text="2" Foreground="Red" Height="20" TextAlignment="Center"></TextBox>
              <TextBox Text="3" Foreground="Red" Height="20" TextAlignment="Center"></TextBox>
              <TextBox Text="4" Foreground="Red" Height="20" TextAlignment="Center"></TextBox>
              <TextBox Text="5" Foreground="Red" Height="20" TextAlignment="Center"></TextBox>
              <TextBox Text="6" Foreground="Red" Height="20" TextAlignment="Center"></TextBox>
        </ItemsControl>
    </Grid>
</Window>

  执行上述代码我们会发现不会出现ScrollBar,我们定义了ItemsControl的高度为100,当下面的Item超过了这个高度后多出的部分直接被剪切掉了,通过查看Window.Resources中的模板,那么我们可以很好理解,因为ItemsControl的结构是一个Border里面嵌套了一个ItemsPresenter,根本么有ScrollViewer,所以当然不会出现ScrollBar。这个问题非常好解决,直接修改ItemsControl的模板,在Border里面加上一个ScrollViewer,问题解决。

 <Style x:Key="ItemsControlStyle1" TargetType="{x:Type ItemsControl}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ItemsControl}">
                        <Border BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                Background="{TemplateBinding Background}" 
                                Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
                            <ScrollViewer>
                                <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            </ScrollViewer>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

  效果如下所示:

    下面来一步步深入,讨论一些复杂的问题。

    在进行最终的问题解释之前,首先来探讨一下ItemsControl这个控件。

    MSDN的解释是:表示用于呈现的项的集合的控件。

    再看一下它的集成关系:

   System.Object
  System.Windows.Threading.DispatcherObject
    System.Windows.DependencyObject
      System.Windows.Media.Visual
        System.Windows.UIElement
          System.Windows.FrameworkElement
            System.Windows.Controls.Control
              System.Windows.Controls.ItemsControl
                System.Windows.Controls.HeaderedItemsControl
                System.Windows.Controls.Primitives.DataGridCellsPresenter
                System.Windows.Controls.Primitives.DataGridColumnHeadersPresenter
                System.Windows.Controls.Primitives.MenuBase
                System.Windows.Controls.Primitives.Selector
                System.Windows.Controls.Primitives.StatusBar
                System.Windows.Controls.Ribbon.RibbonContextualTabGroupItemsControl
                System.Windows.Controls.Ribbon.RibbonControlGroup
                System.Windows.Controls.Ribbon.RibbonGallery
                System.Windows.Controls.Ribbon.RibbonQuickAccessToolBar
                System.Windows.Controls.Ribbon.RibbonTabHeaderItemsControl
                System.Windows.Controls.TreeView

    通过这些继承关系,我们可以发现ItemsControl是很多包含Items的集合的控件的基类,比如ListBox还有TreeView等等。

    关于ItemsControl中有几个非常重要的概念需要理解:

   1 Template 这个不用说ItemsControl的模板,用于展现ItemsControl最终由什么构成,即外表呈现。   

<ItemsControl.Template>
                                <ControlTemplate TargetType="ItemsControl">
                                    <ScrollViewer x:Name="scrollViewer"
                                                  VerticalScrollBarVisibility="Auto" 
                                                  Padding="5">
                                        <ItemsPresenter ></ItemsPresenter>
                                    </ScrollViewer>
                                </ControlTemplate>
                            </ItemsControl.Template>

  2 ItemsPanel属性,这个非常重要,这个是Items项的父容器,它决定了Items以何种方式去呈现,比如常用的Grid、 StackPanel、WrapPanel、UniformGrid、DockPanel等,甚至可以是自定义的Panel。

 <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel  Width="Auto"
                                                Height="Auto"
                                                MaxWidth="500"
                                                IsItemsHost="True"
                                                HorizontalAlignment="Left"
                                                VerticalAlignment="Center">
                                    </WrapPanel>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>

  3 ItemTemplate,这个属性表示每个Item将以何种方式呈现,有了这三种属性我们就可以定义我们需要的各种形式的界面。(下面的代码稍稍复杂一些)

<ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Grid Margin="0,0,10,3"
                                          HorizontalAlignment="Left"
                                          VerticalAlignment="Center">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="*"></ColumnDefinition>
                                            <ColumnDefinition Width="*"></ColumnDefinition>
                                        </Grid.ColumnDefinitions>
                                        <Grid.RowDefinitions>
                                            <RowDefinition Height="*"></RowDefinition>
                                            <RowDefinition Height="*"></RowDefinition>
                                        </Grid.RowDefinitions>
                                        <Border Name="DutyPerson" 
                                                Grid.Row="0"  
                                                Grid.RowSpan="2" 
                                                Grid.Column="0" 
                                                Grid.ColumnSpan="2" 
                                                BorderBrush="#bdbdbd"
                                                BorderThickness="1"      
                                                Padding="0"
                                                Width="70"
                                                Height="32"
                                                ContextMenu="{StaticResource SetLeader}">
                                            <StackPanel>
                                                <TextBox x:Name="dutyPersonTextBox"
                                                         Text="{Binding DutyPersonName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 
                                                         BorderThickness="0"
                                                         Height="30">
                                                    <TextBox.ToolTip>
                                                        <ToolTip HorizontalOffset="-18"  
                                                                 VerticalOffset="5"
                                                                 BorderBrush="Transparent"
                                                                 Background="Transparent"
                                                                 HasDropShadow="False"
                                                                 Placement="Top"
                                                                 Visibility="{Binding IsLeader,Converter={StaticResource BoolToVisibility}}">
                                                            <Grid Margin="0">
                                                                <Image x:Name="personToolTipImage" 
                                                                        Stretch="Uniform"
                                                                        RenderOptions.BitmapScalingMode="NearestNeighbor"
                                                                        Width="88"
                                                                        Height="36"
                                                                        VerticalAlignment="Bottom"
                                                                        Source="/AIPAnnouncement;component/ControlViews/Sources/Images/气泡.png">
                                                                </Image>
                                                                <TextBlock Text="领导"
                                                                           FontSize="13"
                                                                           HorizontalAlignment="Center"
                                                                           VerticalAlignment="Center">
                                                                </TextBlock>
                                                            </Grid>
                                                        </ToolTip>
                                                    </TextBox.ToolTip>
                                                    <i:Interaction.Triggers>
                                                        <i:EventTrigger EventName="TextChanged">
                                                            <interactive:ExInvokeCommandAction Command="{Binding DataContext.ModifyDutyPersonCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=my:AnnouncementApp}}"
                                                                                 CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TextBox}}">
                                                            </interactive:ExInvokeCommandAction>
                                                        </i:EventTrigger>
                                                        <i:EventTrigger EventName="GotFocus">
                                                            <interactive:ExInvokeCommandAction Command="{Binding DataContext.TextBoxGotFocus,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=my:AnnouncementApp}}"
                                                                                 CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TextBox}}">
                                                            </interactive:ExInvokeCommandAction>
                                                        </i:EventTrigger>
                                                    </i:Interaction.Triggers>
                                                </TextBox>
                                                <Popup x:Name="popup" 
                                                       PlacementTarget="{Binding ElementName=dutyPersonTextBox}"
                                                       Width="{Binding ActualWidth,ElementName=dutyPersonTextBox}"
                                                       IsOpen="{Binding  ElementName=dutyPersonTextBox,Path=IsKeyboardFocused, Mode=OneWay}"
                                                       StaysOpen="True">
                                                    <Grid Background="Red">
                                                        <ListBox x:Name="lb_selecthistorymembers"                          
                                                                 SnapsToDevicePixels="true" 
                                                                 ItemsSource="{Binding DataContext.SpecificHistoryMembers,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=my:AnnouncementApp},Mode=TwoWay}" 
                                                                 HorizontalAlignment="Stretch" 
                                                                 ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
                                                                 Background="#fff"
                                                                 BorderThickness="1">
                                                            <i:Interaction.Triggers>
                                                                <i:EventTrigger EventName="SelectionChanged">
                                                                    <interactive:ExInvokeCommandAction Command="{Binding DataContext.OnSelectHistoryMembersListBoxSelected,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=my:AnnouncementApp},Mode=TwoWay}"
                                                                                 CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}">
                                                                    </interactive:ExInvokeCommandAction>
                                                                </i:EventTrigger>
                                                            </i:Interaction.Triggers>
                                                            <ListBox.ItemContainerStyle>
                                                                <Style TargetType="ListBoxItem">
                                                                    <Setter Property="Template">
                                                                        <Setter.Value>
                                                                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                                                                <Border x:Name="Bd" 
                                                                                        Height="Auto" 
                                                                                        Width="Auto"
                                                                                        BorderBrush="{TemplateBinding BorderBrush}"
                                                                                        BorderThickness="0" 
                                                                                        Background="{TemplateBinding Background}"                                                                                         
                                                                                        SnapsToDevicePixels="true">
                                                                                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                                                                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                                                                                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                                                                                </Border>
                                                                                <ControlTemplate.Triggers>
                                                                                    <Trigger Property="IsEnabled" 
                                                                                             Value="false">
                                                                                        <Setter Property="Foreground" 
                                                                                                Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                                                                                    </Trigger>
                                                                                </ControlTemplate.Triggers>
                                                                            </ControlTemplate>
                                                                        </Setter.Value>
                                                                    </Setter>
                                                                    <Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
                                                                    <Setter Property="VerticalAlignment" Value="Center"></Setter>
                                                                    <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
                                                                </Style>
                                                            </ListBox.ItemContainerStyle>
                                                            <ListBox.ItemsPanel>
                                                                <ItemsPanelTemplate>
                                                                    <StackPanel IsItemsHost="True"
                                                                                HorizontalAlignment="Left"
                                                                                VerticalAlignment="Center"
                                                                                Width="{Binding ActualWidth,ElementName=dutyPersonTextBox}"
                                                                                >
                                                                    </StackPanel>
                                                                </ItemsPanelTemplate>
                                                            </ListBox.ItemsPanel>
                                                            <ListBox.ItemTemplate>
                                                                <DataTemplate>
                                                                    <Border  Name="Border"                                                                                                                        
                                                                             BorderThickness="0">
                                                                        <Grid Margin="2,1,1,1">
                                                                            <Label x:Name="label"
                                                                                   Content="{Binding SpecificHistoryDutyPersonName}"
                                                                                   HorizontalAlignment="Stretch"
                                                                                   HorizontalContentAlignment="Center"
                                                                                   FontSize="13">
                                                                            </Label>
                                                                        </Grid>
                                                                    </Border>
                                                                    <DataTemplate.Triggers>
                                                                        <Trigger Property="IsMouseOver" 
                                                                                 Value="true">
                                                                            <Setter Property="Background"
                                                                                    Value="#00a3d9"
                                                                                    TargetName="Border">
                                                                            </Setter>
                                                                            <Setter Property="Background"
                                                                                    Value="#f8f3f0"
                                                                                    TargetName="label">
                                                                            </Setter>
                                                                        </Trigger>
                                                                    </DataTemplate.Triggers>
                                                                </DataTemplate>
                                                            </ListBox.ItemTemplate>
                                                        </ListBox>
                                                    </Grid>
                                                </Popup>
                                            </StackPanel>                                            
                                        </Border>
                                                                               
                                        <xui:Button x:Name="deleteAnnouncementItem"
                                                    Grid.Row="0"     
                                                    Grid.Column="1"
                                                    Height="14" 
                                                    Width="14" 
                                                    Opacity="0"
                                                    HorizontalAlignment="Right"
                                                    VerticalAlignment="Top">
                                                    <i:Interaction.Triggers>
                                                        <i:EventTrigger EventName="Click">
                                                            <interactive:ExInvokeCommandAction 
                                                                        Command="{Binding DataContext.DutyPersonDeleteCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=my:AnnouncementApp}}"
                                                                        CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=xui:Button}}">
                                                            </interactive:ExInvokeCommandAction>
                                                        </i:EventTrigger>
                                                    </i:Interaction.Triggers>
                                            <Button.Background>
                                                <ImageBrush ImageSource="/AIPAnnouncement;Component/ControlViews/Sources/Images/关闭.png">
                                                </ImageBrush>
                                            </Button.Background>
                                        </xui:Button>
                                    </Grid>
                                    <DataTemplate.Triggers>                                        
                                        <DataTrigger Binding="{Binding IsLeader}" 
                                                     Value="true">                                           
                                            <Setter Property="BorderBrush" 
                                                    Value="#f00" 
                                                    TargetName="DutyPerson">
                                            </Setter>
                                        </DataTrigger>
                                        <Trigger Property="IsMouseOver" 
                                                 Value="true"
                                                 SourceName="DutyPerson">
                                            <Setter Property="BorderThickness" 
                                                    Value="0" 
                                                    TargetName="DutyPerson">
                                            </Setter>                                            
                                        </Trigger>
                                        <Trigger Property="IsMouseOver" 
                                                 Value="true"
                                                 SourceName="deleteAnnouncementItem">                                           
                                            <Setter Property="Opacity" 
                                                    Value="1"
                                                    TargetName="deleteAnnouncementItem">
                                            </Setter>
                                        </Trigger>
                                    </DataTemplate.Triggers>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>

  这里面的每一个Item定义成了类似于百度的搜索框一样的东西,当我们在文本输入框中输入文字时,会弹出一个Popup,里面是一个ListBox,我们可以从中挑选我们需要的选项,最后加入到TextBox中去,这里做了一个模板,在每一个TextBox下面添加一个Popup,当输入文字时会自动检索当前历史记录中是否存在当前项,这里面的核心是 IsOpen="{Binding  ElementName=dutyPersonTextBox,Path=IsKeyboardFocused,  Mode=OneWay}"  StaysOpen=”true” 这句的意思表示当前的Popup是否打开是取决于dutyPersonTextBox(一个TextBox控件)是否获得鼠标的焦点,这里使用IsKeyboardFocused来表示鼠标是否获取到焦点,后面我们会看一看具体效果的图片。

    刚开始的时候,没有很多思考,当自己定义ItemsControl的ItemsPanel时,给它赋了一个定值,这里就埋下了一个很大的隐患,所以我们在不断的往ItemsControl中添加Item的项时,ItemsControl的高度只会维持在70,因为Items的容器ItemsPanel的高度就决定了ItemsControl的高度,当超过这个高度的时候会自动地去剪裁掉多余的部分,这是WPF的一个基本机理,所以我们在设置ItemsPanel的容器WrapPanel的时候一定要将Height设置为Auto,这样我们就能看到ItemsPanel的高度自动增加,这是其中一个方面,另外一个方面就是当我们必须设置ItemsPanel的高度或者是其父容器的高度为一个固定值假设为FixHeight,这样当随着Item的项的增多,ItemsPanel容器的高度超过FixHeight时,我们就会发现ScrollBar会出现,这些东西都是需要我们去不断地思考和总结的一些结论。     WPF的这种机理在很多的地方都是可以看到的,例如当我们往WrapPanel中添加项目时,为了保证能够使添加的项自动添加到第二行,那么我们必须为WrapPanel设置一个宽度,这样当我们添加项时才会自动跳转到下一行,因为如果我们不设置这个值,默认的高度和宽度都是Auto,这个在使用中必须要十分注意,并且平时多积累,才能真正地学以致用。

      今天就总结这么多,最后看一看最终的效果,其中第三行人员这一行就是使用ItemsControl做出来的效果。

 

posted @ 2016-06-02 16:15  Hello——寻梦者!  阅读(2573)  评论(0编辑  收藏  举报