title: Static/Freeze DataGridRow at the top of DataGrid for WPF
body: <p>I wanted to create a custom DataGrid with static/frozen row at the top, similar to Excel's freeze row functionality, no matter how you scroll, the static row will always remain up top. I've came across <a href="https://stackoverflow.com/questions/8756791/freeze-datagrid-row">Freeze DataGrid Row</a> but noticed there weren't a complete solution. The static row also need to allow re-orderable columns like all other rows.</p>
  
  <p>Here are my attempt on creating the custom DataGrid. I've found the area where my static row will display at. <a href="https://i.stack.imgur.com/dY6ij.png" rel="nofollow noreferrer">space between column headers and scroll content presenter</a></p>
  
  <p>I've tried to use a DataGridRow control but was unable to get it displayed/working. Maybe I've misunderstood how this control is used and this is the wrong approach?</p>
  
  <p><strong>Wrong DataGridRow attempt</strong></p>
  
  <pre><code>&lt;DataGridRow Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Item="{Binding StaticRow, RelativeSource={RelativeSource AncestorType={x:Type local:CustomDataGrid}}}" /&gt;
  </code></pre>
  
  <p><strong>CustomDataGrid.xaml</strong></p>
  
  <p>I have a textblock placeholder between DataGridColumnHeadersPresenter and ScrollContentPresenter </p>
  
  <pre><code>&lt;Style TargetType="{x:Type local:CustomDataGrid}"&gt;
      &lt;Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/&gt;
      &lt;Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/&gt;
      &lt;Setter Property="BorderBrush" Value="#FF688CAF"/&gt;
      &lt;Setter Property="BorderThickness" Value="1"/&gt;
      &lt;Setter Property="RowDetailsVisibilityMode" Value="VisibleWhenSelected"/&gt;
      &lt;Setter Property="ScrollViewer.CanContentScroll" Value="true"/&gt;
      &lt;Setter Property="ScrollViewer.PanningMode" Value="Both"/&gt;
      &lt;Setter Property="Stylus.IsFlicksEnabled" Value="False"/&gt;
      &lt;Setter Property="Template"&gt;
          &lt;Setter.Value&gt;
              &lt;ControlTemplate TargetType="{x:Type local:CustomDataGrid}"&gt;
                  &lt;Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True"&gt;
                      &lt;ScrollViewer x:Name="DG_ScrollViewer" Focusable="false"&gt;
                          &lt;ScrollViewer.Template&gt;
                              &lt;ControlTemplate TargetType="{x:Type ScrollViewer}"&gt;
                                  &lt;Grid&gt;
                                      &lt;Grid.ColumnDefinitions&gt;
                                          &lt;ColumnDefinition Width="Auto"/&gt;
                                          &lt;ColumnDefinition Width="*"/&gt;
                                          &lt;ColumnDefinition Width="Auto"/&gt;
                                      &lt;/Grid.ColumnDefinitions&gt;
                                      &lt;Grid.RowDefinitions&gt;
                                          &lt;RowDefinition Height="Auto"/&gt;
                                          &lt;RowDefinition Height="Auto"/&gt;
                                          &lt;RowDefinition Height="*"/&gt;
                                          &lt;RowDefinition Height="Auto"/&gt;
                                      &lt;/Grid.RowDefinitions&gt;
                                      &lt;Button Command="{x:Static DataGrid.SelectAllCommand}" Focusable="false" Style="{DynamicResource {ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}}" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.All}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/&gt;
                                      &lt;DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter" Grid.Column="1" Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/&gt;
  
                                      &lt;TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"&gt;Static Row Here&lt;/TextBlock&gt;
  
                                      &lt;ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" Grid.ColumnSpan="2" Grid.Row="2"/&gt;
                                      &lt;ScrollBar x:Name="PART_VerticalScrollBar" Grid.Column="2" Maximum="{TemplateBinding ScrollableHeight}" Orientation="Vertical" Grid.Row="2" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}"/&gt;
                                      &lt;Grid Grid.Column="1" Grid.Row="3"&gt;
                                          &lt;Grid.ColumnDefinitions&gt;
                                              &lt;ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/&gt;
                                              &lt;ColumnDefinition Width="*"/&gt;
                                          &lt;/Grid.ColumnDefinitions&gt;
                                          &lt;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}"/&gt;
                                      &lt;/Grid&gt;
                                  &lt;/Grid&gt;
                              &lt;/ControlTemplate&gt;
                          &lt;/ScrollViewer.Template&gt;
                          &lt;ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/&gt;
                      &lt;/ScrollViewer&gt;
                  &lt;/Border&gt;
              &lt;/ControlTemplate&gt;
          &lt;/Setter.Value&gt;
      &lt;/Setter&gt;
      &lt;Style.Triggers&gt;
          &lt;MultiTrigger&gt;
              &lt;MultiTrigger.Conditions&gt;
                  &lt;Condition Property="IsGrouping" Value="true"/&gt;
                  &lt;Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/&gt;
              &lt;/MultiTrigger.Conditions&gt;
              &lt;Setter Property="ScrollViewer.CanContentScroll" Value="false"/&gt;
          &lt;/MultiTrigger&gt;
      &lt;/Style.Triggers&gt;
  &lt;/Style&gt;
  </code></pre>
  
  <p><strong>CustomDataGrid.cs</strong></p>
  
  <pre><code>public class CustomDataGrid : DataGrid
  {
      static CustomDataGrid()
      {
          DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomDataGrid), new FrameworkPropertyMetadata(typeof(CustomDataGrid)));
      }
  
      public static readonly DependencyProperty StaticRowProperty = DependencyProperty.Register(
          "StaticRow", typeof(object), typeof(CustomDataGrid), new PropertyMetadata(default(object)));
  
      public object StaticRow
      {
          get { return GetValue(StaticRowProperty); }
          set { SetValue(StaticRowProperty, value); }
      }
  }
  </code></pre>