0

I don't have a long experience of programming in c# and using WPF and I apologize if my "vocabulary" is not enough technical or appropriate.

I need to display a table in my application where rows are instances of a class and columns are properties/attributes that define my objects.

public class myObject
{
    //constructor
    public myObject() { }

    //properties
    public string Topic { get;  set}

    public string Description { get; set; }

    public bool Display { get; set; }

    public List<myAttribute> AttributeList { get; set; }


    //method
    public string GetAttributeValueByName(string sAttributeName)
    {
        foreach (myAttribute A in this.AttributeList)
        {
            if (A.Name == sAttributeName) { return A.Value.ToString(); }
        }

        return string.Empty;
    }
}

I have a list of myObjects that I bind as datagrid.ItemSource and I'm already able to properly bind defined properties like Topic/Description and display them in columns. myObject.Display is one-way binded as well and let me to visualize myObject in the datagrid or not (as a sort of filter which come from somewhere else).

Now, as you could notice in the code above, I have a property that return me a list of attributes. This because myObject have an undefined number of free attributes which the user can set. I have set a proper class for this Attributes (with few simple properties like .Name, .Value, .Color, .Rank). I have methods that let me to retrieve the value as a string for example or so.

My main problem is that I'm not able to visualize them properly in the datagrid because, in general, the binding works with properties and not with methods. I did some resarch about binding a method and I've tried with any successful result.

Briefly:

  • I need dynamic columns (some fixed like Topic + one for each attribute)
  • all myObjects have same number of element in AttributeList
  • I need to bind (at least one-way) the value of an attribute in the proper cell of the datagrid.

UI class:

namespace myApp.UserInterface
{
    public partial class control : UserControl
    {
        control()
        {
            InitializeComponent();
            InitializeData();
        }

        private InitializeData()
        {
            datagrid.ItemsSource = Core.myObjectPoolList;
        }
    }
}

UI XAML

<UserControl x:Class="myApp.UserInterface.control"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:myApp.UserInterface;assembly="
         xmlns:core="clr-namespace:myObject.Core;assembly="
         xmlns:system="clr-namespace:System;assembly=mscorlib"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <ObjectDataProvider x:Key="basic_attribute_value"
                            ObjectType="{x:Type core:D2P_Issue}"
                            MethodName="GetAttributeValueByName"
                            IsAsynchronous="True">
            <ObjectDataProvider.MethodParameters>
                <system:String>status</system:String>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </UserControl.Resources>

    <Grid>
        <DataGrid Name="issuepool_datagrid" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Topic" Binding="{Binding Topic}" Width="150" TextBlock.TextAlignment="Center"/>
                <DataGridTextColumn Header="Description" Binding="{Binding Description}" Width="150" TextBlock.TextAlignment="Center"/>
                <DataGridTextColumn x:Name="status_column" Header="status" Binding="{Binding Source={StaticResource basic_attribute_value}, Mode=OneWay}" Width="150" TextBlock.TextAlignment="Center"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</UserControl>

In this example I was trying to leave behind the dynamic generation of columns and just hardcode it to look for the "status" attribute name.

Feel free to ask if something is not clear.

4
  • You can’t bind to a method, but a method that returns a value can probably be rewritten as a read-only property. Do these methods have parameters? Another option is a multivalue converter. Commented Dec 19, 2017 at 13:19
  • The method take a parameter (attribute name) and return me a value (attribute value) looking throw a list of "attributes". Basically I should create a property for each attribute, but I can not since it's the user that define these attributes. I'm doing research on converters... Commented Dec 19, 2017 at 13:52
  • No, you should not create a property for each attribute, because they're not fixed. Do all the items in the grid have the same set of attributes? Commented Dec 19, 2017 at 14:36
  • they do! The table is somehow coherent. Commented Dec 19, 2017 at 14:40

2 Answers 2

1

You could create the columns dynamically in code behind. Pretty simple though. If you were going to reuse this multiple times in the same project, you could rewrite this as an attached behavior, and store the collection of dynamic columns on the grid itself using another attached property.

It's up to you to figure out when the set of attributes changes, and update the columns accordingly. I've stored the dynamic columns in a dictionary to simplify the business of updating them intelligently, if you happen to know that only one attribute was added or removed.

If were you, I'd replace myObject.AttributeList with Dictionary<String, myAttribute> as well. The binding path would have to change slightly.

This allows the user to edit the attribute values. You could set IsReadOnly = true on the dynamic columns if you like.

public partial class control : UserControl
{
    control()
    {
        InitializeComponent();
        InitializeData();
    }

    private InitializeData()
    {
        datagrid.ItemsSource = Core.myObjectPoolList;

        var firstItem = Core.myObjectPoolList.First();

        if (firstItem != null) {
            UpdateDataGridColumns(dataGrid, firstItem.AttributeList, _dynamicColumns);
        }
    }

    private Dictionary<string, DataGridColumn> _dynamicColumns = new Dictionary<string, DataGridColumn>();
    protected void UpdateDataGridColumns(DataGrid dg, List<myAttribute> attributesSample, Dictionary<string, DataGridColumn> existingDynamicColumns)
    {
        foreach (var col in existingDynamicColumns.Values)
        {
            dg.Columns.Remove(col);
        }
        existingDynamicColumns.Clear();

        int idx = 0;
        foreach (var attr in attributesSample)
        {
            var column = new DataGridTextColumn() {
                Header = attr.Name,
                Binding = new Binding($"AttributeList[{idx}].Value")
            };

            dg.Columns.Add(column);

            existingDynamicColumns.Add(attr.Name, column);

            ++idx;
        }
    }
}

Alternate approach: Put all the attributes in one column. This doesn't use the code behind above. This is readonly in this form, but could be adapted to let the user edit the values in the grid.

Add this property to myObject:

    public IEnumerable<String> AttributeNames 
        => AttributeList.Select(a => a.Name);

And this to DataGrid.Columns in the XAML:

<DataGridTemplateColumn
    Width="*"
    >
    <DataGridTemplateColumn.HeaderStyle>
        <Style>
            <Setter Property="ContentControl.HorizontalContentAlignment" Value="Stretch" />
        </Style>
    </DataGridTemplateColumn.HeaderStyle>
    <DataGridTemplateColumn.HeaderTemplate>
        <DataTemplate>
            <ItemsControl
                ItemsSource="{Binding DataContext.Items[0].AttributeNames, RelativeSource={RelativeSource AncestorType=DataGrid}}"
                >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Label 
                            Content="{Binding}" 
                            />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </DataTemplate>
    </DataGridTemplateColumn.HeaderTemplate>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ItemsControl
                ItemsSource="{Binding AttributeList}"
                >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Label Content="{Binding Value}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Sign up to request clarification or add additional context in comments.

1 Comment

Using a dictionary was to key to resolve the problem: Binding = new Binding($"AttributeList[{idx}].Value") Thanks!
0

Why not use a DataGridComboBoxColumn?

Something like this:

<DataGridComboBoxColumnHeader="Attributes" Binding="{Binding AttributeList}" width="150" TextBlock.TextAlignment="Center"/>

3 Comments

This will not let me to have an overview of all the attributes, which is the main reason why i build the table! but thanks!
Sorry i'm not understanding what the desired result is? Is it that you want a column for each myAttribute elements in the List?
Exactly! a new column for each attribute!

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.