Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embed DataGrid into WPF Treeview nodes

I need to display Hierarchy in the treeview. But Details should be displayed in the datagrid.

How I'd like it to be

How do I have to write my template to achieve this? I misunderstand smth in templates for now.

    <TreeView ItemsSource="{Binding Path=Categories}">
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type stackProjects:Category}" ItemsSource="{Binding Path=SubCategories}">
                <TextBlock Margin="3" Text="{Binding Path=CategoryName}"/>
            </HierarchicalDataTemplate>

            <HierarchicalDataTemplate DataType="{x:Type stackProjects:SubCategory}" ItemsSource="{Binding Path=Details}">
                <TextBlock Text="{Binding Path=SubCategoryName}"/>
            </HierarchicalDataTemplate>

            <DataTemplate DataType="{x:Type stackProjects:Detail}" >
                <StackPanel Orientation="Horizontal">
                    <TextBlock Margin="3" Text="{Binding Path=Name}"/>
                    <TextBlock Margin="3" Text=" - "/>
                    <TextBlock Margin="3" Text="{Binding Path=Info}"/>
                </StackPanel>
            </DataTemplate>
        </TreeView.Resources>
    </TreeView>
like image 689
Artiom Avatar asked Feb 19 '23 12:02

Artiom


1 Answers

I've found a workaround. I had to understand that Details should be presented as a collection within a single element which has IEnumerable property. May be it's not the best solution but it works.

I needed to create a Converter to wrap my collection into single one.

public class BoxingItemsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var values = value as IEnumerable;
        var type = parameter as Type;

        if (values == null || type == null)
            return null;

        if (type.GetInterfaces().Any(x => x == typeof (IItemsWrapper)))
        {
            var instance = (IItemsWrapper) type.Assembly.CreateInstance(type.FullName);
            instance.Items = (IEnumerable) value;
            //returned value should be IEnumerable with one element. 
            //Otherwise we will not see children nodes
            return new List<IItemsWrapper> {instance};
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Example for wrappers:

internal interface IItemsWrapper
{
    IEnumerable Items { get; set; }
}

public class ItemsWrapper : IItemsWrapper
{
    public IEnumerable Items { get; set; }
}

public class DetailItemsWrapper : ItemsWrapper{}

public class InvoiceItemsWrapper:ItemsWrapper{}

And the xaml. It will not require a lot of changes. You just need to use Boxing converter and set the Type to return in the converter parameter.

<TreeView ItemsSource="{Binding Path=Categories}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type wpfProj:Category}" ItemsSource="{Binding Path=SubCategories}">
            <TextBlock Margin="4" Text="{Binding Path=CategoryName}"/>
        </HierarchicalDataTemplate>

        <HierarchicalDataTemplate DataType="{x:Type wpfProj:SubCategory}" 
                                  ItemsSource="{Binding Path=Details, Converter={StaticResource boxingItems}, ConverterParameter={x:Type wpfProj:DetailItemsWrapper}}" >
            <StackPanel>
                <TextBlock Margin="4" Text="{Binding Path=SubCategoryName}"/>
            </StackPanel>
        </HierarchicalDataTemplate>

        <DataTemplate DataType="{x:Type wpfProj:DetailItemsWrapper}" >
            <DataGrid ItemsSource="{Binding Path=Items}"/>  
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

I've uploaded sample to dropbox. Here is how it does look like:

DataGrid for TreeView nodes

like image 91
Artiom Avatar answered Feb 26 '23 22:02

Artiom