FCin
FCin

Reputation: 3925

How to bind custom datagrid columns

I need to modify datagrid so that each column header has a textbox, combobox and checkbox, and all of them have to be bound to a property. I spent a lot of time trying to implement this with a default DataGrid, but I don't think this is possible, so I decided to create a custom DataGrid.

So far I have a bindable property BindableColumns that stores DataTable, so I have the data that I need to display. The problem is I don't know how to pass that data to OnAutoGeneratedColumns, so I can add columns to Columns property of DataGrid.

public class BindableGrid : DataGrid
{
    public DataTable BindableColumns
    {
        get { return (DataTable)GetValue(BindableColumnsProperty); }
        set { SetValue(BindableColumnsProperty, value); }
    }

    public static readonly DependencyProperty BindableColumnsProperty =
        DependencyProperty.Register("BindableColumns", typeof(DataTable), typeof(BindableGrid), new PropertyMetadata(null, BindableColumnsPropertyChanged));

    private static void BindableColumnsPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        // This is where I get DataTable after binding
    }


    protected override void OnAutoGeneratedColumns(EventArgs e)
    {
        // This is where I need the DataTable to generate columns.
        // I don't know how to invoke this method myself.
        Columns.Add(new DataGridTemplateColumn
        {
            Header = "Test1",
            HeaderTemplate = new DataTemplate()
        });
    }
}

And xaml:

<controls:BindableGrid ItemsSource="{Binding Data}" BindableColumns="{Binding Data}" AutoGenerateColumns="True">
 </controls:BindableGrid>

EDIT:

Thanks to @Ramin I have working columns. I had some trouble with dynamically adding rows, because DataGrid expects rows as a class with exactly the same variable names as column's bindings. For anyone having problems, here's how I solved it:

for (var rowIndex = 0; rowIndex < data.Rows.Count; rowIndex++)
{
   dynamic row = new ExpandoObject();

   for (var i = 0; i < data.Columns.Count; i++)
   // Create variables named after bindings, and assign values
        ((IDictionary<string, object>)row)[data.Columns[i].ColumnName.Replace(' ', '_')] = data.Rows[rowIndex].ItemArray[i];

    // Add row to DataGrid
    dg.Items.Add(row);
}

Upvotes: 0

Views: 2211

Answers (1)

rmojab63
rmojab63

Reputation: 3629

Regarding the source of the problem, here is a DataGrid with a templated header that contains a TextBox, ComboBox and a CheckBox:

 <DataGrid >
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding}" >
                <DataGridTextColumn.HeaderTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBox Text="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
                            <CheckBox IsChecked="{Binding DataContext.Value , RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
                            <ComboBox ItemsSource="{Binding DataContext.Names , RelativeSource={RelativeSource AncestorType=DataGrid}}" SelectedIndex="0"/>
                        </StackPanel>
                    </DataTemplate>
                </DataGridTextColumn.HeaderTemplate>
            </DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>

The DataContext is using a custom class:

public MainWindow()
{
    InitializeComponent();
    DataContext = new MyClass() { Name = "Name0", Value = true, Names = new string[2] { "Name1", "Name2" } };
}

public class MyClass
{
    public string Name { get; set; }
    public bool Value { get; set; }
    public string[] Names { get; set; }
}

EDIT

you can dynamically add columns:

    public void addNewColumn(Header h, string bindcol)
    {
        DataGridColumn col = new DataGridTextColumn(){Binding=new Binding(bindcol)};
        col.Header = h;
        col.HeaderTemplate = (DataTemplate)FindResource("dgh") as DataTemplate;
        dg.Columns.Add(col);
    }

in App.xaml:

<Application.Resources>
    <DataTemplate x:Key="dgh">
        <StackPanel Orientation="Horizontal">
            <TextBox Text="{Binding Name}" />
            <CheckBox IsChecked="{Binding Value}"/>
            <ComboBox ItemsSource="{Binding Names}" SelectedIndex="0"/>
        </StackPanel>
    </DataTemplate>
</Application.Resources>

To Test (assuming that there is a DataGrid with name dg):

    public MainWindow()
    {
        InitializeComponent();
        var h1 = new Header()
        {
            Name = "Name0",
            Value = true,
            Names = new string[2] { "Name1", "Name2" }
        };
        var h2 = new Header()
        {
            Name = "Name1",
            Value = true,
            Names = new string[2] { "Name12", "Name22" }
        };

        addNewColumn(h1, "col1");
        addNewColumn(h2, "col2");

    }

    public class Header
    {
         public string Name { get; set; }
         public bool Value { get; set; }
         public string[] Names { get; set; }
    }

Note that "col1" and "col2" refer to the datagrid's ItemsSource.

Upvotes: 1

Related Questions