简体   繁体   中英

Assign a CellTemplate to a Dynamically Generated DataGridTemplateColumn in C#/WPF

I currently have a DataGrid that is bound to a DataTable via ItemSource . I want to be able to apply a CellTemplate ( MyTemplate ) to all columns in the DataGrid with a certain type ( MyType ).

Since the DataTable has a dynamic number of columns, I cannot disable AutoGenerateColumns and manually define DataGridTemplateColumns in the WPF.

Here is my DataTable in WPF:

<DataGrid HeadersVisibility="Column" VerticalScrollBarVisibility="Visible" CanUserAddRows="False"
 IsSynchronizedWithCurrentItem="False" FontSize="12"  BorderThickness="0,1,0,0" RenderTransformOrigin="0.5,0.5"
 ItemsSource="{Binding MyDataTable}" AutoGenerateColumns="True" AutoGeneratingColumn="GeneratingColumnEvent"/>

The DataTemplate I want to assign is defined in the UserControl's Resource Dictionary (and works when used in a explicitly defined DataGridTemplateColumn ).

<UserControl.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <SharedResourceDictionary Source="{Resources Directory}/MyTemplates.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
  </ResourceDictionary>
</UserControl.Resources>

My AutoGenerateColumn event is defined like this:

private void GeneratingColumnEvent(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            if (e.PropertyType == typeof (MyType))
            {
                var newCol = new DataGridCandidateTemplateColumn
                {
                    CellTemplate = (DataTemplate) FindResource("MyTemplate"),
                    ColumnName = e.PropertyName,
                };
                e.Column = newCol;
                e.Column.Header = e.PropertyName;
            }
        }

With the custom DataGridCandidateTemplateColumn class defined like this:

class DataGridCandidateTemplateColumn : DataGridTemplateColumn
    {
        public string ColumnName { get; set; }

        protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
        {
            // The DataGridTemplateColumn uses ContentPresenter with your DataTemplate.
            ContentPresenter cp = (ContentPresenter)base.GenerateElement(cell, dataItem);
            // Reset the Binding to the specific column. The default binding is to the DataRowView.
            if (cp != null)
                BindingOperations.SetBinding(cp, ContentPresenter.ContentProperty, new Binding(this.ColumnName));
            return cp;
        }
    }

If I don't try to apply the template, the column uses the toString representation of MyType . If I do apply the template like above, nothing appears in the column's cells. What am I missing?

It's a bit unclear what exactly you are trying to achieve by using that TemplateColumn. In case it's intended just for formatting, then you can add ResourceDictionary file and reference it in your Windows XAML, something like in the following example:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib">
....

        <Setter Property="Margin" Value="0,0,0,3" />
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="FontSize" Value="{StaticResource somevalue}"  />
        <Setter Property="GridLinesVisibility" Value="All"/>
        <Setter Property="HorizontalGridLinesBrush" Value="{StaticResource Somevalue}" />
        <Setter Property="VerticalGridLinesBrush" Value="{StaticResource SomeValue}" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="Foreground" Value="Transparent" />
        <Setter Property="VerticalAlignment" Value="Stretch" />
        <Setter Property="VerticalContentAlignment" Value="Stretch" />
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />

    <Style TargetType="DataGridColumnHeader">
        <Setter Property="Background" Value="{StaticResource ColumnHeaderBackgroundColor}"/>
        <Setter Property="Foreground" Value="{StaticResource ColumnHeaderForegroundColor}"/>
        <Setter Property="BorderBrush" Value="{StaticResource ColorBorderColumnHeader}" />
        <Setter Property="BorderThickness" Value="1,0,0,0"/>
        <Setter Property="Margin" Value="0,0,5,0"/>
        <Setter Property="Padding" Value="6,6,10,6"/>
        <Setter Property="Cursor" Value="Hand"/>
    </Style>

    <Style TargetType="DataGridRowHeader">
        <Setter Property="Visibility" Value="Collapsed"/>
        <Setter Property="Width" Value="0"/>
    </Style>

    <Style TargetType="DataGridRow">
        <Setter Property="Foreground" Value="{StaticResource GridFontColor}" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Style.Triggers>
            <Trigger Property="DataGridRow.IsSelected" Value="True">
            </Trigger>
        </Style.Triggers>
    </Style>

    <Style TargetType="DataGridCell">
        <Setter Property="Padding" Value="4,4,2,4"/>
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGridCell}">
                    <Border Padding="{TemplateBinding Padding}" 
                                    BorderBrush="{TemplateBinding BorderBrush}" 
                                    BorderThickness="{TemplateBinding BorderThickness}" 
                                    Background="{TemplateBinding Background}" 
                                    SnapsToDevicePixels="True">
                        <ContentPresenter SnapsToDevicePixels="True"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>

