WPF Datagrid binding custom column headers

后端 未结 3 1563
一个人的身影
一个人的身影 2021-02-03 12:22

I am trying to figure out how to bind a WPF DataGrid\'s column header and main data to a data source using an MVVM pattern. The result I\'m looking for would look like this:

相关标签:
3条回答
  • 2021-02-03 12:41

    We do something similar in our app.

    What i have done is derived my own column type (DataGridSearchableBooleanColumn), then i replace the DataGridColumnHeader template, i put two content presenters in there. the first i bind to the content (the same as the default template) the second i bind to the column. I use a data template for the column (i have a few of them for different search types (text, combo, boolean). then i add the extra properties to the column so i can bind to them. See if this code makes sense.

       <!--Style for the datagrid column headers, contains a text box for searching-->
       <Style
          x:Key="columnHeaderStyle"
          TargetType="dg:DataGridColumnHeader">
          <Setter
             Property="Foreground"
             Value="#FF000000" />
          <Setter
             Property="HorizontalContentAlignment"
             Value="Left" />
          <Setter
             Property="VerticalContentAlignment"
             Value="Center" />
          <Setter
             Property="IsTabStop"
             Value="False" />
          <Setter
             Property="Padding"
             Value="1,2,1,2" />
          <Setter
             Property="Template">
             <Setter.Value>
                <ControlTemplate
                   TargetType="dg:DataGridColumnHeader">
                   <Grid
                      x:Name="Root">
                      <dg:DataGridHeaderBorder
                         Background="{TemplateBinding Background}"
                         BorderBrush="{TemplateBinding BorderBrush}"
                         BorderThickness="{TemplateBinding BorderThickness}"
                         Padding="{TemplateBinding Padding}"
                         IsClickable="{TemplateBinding CanUserSort}"
                         IsHovered="{TemplateBinding IsMouseOver}"
                         IsPressed="{TemplateBinding IsPressed}"
                         SeparatorBrush="{TemplateBinding SeparatorBrush}"
                         SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
                         SortDirection="{TemplateBinding SortDirection}">
    
                         <Grid
                            HorizontalAlignment="Stretch"
                            Margin="{TemplateBinding Padding}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                            <Grid.Resources>
                               <DataTemplate
                                  DataType="{x:Type local:DataGridSearchableBooleanColumn}">
                                  <CheckBox
                                     Margin="0,5,0,0"
                                     IsThreeState="True"
                                     IsChecked="{Binding Path=IsChecked}" />
                               </DataTemplate>
                            </Grid.Resources>
                            <Grid.ColumnDefinitions>
                               <ColumnDefinition />
                               <ColumnDefinition
                                  Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                               <RowDefinition
                                  Height="19" />
                               <RowDefinition
                                  Height="Auto" />
                            </Grid.RowDefinitions>
    
                            <ContentPresenter
                               Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay, Path=Content}" />
    
                            <Path
                               x:Name="SortIcon"
                               Fill="#FF444444"
                               Stretch="Uniform"
                               HorizontalAlignment="Left"
                               Margin="4,0,0,0"
                               VerticalAlignment="Center"
                               Width="8"
                               Opacity="0"
                               RenderTransformOrigin=".5,.5"
                               Grid.Column="1"
                               Data="F1 M -5.215,6.099L 5.215,6.099L 0,0L -5.215,6.099 Z ">
                               <Path.RenderTransform>
                                  <ScaleTransform
                                     ScaleX=".9"
                                     ScaleY=".9" />
                               </Path.RenderTransform>
                            </Path>
                            <ContentPresenter
                               x:Name="columnHeaderContentPresenter"
                               Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Column}"
                               Grid.Row="1"
                               Grid.ColumnSpan="2"
                               Margin="0,0,0,0" />
                         </Grid>
                      </dg:DataGridHeaderBorder>
    
                      <Thumb
                         x:Name="PART_LeftHeaderGripper"
                         HorizontalAlignment="Left">
                         <Thumb.Style>
                            <Style
                               TargetType="{x:Type Thumb}">
                               <Setter
                                  Property="Width"
                                  Value="8" />
                               <Setter
                                  Property="Background"
                                  Value="Transparent" />
                               <Setter
                                  Property="Cursor"
                                  Value="SizeWE" />
                               <Setter
                                  Property="Template">
                                  <Setter.Value>
                                     <ControlTemplate
                                        TargetType="{x:Type Thumb}">
                                        <Border
                                           Background="{TemplateBinding Background}"
                                           Padding="{TemplateBinding Padding}" />
                                     </ControlTemplate>
                                  </Setter.Value>
                               </Setter>
                            </Style>
                         </Thumb.Style>
                      </Thumb>
                      <Thumb
                         x:Name="PART_RightHeaderGripper"
                         HorizontalAlignment="Right">
                         <Thumb.Style>
                            <Style
                               TargetType="{x:Type Thumb}">
                               <Setter
                                  Property="Width"
                                  Value="8" />
                               <Setter
                                  Property="Background"
                                  Value="Transparent" />
                               <Setter
                                  Property="Cursor"
                                  Value="SizeWE" />
                               <Setter
                                  Property="Template">
                                  <Setter.Value>
                                     <ControlTemplate
                                        TargetType="{x:Type Thumb}">
                                        <Border
                                           Background="{TemplateBinding Background}"
                                           Padding="{TemplateBinding Padding}" />
                                     </ControlTemplate>
                                  </Setter.Value>
                               </Setter>
                            </Style>
                         </Thumb.Style>
                      </Thumb>
                   </Grid>
                </ControlTemplate>
             </Setter.Value>
          </Setter>
       </Style>
    
    0 讨论(0)
  • 2021-02-03 12:47

    Here's what I ended up doing to use this with the MVVM pattern:

    I have two sets of data for binding on my view model: one for the actual grid data and one for the column headers. Currently these are exposed as two properties:

    // INotifyPropertyChanged support not shown for brevity
    public DataTable GridData { get; set; } 
    public BindingList<ImportColumnInfo> ColumnData { get; set; }
    

    The trick to working with two differing sets of data is in the grid. I have subclassed the DataGrid and given the grid an additional data source called ColumnSource, as a dependency property. This is what is bound to the ColumnData on my view model. I then set the header of each auto-generated column to the appropriately indexed data in the ColumnSource data source. The code is as follows:

    public class ImporterDataGrid : DataGrid
    {
        protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
        {
            base.OnAutoGeneratingColumn(e);
    
            int columnIndex = this.Columns.Count;
            var column = new ImporterDataGridColumn();
            column.Header = ColumnSource[columnIndex];
            column.Binding = new Binding(e.PropertyName) { Mode = BindingMode.OneWay };
            e.Column = column;
        }
    
        public IList ColumnSource
        {
            get { return (IList)GetValue(ColumnSourceProperty); }
            set { SetValue(ColumnSourceProperty, value); }
        }
    
        public static readonly DependencyProperty ColumnSourceProperty = DependencyProperty.Register("ColumnSource", typeof(IList), typeof(ImporterDataGrid), new FrameworkPropertyMetadata(null));
    
    }
    

    I can now perform normal data binding in the templated header of my columns, which will all bind against the data in the ColumnData property of my view model.

    UPDATE: I was asked to show the XAML for my grid. It's really basic, but here it is:

    <Controls:ImporterDataGrid 
        AutoGenerateColumns="True" x:Name="previewDataGrid"
        VerticalScrollBarVisibility="Visible"
        HorizontalScrollBarVisibility="Visible"
        IsReadOnly="True"
        SelectionMode="Extended"
        HeadersVisibility="Column"
        ItemsSource="{Binding PreviewData}"
        ColumnSource="{Binding PreviewColumnData}"
        Style="{StaticResource ImporterDataGridStyle}"
        Background="White" CanUserReorderColumns="False" CanUserResizeRows="False"
        CanUserSortColumns="False" AlternatingRowBackground="#FFFAFAFA" AllowDrop="True" />
    

    And here is the ImporterColumnHeaderStyle:

    <Style x:Key="ImporterDataGridColumnHeaderStyle" TargetType="{x:Type toolkit:DataGridColumnHeader}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type toolkit:DataGridColumnHeader}">
                    <Grid>
                        <toolkit:DataGridHeaderBorder Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" IsClickable="{TemplateBinding CanUserSort}" IsHovered="False" IsPressed="False" SortDirection="{TemplateBinding SortDirection}">
                            <Grid>
                                <CheckBox Height="16" Margin="6,6,16,0" Name="importCheckBox" IsChecked="{Binding Path=Import}" VerticalAlignment="Top">Import Column</CheckBox>
                                <StackPanel IsEnabled="{Binding Path=Import}">
                                    <ComboBox Height="24" Margin="6,29,6,0" Name="columnTypeComboBox" VerticalAlignment="Top" SelectedValue="{Binding ColumnType}" ItemsSource="{Binding Source={local:EnumList {x:Type Models:ImportColumnType}}}">
                                    </ComboBox>
                                    <TextBox Height="23"  Margin="6,6,6,33" Name="customHeadingTextBox" VerticalAlignment="Bottom" Text="{Binding Path=CustomColumnName}" IsEnabled="{Binding ColumnType, Converter={StaticResource ColumnTypeToBooleanConverter}}" />
                                </StackPanel>
                                <TextBlock Height="20" Margin="6,0,6,7" Name="originalHeadingTextBlock" Text="{Binding Path=OriginalColumnName}" VerticalAlignment="Bottom" Foreground="Gray" />
                            </Grid>
                        </toolkit:DataGridHeaderBorder>
    
                        <Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left">
                            <Thumb.Style>
                                <Style TargetType="{x:Type Thumb}">
                                    <Setter Property="Width" Value="8"/>
                                    <Setter Property="Background" Value="Transparent"/>
                                    <Setter Property="Cursor" Value="SizeWE"/>
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type Thumb}">
                                                <Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </Thumb.Style>
                        </Thumb>
                        <Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right">
                            <Thumb.Style>
                                <Style TargetType="{x:Type Thumb}">
                                    <Setter Property="Width" Value="8"/>
                                    <Setter Property="Background" Value="Transparent"/>
                                    <Setter Property="Cursor" Value="SizeWE"/>
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type Thumb}">
                                                <Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </Thumb.Style>
                        </Thumb>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    0 讨论(0)
  • 2021-02-03 13:02

    I am definitely a WPF / MVVM / databinding noob, but have been working hard on this stuff lately. I don't know what you have wired up so far, but first you'll want to set the DataContext for your View. Since you're using MVVM, I assume you have a ViewModel, so that should be the DataContext for your View.

    i.e. if you have your View create / own your ViewModel, it could look something like this:

    MyViewModel vm = new MyViewModel();
    this.DataContext = vm;
    

    You can easily databind your CheckBox, ComboBox, and TextBox to properties in your ViewModel. I have found the easiest way is to make your ViewModel inherit from a base viewmodel class, like the one that Josh Smith wrote. This will give you a method to call internally when you want the ViewModel to notify the GUI of any changes in values.

    Assuming you have properties like ImportColumn, LastName, and LastNameText (all C# properties, not fields that call OnPropertyChanged accordingly), then your XAML would look something like this:

    <CheckBox IsChecked="{Binding ImportColumn}" />
    <ComboBox SelectedItem="{Binding LastName}" />
    <TextBox Text="{Binding LastName Text, Mode=TwoWay}" />
    

    I hope this helps you out. If not, please comment and I'll try to do as best as I can to try other things out.

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