[WPF] DataGridComboBoxColumn Darstellung ändern [SOLVED]

benediktibk

Standardgruppe für nicht aktivierte User
#1
Mir geht es darum, dass ich Elemente habe, welche einen Node gesetzt bekommen können (bzw. sollten). Ich würde das gerne über eine ComboBox lösen und dort aber die Namen der Nodes anzeigen. Ich habe das schon mit einigen Ratschlägen von Stackoverflow versucht, unter anderem auch einem Dictionary als ItemSource für die ComboBox. Das Resultat war aber immer das gleiche: eine leere ComboBox. Kann mir da bitte jemand auf die Sprünge helfen?

Zur Verdeutlichung ein Minimalbeispiel, ohne Dictionary. Das würde mir nämlich eigentlich besser gefallen, da ich dann nicht noch das Dictionary pflegen müsste. Es fehlt absichtlich das INotifyProperty-Zeugs, weil das ja eigentlich unerheblich sein sollte um in der ComboBox was angezeigt zu bekommen.

Code:
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:Model x:Key="Model"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource Model}">
        <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}">
            <DataGrid.Columns>
                <DataGridComboBoxColumn 
                    ItemsSource="{Binding OtherItems}" 
                    DisplayMemberPath="Name"
                    SelectedItemBinding="{Binding OtherItem}" 
                    Header="other item"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
Code:
public class OtherItem
{
    public string Name { get; set; }
}

public class Item
{
    public OtherItem OtherItem { get; set; }
}

class Model
{
    public Model()
    {
        OtherItems = new ObservableCollection<OtherItem> { new OtherItem{ Name = "blub" }, new OtherItem{ Name = "blob"} };
        Items = new ObservableCollection<Item> { new Item(), new Item(), new Item() };
    }

    public ObservableCollection<OtherItem> OtherItems { get; set; }
    public ObservableCollection<Item> Items { get; set; } 
}
mfg benediktibk
 
Zuletzt bearbeitet:

benediktibk

Standardgruppe für nicht aktivierte User
#2
Jetzt bin ich doch noch selber auf einen grünen Ast gekommen. Des Rätsels Lösung (eine der vermutlich vielen möglichen) sind zwei eigene Konverter (Ableitungen von IValueConverter): Eine von Node zu string und eine von einer Liste von Node zu einer Liste von string. Schnell zusammengeklopft (mit ein wenig Überbleibseln vom herumprobieren) sieht das dann so aus:
Code:
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:NodeToNodeNameConverter x:Key="NodeToNodeNameConverter"/>
        <local:NodesToNodeNamesConverter x:Key="NodesToNodeNamesConverter"/>
        <local:NodeNames x:Key="NodeNames"/>
    </Window.Resources>
    <Grid Name="MainGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Content="{Binding SingleItem.Node, Converter={StaticResource NodeToNodeNameConverter}, ConverterParameter=Name, UpdateSourceTrigger=PropertyChanged}"/>
        <ComboBox 
            Grid.Row="1" 
            ItemsSource="{Binding Nodes, Converter={StaticResource NodesToNodeNamesConverter}, ConverterParameter=Name, UpdateSourceTrigger=PropertyChanged}" 
            SelectedItem="{Binding SingleItem.Node, Converter={StaticResource NodeToNodeNameConverter}, ConverterParameter=Name, UpdateSourceTrigger=PropertyChanged}"/>
        <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Items}" Grid.Row="2">
            <DataGrid.Columns>
                <DataGridComboBoxColumn 
                    ItemsSource="{StaticResource NodeNames}"
                    SelectedItemBinding="{Binding Node, Converter={StaticResource NodeToNodeNameConverter}, ConverterParameter=Name, UpdateSourceTrigger=PropertyChanged}"
                    Header="node"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
Code:
    public class Node
    {
        public string Name { get; set; }
    }

    public class Item
    {
        public Node Node { get; set; }
    }

    public class NodeNames : List<string>
    {
    }

    public class NodeToNodeNameConverter : IValueConverter
    {
        private readonly Dictionary<string, Node> _mapping;

        public NodeToNodeNameConverter()
        {
            _mapping = new Dictionary<string, Node>();
        }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return null;

            var node = value as Node;

            if (node == null)
                throw new ArgumentException("value has invalid type");

            return node.Name;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var name = value as string;

            if (name == null)
                throw new ArgumentException("value has invalid type");

            Node result;
            var found = _mapping.TryGetValue(name, out result);
            return found ? result : null;
        }

        public void UpdateMapping(IEnumerable<Node> nodes)
        {
            _mapping.Clear();

            foreach (var node in nodes)
                _mapping.Add(node.Name, node);
        }
    }

    public class NodesToNodeNamesConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var nodes = value as IEnumerable<Node>;

            if (nodes == null)
                throw new ArgumentException("value has invalid type");

            var names = new List<string>();
            names.AddRange(nodes.Select(node => node.Name));
            return names;
        }

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

    class Model
    {
        public Model()
        {
            Nodes = new ObservableCollection<Node> { new Node { Name = "blub" }, new Node { Name = "blob" } };
            Items = new ObservableCollection<Item> { new Item(), new Item(), new Item() };
            SingleItem = Items[1];
            SingleItem.Node = Nodes[1];
        }

        public ObservableCollection<Node> Nodes { get; set; }
        public ObservableCollection<Item> Items { get; set; }

        public Item SingleItem { get; set; }
    }
Code:
    public partial class MainWindow
    {
        private readonly Model _model;

        public MainWindow()
        {
            InitializeComponent();
            _model = new Model();
            var converter = FindResource("NodeToNodeNameConverter") as NodeToNodeNameConverter;

            if (converter == null)
                throw new Exception("static resource is missing");

            converter.UpdateMapping(_model.Nodes);
            MainGrid.DataContext = _model;

            var nodeNames = FindResource("NodeNames") as NodeNames;

            if (nodeNames == null)
                throw new Exception("static resource is missing");

            nodeNames.AddRange(_model.Nodes.Select(node => node.Name));
        }
    }
Falls jemand eine elegantere Variante parat hat, immer her damit.

Danke für die mentale Unterstützung bei meinem Lernprozess :D,
benediktibk
 
Oben