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:
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.
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>
Why not use a DataGridComboBoxColumn ?
Something like this:
<DataGridComboBoxColumnHeader="Attributes" Binding="{Binding AttributeList}" width="150" TextBlock.TextAlignment="Center"/>
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.