Freeze DataGrid Row

前端 未结 2 1499
死守一世寂寞
死守一世寂寞 2020-12-14 10:09

I was wondering if in a WPF datagrid in .net 4.0, is it possible to have a static row.

What I am trying to achieve is to create a static row (row 0), that will alway

相关标签:
2条回答
  • 2020-12-14 10:47

    This "Simple solution" is only for a freezable footer, the frozen header solution would be a bit different (and actually much easier - just play with the HeaderTeamplate - put a stack panel with as many items stacked up as you want).

    So I needed a footer row that is freezable, I couldn't find anything for months, so finally I decided to stop being lazy and investigate.

    So if you need a footer, the gist is find a place in DataGrid's Template between the rows and the horizontal scrollviewer where you can squeeze extra Grid.Row with an ItemsControl with Cells.

    PLAN OF ATTACK:

    First, extract the DataGrid template (I used Blend). When getting familiarized with the template, note the parts in order:

     PART_ColumnHeadersPresenter
     PART_ScrollContentPresenter
     PART_VerticalScrollBar
    

    right under PART_VerticalScrollBar, there is a grid (I'll post it here for clarity)

    <Grid Grid.Column="1" Grid.Row="2">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
          <ColumnDefinition Width="*"/>
       </Grid.ColumnDefinitions>
       <ScrollBar x:Name="PART_HorizontalScrollBar" Grid.Column="1" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}"/>
    </Grid>
    

    That's the grid I modified to include a "freezable/footer row". I am going to just hard-code colors, and replace Binding with hoperfully helpful "pretend" properties for simplicity (I'll mark them "MyViewModel.SomeProperty so they are easy to see):

    <Grid Grid.Column="1" Grid.Row="2" x:Name="PART_DataGridColumnsVisualSpace">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=NonFrozenColumnsViewportHorizontalOffset}"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ScrollBar Grid.Column="2" Grid.Row="3" Name="PART_HorizontalScrollBar" Orientation="Horizontal" 
               Maximum="{TemplateBinding ScrollableWidth}" ViewportSize="{TemplateBinding ViewportWidth}"
               Value="{Binding Path=HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
               Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
    
        <Border x:Name="PART_FooterRowHeader" Grid.Row="1" Height="30" Background="Gray" BorderBrush="Black" BorderThickness="0.5">
        <TextBlock Margin="4,0,0,0" VerticalAlignment="Center">MY FOOTER</TextBlock>
        </Border>
        <ItemsControl x:Name="PART_Footer" ItemsSource="{Binding MyViewModel.FooterRow}"
                  Grid.Row="1" Grid.Column="1" Height="30">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Background="Gray" BorderThickness="0,0,0.5,0.5" BorderBrush="Black">
                         <!-- sticking a textblock as example, i have a much more complex control here-->
                        <TextBlock Text="{Binding FooterItemValue}"/>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer x:Name="PART_Footer_ScrollViewer" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" 
                              CanContentScroll="True" Focusable="false">
                        <StackPanel IsItemsHost="True" Orientation="Horizontal"/>
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
        </ItemsControl>
    </Grid>
    

    Also add to DataGrid respond to scroll and header resize

    <DataGrid ... ScrollViewer.ScrollChanged="OnDatagridScrollChanged"
    
    <Style TargetType="DataGridColumnHeader">
        <EventSetter Event="SizeChanged" Handler="OnDataColumnSizeChanged"/>
    </Style>
    

    Now, back in .xaml.cs

    Basically two main things are needed:

    (1) sync column resize (so that corresponding footer cell resizes) (2) sync DataGrid scroll with footer scroll

    //syncs the footer with column header resize
    private void OnDatagridScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        if (e.HorizontalChange == 0.0) return;
        FooterScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
    }
    
    //syncs scroll
    private void OnDataColumnSizeChanged(object sender, SizeChangedEventArgs e)
    {
        //I don't know how many of these checks you need, skip if need to the gist
        if (!_isMouseDown) return;
        if (!_dataGridLoaded) return;
        if (!IsVisible) return;
    
         var header = (DataGridColumnHeader)sender;
         var index = header.DisplayIndex - ViewModel.NumberOfHeaderColumns;
    
         if (index < 0 || index >= FooterCells.Count) return;
    
         FooterCells[index].Width = e.NewSize.Width;
    }
    
    //below referencing supporting properties:
    private ScrollViewer _footerScroll;
    private ScrollViewer FooterScrollViewer
    {
        get {
            return _footerScroll ??
                  (_footerScroll = myDataGrid.FindVisualChildByName<ScrollViewer>("PART_Footer_ScrollViewer"));
            }
    }
    
    //added this so I don't have to hunt them down from XAML every time
    private List<Border> _footerCells;
    private List<Border> FooterCells
    {
        get
        {
            if (_footerCells == null)
            {
                var ic = myDataGrid.FindVisualChildByName<ItemsControl>("PART_Footer");
                _footerCells = new List<Border>();
                for (var i = 0; i < ic.Items.Count; i++)
                {
                   var container = ic.ItemContainerGenerator.ContainerFromIndex(i);                             
                   var border = ((Visual)container).FindVisualChild<Border>();
                  _footerCells.Add(border);
                }
             }
             return _footerCells;
        }
    }
    

    that's it! I think the most important part is the XAML to see where you can put your "freezable row", everything else, like manipulating/sync'ing things is pretty easy - almost one liners)

    0 讨论(0)
  • 2020-12-14 11:00

    I am not sure about the rows but you can freeze columns using FrozenColumnCount. This way it will always be visible. There should be a freeze property.

    0 讨论(0)
提交回复
热议问题