WPF Datagrid binding custom column headers

别来无恙 提交于 2019-12-20 10:10:48

问题


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:


(source: vallelunga.com)

I've successfully styled the headers here, but I'm unsure how to bind the values in the headers. Specifically, the IsChecked property of the check-box, the selected index of the combo box and the value of the text box.

I was previously using a simple DataTable to populate the main grid data, but I'm going to need something more complex to hold both the grid data and the values for each column. Or perhaps I can store them as separate entities entirely.

So, does anyone have any idea of how I might pull off this binding? One limitation is that the columns must be auto-generated since I have no idea what they will be until runtime. The application simply loads the data form an Excel spreadsheet and there may be any number of columns present.

Thanks, Brian


回答1:


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>



回答2:


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.




回答3:


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>


来源:https://stackoverflow.com/questions/2189671/wpf-datagrid-binding-custom-column-headers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!