Otherwise, use that GeneratingColumnEvent and set the Columns ' individual properties, like width , String format, etc.

private void GeneratingColumnEvent(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    try
    {
        //Modify Columns names in Header, and apply proper String Format if needed
        // Collapse the ID column
        if (e.Column.Header.ToString() == "ID")
        { e.Column.Visibility = Visibility.Collapsed; }

        // Item
        else if (e.Column.Header.ToString() == "Item")
        { e.Column.Width = new DataGridLength(4.5, DataGridLengthUnitType.Star); }

        // Price
        if (e.Column.Header.ToString() == "Price")
        {
            e.Column.Header = "Px";
            (e.Column as DataGridTextColumn).Binding.StringFormat = "C2";
            e.Column.Width = new DataGridLength(1.7, DataGridLengthUnitType.Star);
        }
   }
    catch { }
}

Answering my own question. I apologize since my solution will not work for everyone.

The reason why I had to work with a dynamic number of columns was because the number of columns was 3 + 5n where n was number of categories of data contained in my datatable.

I was able to leverage the data (more below) in a way to make it possible for me to disable AutoGenerateColumns and define the columns as part of another event (using a loop to generate the 5n categories). By being able to define each column, I was able to assign my templates without relying on DataGridAutoGeneratingColumnEventArgs to determine the right template.


Instead of binding my datagrid to a datatable where I pre-processed my data to have 3 + 5n columns, I instead wrapped the 5n portion of the data with a custom class. I also wrapped each row in a class and made the datagrid use a List of these classes as its ItemSource .

This allowed me to create the columns as part of the initialization process of the datagrid. When it came to defining the columns for the 5n part of the datagrid, I used a converter like this:

Column Definition:

private void GenerateColumnEvent(object sender, EventArgs e)
{
    var table = sender as DataGrid;
    var context = table.DataContext 

    //Regular column definitions

    foreach(var category in context.CategoryIDList)
    {
        var binding = new Binding("CategoryWrapper")
        {
            Converter = new FindCategoryProperty(),
                ConverterParameter = new Tuple<categoryID, string>(category, "Property1")
        };
        var column = new DataGridTextColumn
        {
            Binding = binding,
            //Other column defining things (templates, headers, etc)
        };
        table.Columns.Add(column);

        //More column definitions
    }
}

Converter Definition:

class FindCategoryProperty: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && parameter != null)
        {
            var categoryList = (IList<CategoryClass>) value;
            var categoryStringTuple = (Tuple<categoryID, string>) parameter;
            var categoryID = categoryStringTuple.Item1;
            var child = categoryList.FirstOrDefault(c => c.CategoryID == categoryID);
            if (child != null)
            {
                switch (categoryStringTuple.Item2)
                    {
                        case "Property1":
                            return child.getProperty1();
                        case "Property2":
                            return child.getProperty2();
                        //etc
                        default:
                            return "";
                    }
            }
        }
        return "";
    }

    //Left the ConvertBack unimplemented
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM