3

I am trying to dynamically generate a DataGrid from a DataTable while at the same time display two button columns for "Edit" and "Delete" functionality. I can manually create a button column in XAML, and I can dynamically generate columns from a DataTable, but I can't seem to do both. If I were just coding this in the code-behind I think it would be a much simpler problem to solve because I would have direct access to the control. However, I am building this View using MVVM and I can't think of a way to manipulate the View dynamically to this level of detail.

Here's a little bit of my code (please note that there may be some Copy/Paste FAILS that are obvious to someone with more WPF/MVVM experience than I have):

XAML:

        <DataGrid   x:Name="grdMapValues"
                    AutoGenerateColumns="True"
                    ItemsSource="{Binding GridSourceDataTable}">
            <DataGrid.Columns>
                <DataGridTemplateColumn IsReadOnly="True">
                    <DataGridTemplateColumn.HeaderTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Button HorizontalAlignment="Left"
                                        Command="{Binding Path=DataContext.Commands.AddMapValue,
                                        RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"                                                    
                                        Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
                                    <Image Width="14" Height="14" Source="/Controls;component/Resources/Images/new.gif" ToolTip="New" />
                                </Button>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.HeaderTemplate>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"                                 
                                        Command="{Binding Path=DataContext.Commands.EditMapValue,
                                        RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
                                    <Image Width="14" Height="14" Source="/Controls;component/Resources/Images/edit.gif" />
                                </Button>
                                <Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"                                                    
                                        Command="{Binding Path=DataContext.Commands.DeleteMapValue,
                                        RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
                                    <Image Width="14" Height="14" Source="/Controls;component/Resources/Images/delete.gif" />
                                </Button>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>

ViewModel:

private DataTable _gridSource;
public DataTable GridSourceDataTable
{
    get
    {
        return _gridSource;
    }
    set
    {
        _gridSource = value;
        OnPropertyChanged("GridSourceDataTable");
    }
}

private void GenerateDataTable()
{        
    switch (caseInt)
    {
        case 1:
            if (_isDate)
            {
                _gridSource.Columns.Add("Date", typeof(DateTime));
            }
            else
            {
                _gridSource.Columns.Add("Number", typeof(string));
            }
            _gridSource.Columns.Add("Value", typeof(decimal));
            _gridSource.Columns.Add("SourceString", typeof(string));
            GridIsVisible = true;
            break;
        //Other similar case blocks removed
        default:
            GridIsVisible = false;
            break;
    }
    OnPropertyChanged("GridSourceDataTable");
}

So I am guessing that the solution, if there is one, will involve either some hybrid of XAML and the DataTable property OR it will involve a more sophisticated object or property than my simple DataTable.

I've been working on this one for several hours, so any help that can be offered will be greatly appreciated.

2
  • If the code is purely View related then it does not break the MVVM pattern, MVVM does NOT mean no code behind it means dont manipulate the business logic/models in the view. If you need code behind for the View to work and it does not touch your data then is fine. Just don't add the code to the ViewModel or Model Commented Nov 20, 2013 at 3:17
  • Well in this case the code won't be strictly View related. The decision on which columns to display in the grid and what data will be in those columns is based entirely on the object passed into the ViewModel constructor. That object has a List<T> and the objects in that list inherit T so we don't know until runtime what specific object type the user has chosen to manipulate. Commented Nov 20, 2013 at 13:20

1 Answer 1

5

You can use attached behaviors to achieve what you are trying. You will be manipulating and adding additional columns to the grid from an attached behavior - that means you are still following MVVM pattern.

This is the attached behavior I tried:

public class DataGridColumnBehavior : Behavior<DataGrid>
{
    public AdditionalColumnsList AdditionalColumns { get; set; }

    protected override void OnAttached()
    {
        base.OnAttached();
        AddAdditionalColumns();
    }

    void AddAdditionalColumns()
    {
        if(AdditionalColumns == null || AdditionalColumns.Count == 0) return;

        foreach (var additionalColumn in AdditionalColumns)
        {
            AssociatedObject.Columns.Add(new DataGridTemplateColumn
            {
                HeaderTemplate = additionalColumn.HeaderTemplate,
                CellTemplate = additionalColumn.CellTemplate
            });
        }
    }
}

I have used couple of simple classes to represent additional column and additional column list.

public class AdditionalColumn
{
    public DataTemplate HeaderTemplate { get; set; }

    public DataTemplate CellTemplate { get; set; }
}

public class AdditionalColumnsList : List<AdditionalColumn>
{
    
}

And, this is how you attach a behavior to DataGrid:

<DataGrid Margin="5" ItemsSource="{Binding GridSource}">
    <i:Interaction.Behaviors>
        <local:DataGridColumnBehavior>
            <local:DataGridColumnBehavior.AdditionalColumns>
                <local:AdditionalColumnsList>
                    <local:AdditionalColumn>
                        <local:AdditionalColumn.HeaderTemplate>
                            <DataTemplate>
                                <TextBlock Text="Button Header" />
                            </DataTemplate>
                        </local:AdditionalColumn.HeaderTemplate>
                        <local:AdditionalColumn.CellTemplate>
                            <DataTemplate>
                                <Button Content="Button" Command="{Binding ButtonCommand}" />
                            </DataTemplate>
                        </local:AdditionalColumn.CellTemplate>
                    </local:AdditionalColumn>
                </local:AdditionalColumnsList>
            </local:DataGridColumnBehavior.AdditionalColumns>
        </local:DataGridColumnBehavior>
    </i:Interaction.Behaviors>
</DataGrid>

And the resulting grid:

enter image description here

Read this CodeProject article on attached behaviors.

Please note, I have used Blend Behavior here. This blog post discusses differences between attached behaviors and blend behaviors.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for the solution. I have one doubt, How can I add the column at the end of the datagrid? Thanks
In behavior's "OnAttached" method, you could hook into AutoGeneratedColumns event and call the behavior's AddAdditionalColumns method in the event handler, that way you get custom columns at the end.
I am newbie in WPF. I don't know how to hook AutoGeneratedColumns event. I would really appreciate if you can provide some code. Thanks a lot
It is not a WPF thingy, it is same as hooking events in C#. You would have something like AssociatedObject.AutoGeneratedColumns += (s, e) => AddAdditionalColumns() in behavior's OnAttached method instead of AddAdditionalColumns() just after the base.OnAttached();
think, you are using AutoGeneratingColumns event instead of AutoGeneratedColumns.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.