Budowa kontrolki typu DataMatrix

0

Potrzebuje zrobić kontrolkę ktora wyswietli porownanie obiektow jako matryca na ktorej widać roznice pomiedzy obiektami. W pierwszej kolumnie ListView maja byc jakies skladniki unikatowe wystepujace w roznych konfiguracjach w roznych obiektach zlozonych a w kolejnych kolumnach ListView maja byc dodawane dynamiecznie porownywane obiekty. Naglowkiem tych kolumn maja byc nazwy porownywanych obiektow zlozonych a wartosciami znaczek 'x' symbolizujacy czy dany skladnik z pierwszej kolumny wystepuje w tym obiekcie zlozonym czy nie. To co do tej pory zrobiłem, oparłem o słownik, tzn. ListView wyswietla kolekcje obiektow "LiniaPorownania" w takim obiekcie porownania zawsze wystepuje wlasciwosc Typu Dictionary<string,bool> ktorej klucz 'string' chcialbym zeby byl naglowkiem kolejnych kolumn ListView a wartosc znacznikiem 'x' na matrycy oznaczajacym czy dany skladnik jest zawarty w skladzie oiektu zlozoego czy nie.

matryca_przyklad.png

Poniżej przykład abstrakcyjny ale ma wszystkie elementy ktore chcialbym miec w docelowym rozwiazaniu.

mój Model

public class Kolor 
    {
        public string Nazwa { get; }   
        public int Nasycenie { get; private set; }  
        public Kolor(string nazwa, int nasycenie)
        {
            this.Nazwa = nazwa;
            this.Nasycenie = nasycenie;
        }

        public void ZmienNasycenie(int noweNasycenie)
        {
            Nasycenie = noweNasycenie;
        }

        public override string ToString()
        {
            return $"Kolor:{Nasycenie.ToString().PadLeft(4, ' ')} - '{Nazwa}'";
        }
    }

    public class ZmieszanaFarba
    {
        public string NazwaMieszaniny { get; } 
        public List<Kolor> Skladniki { get; }

        public ZmieszanaFarba(string nazwa)
        {
            Skladniki = new List<Kolor>();
            this.NazwaMieszaniny= nazwa;
        }

        public ZmieszanaFarba DodajSkladnik(Kolor kolor)
        {
            bool czyDodany = false;

            foreach (var item in Skladniki)
            {
                if (item.Nazwa == kolor.Nazwa )
                {
                    item.ZmienNasycenie(item.Nasycenie + kolor.Nasycenie);
                    czyDodany = true;
                }
            }

            if (!czyDodany)
                Skladniki.Add(kolor);

            return this;
        }


    }

    public class LiniaPorownania
{
    public Kolor SkladnikKolor { get; private set; }
    public Dictionary<string, bool> Matryca;

    public LiniaPorownania(Kolor kolor)
    {
        Matryca = new Dictionary<string, bool>();
        this.SkladnikKolor = kolor;
    }

    public void DodajDoMatrycy(ZmieszanaFarba mieszanina)
    {
        string nazwaMieszaniny = mieszanina.NazwaMieszaniny;
        bool czyZawieraTenSkladnik = mieszanina.Skladniki.Any(o => (o.Nazwa == SkladnikKolor.Nazwa &&
                                                                    o.Nasycenie == SkladnikKolor.Nasycenie));
        Matryca.Add(nazwaMieszaniny, czyZawieraTenSkladnik);
    }
}

mój ViewModel

public class ViewModel : INotifyPropertyChanged
   {
       private ObservableCollection<ZmieszanaFarba> mieszaniny;
       public ObservableCollection<ZmieszanaFarba> Mieszaniny { get { return mieszaniny; } }


       public event PropertyChangedEventHandler? PropertyChanged;
       private void OnPropertyChanged(string nazwa)
       {
           if (PropertyChanged != null)
               PropertyChanged(this, new PropertyChangedEventArgs(nazwa));
       }

       private ZmieszanaFarba zaznaczonaFarba;
       public ZmieszanaFarba ZaznaczonaFarba {
           get { return zaznaczonaFarba; }
           set { zaznaczonaFarba = value;
               OnPropertyChanged(nameof(ZaznaczonaFarba)); } }

       private ObservableCollection<LiniaPorownania> matrycaPorownan;
       public ObservableCollection<LiniaPorownania> MatrycaPorownan  { get { return matrycaPorownan; } }

       public ViewModel()
       {
           Kolor zoltyA = new Kolor("ŻółtyA", 110);
           Kolor zoltyB = new Kolor("ŻółtyB", 175);
           Kolor niebieskiA = new Kolor("NiebieskiA", 77);
           Kolor niebieskiB = new Kolor("NiebieskiB", 135);
           Kolor czerwonyA = new Kolor("CzerwonyA", 95);
           Kolor czerwonyB = new Kolor("CzerwonyB", 225);
           Kolor bialyA = new Kolor("BiałyA", 200);

           ZmieszanaFarba zielonyA = new ZmieszanaFarba("ZielonyJasny")
               .DodajSkladnik(zoltyA)
               .DodajSkladnik(niebieskiA);
           ZmieszanaFarba zielonyB = new ZmieszanaFarba("ZielonyCiemny")
               .DodajSkladnik(zoltyB)
               .DodajSkladnik(niebieskiB);
           ZmieszanaFarba pomaranczA = new ZmieszanaFarba("PomaranczJasny")
               .DodajSkladnik(zoltyA)
               .DodajSkladnik(czerwonyB)
               .DodajSkladnik(bialyA);
           ZmieszanaFarba pomaranczB = new ZmieszanaFarba("PomaranczCiemny")
               .DodajSkladnik(zoltyB)
               .DodajSkladnik(czerwonyB);
           ZmieszanaFarba fiolet = new ZmieszanaFarba("Fioler")
               .DodajSkladnik(czerwonyA)
               .DodajSkladnik(niebieskiB);

           mieszaniny = new ObservableCollection<ZmieszanaFarba>() { zielonyA, zielonyB, pomaranczA, pomaranczB, fiolet };
           ZaznaczonaFarba = zielonyA;

           List<Kolor> unikalneKolory = new List<Kolor>();

           foreach (var item in mieszaniny)
               foreach (var item2 in item.Skladniki)
                   if (!unikalneKolory.Contains(item2))
                       unikalneKolory.Add(item2);

           unikalneKolory = unikalneKolory.OrderBy(o => o.Nazwa).ThenBy(o => o.Nasycenie).ToList();

           matrycaPorownan = new ObservableCollection<LiniaPorownania>();

           foreach (var kolor in unikalneKolory)
           {
               LiniaPorownania linia = new LiniaPorownania(kolor);
               foreach (var mieszanina in mieszaniny)
                   linia.DodajDoMatrycy(mieszanina);

               matrycaPorownan.Add(linia);
           }
       }
   }

i widok

<Window x:Class="WPF_multi_próby.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_multi_próby"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="150"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Name="ListaMieszanin" 
                    Grid.Row="0" Grid.Column="0" Orientation="Vertical">
            <TextBlock Text="Lista mieszanin:"/>
            <ListView ItemsSource="{Binding Mieszaniny}" SelectedItem="{Binding ZaznaczonaFarba}" Margin="10">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="Nazwa mieszaniny: "/>
                            <TextBlock Text="{Binding NazwaMieszaniny}" Width="120" FontWeight="Bold"/>
                            <TextBlock Text="Składników: " Margin="0,0,10,0"/>
                            <TextBlock Text="{Binding Skladniki.Count}" FontWeight="Bold"/>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackPanel>
        <StackPanel Name="SkladZaznaczonejMieszaniny"
                    Grid.Column="1" Grid.Row="0">
            <TextBlock Text="sklad zaznaczonej mieszaniny"/>
            <ListView ItemsSource="{Binding ZaznaczonaFarba.Skladniki}" Margin="10">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Nazwa farby" DisplayMemberBinding="{Binding Nazwa}" Width="100"/>
                        <GridViewColumn Header="Nasycenie barwy" DisplayMemberBinding="{Binding Nasycenie}" Width="100"/>
                    </GridView>
                </ListView.View>
            </ListView>
        </StackPanel>
        <StackPanel Name="MultiporownywarkaMieszanin"
                    Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Orientation="Vertical">
            <TextBlock Text="Multiporownywarka mieszanin"/>
            <ListView ItemsSource="{Binding MatrycaPorownan}" Margin="10" FontFamily="Cascadia Code" >
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Unikalny składnik" DisplayMemberBinding="{Binding SkladnikKolor}" Width="180"/>
                        <!-- nie wiem jak tu zrobic te dynamiczne kolumny -->
                    </GridView>
                </ListView.View>
            </ListView>
        </StackPanel>
    </Grid>
</Window>

obraz_2022-02-03_134445.png

0

research pokazuje ze niewiele jest rozwiazan na standardowych kontrolkach. Jako wyjscie awaryjne bede sie mierzyl z tym rozwiazaniem:
https://www.codeproject.com/Articles/37241/Displaying-a-Data-Matrix-in-WPF

Nie do konca w nim wszystko rozumiem, ale wygald ze dziala dokladnie tak jak tego potrzebuje.

Przyszedl mi jeszcze jeden pomysl na wykorzystanie ListView, ktorego sam nie jestem w stanie zweryfikowac bo nie jarze jeszcze do konca mozliwosci i ograniczen refleksji.

Czy mozna za pomoca refleksji utworzyc obiekty ktore beda tworzone na bazie mojego LiniaPorownania i refleksją bedą w tym nowym obiekcie dodawac property na podstawie kluczy z Dictionary? obiekt musialby miec tyle property ile jest wartosci w Dictionary.
tzn. ten obiekt mialby miec dodawane dynamicznie property za kazdym razem gdy dodam obiekt do porownaia, czyli dodam do Dictioary kolejna wartosc.

1

Robiłem, i to w wersji w której można zaznaczać i odznaczać "X". I z tego co widzę po kodzie to było sporo zabawy. Rozwiązanie oparłem na DataGrid, WPF nie pozwala na bindowania definicji kolumn, więc w code behind ręcznie tworzyłem kolumny, a także definiowałem bindingi, za każdym razem gdy zestaw kolumn się zmienił.

O refleksji zapomnij, nie jest do niczego potrzebna tutaj.

0

(Troche namieszalem bo w pierwszym poscie w modelu mam polskie nazwy a teraz przeszedlem na angielskie bo pytania zadawalem tez na angielskich forach)

cos zaczyna mi ruszac wlasnie na podstawie DataMatrixControl z CodeProject

tylko DataTamplates dla RowHeaders i ColumnHeaders to troche jeszcze czarna magia dla mnie i to teraz glownie idzie opornie.

skopiowalem i powoli dodaje swoje modele do tego projektu:
https://github.com/Varran/WpfMatrixDemo

głowny moj problem to jak z takiego obiektu:

public class MatrixColumnHeaderItem : MatrixItemBase
{
    public MatrixColumnHeaderItem(object columnHeader)
    {
        this.ColumnHeader = columnHeader;
    }

    public object ColumnHeader { get; private set; }
}

gdzie do naglowna kolumny jest bindowana nazwa tak:

<DataTemplate DataType="{x:Type mx:MatrixColumnHeaderItem}">
    <Border 
  Background="{StaticResource BackBrush}" 
  BorderBrush="{StaticResource BorderBrush}" 
  BorderThickness="{StaticResource BorderThickness}" 
  Padding="0,4"
  >
        <TextBlock 
    FontWeight="Bold"
    Foreground="{StaticResource HeaderForeground}" 
    HorizontalAlignment="Center" 
    Text="{Binding Path=ColumnHeader}" 
    VerticalAlignment="Center" 
    />
    </Border>
</DataTemplate>

a tak naprawde pod

object ColumnHeader { get; private set; }

kryje sie moj obiekt:

public class MixedPaint
    {
        public string PaintName { get; } 
        public List<ColorBase> Ingredients { get; }

        public MixedPaint(string name)
        {
            Ingredients = new List<ColorBase>();
            this.PaintName= name;
        }

        public MixedPaint AddIngredient(ColorBase color)
        {
            bool added = false;

            foreach (var item in Ingredients)
            {
                if (item.Name == color.Name )
                {
                    item.ChangeSaturation(item.Saturation + color.Saturation);
                    added = true;
                }
            }

            if (!added)
                Ingredients.Add(color);

            return this;
        }

        public override string ToString()
        {
            return $"ToString()={PaintName}";
        }
    }

i jak w takim bindingu dostac się do wlasciwosci mojego obiektu , tzn tak normalnie to bym rzutowal ale jak to zrobic w bindowaniu?

Jezeli mialbys czas, chec i zdrowie to tu sa moje wypociny
https://github.com/Varran/WpfMatrixDemo

to co sam dodalem do gotowca to tylko:

  • folder Farbki
  • PaintMatrix.cs
  • PaintMatrixTemplates.xaml

PS. Zaraz pewnie natkne sie na sciane jak w DataTemplates zastapic elipse CheckBoxem zbindowanym w dwie strony

0

Prawie mam, prosiłbym tylko podpowiedzi/pomoc z:

  • jak ustawić DataTemplate dla komórek DataGrid'a aby były to CheckBox'y - w viewmodelu wartości są ok, tylko w widoku ich nie widać.
  • jak ustawić DataTemplate dla nagłówka wierszy aby "RowHeaders" był zbindowany tak aby nie wyświetlać w nagłówku wiersza stringa tylko zagnieździć w nagłówku StackPanel i tam parę właściwości z "MatrixLine" bindować - da się jakoś MultiConverter zagnieżdżony zrobić?
  • jak ustawić DataTemplaet dla nagłówków kolumn żeby jest trochę wizualnie poprawić m.in. okręcić tekst o 90stopni

https://github.com/Varran/WPF_Multiporownywarki_Baza

To co do tej pory zrobiłem:
Model

public class ColorBase 
{
    public string Name { get; }   
    public int Saturation { get; private set; }  
    public ColorBase(string name, int saturation)
    {
        this.Name = name;
        this.Saturation = saturation;
    }

    public void ChangeSaturation(int newSaturation)
    {
        Saturation = newSaturation;
    }

    public override string ToString()
    {
        return $"ColorBase: {Saturation.ToString().PadLeft(4, ' ')} - '{Name}'";
    }
}

public class MixedPaint
{
    public string PaintName { get; } 
    public List<ColorBase> Ingredients { get; }

    public MixedPaint(string name)
    {
        Ingredients = new List<ColorBase>();
        this.PaintName= name;
    }

    public MixedPaint AddIngredient(ColorBase color)
    {
        bool added = false;

        foreach (var item in Ingredients)
            if (item.Name == color.Name )
            {
                item.ChangeSaturation(item.Saturation + color.Saturation);
                added = true;
            }

        if (!added)
            Ingredients.Add(color);

        return this;
    }        
}

public class MatrixLine
{
    public ColorBase ColorIngredient { get; private set; }
    public Dictionary<string, bool> Matrix;

    public MatrixLine(ColorBase color)
    {
        Matrix = new Dictionary<string, bool>();
        this.ColorIngredient = color;
    }

    public void AddToMatrix(MixedPaint mixedPaint)
    {
        string paintName = mixedPaint.PaintName;
        bool doesItContainIgredient = mixedPaint.Ingredients.Any(o => (o.Name == ColorIngredient.Name &&
                                                                    o.Saturation == ColorIngredient.Saturation));
        Matrix.Add(paintName, doesItContainIgredient);
    }
}

ViewModel

public class ViewModel : INotifyPropertyChanged
{
    private ObservableCollection<MixedPaint> mixedPaints;
    public ObservableCollection<MixedPaint> MixedPaints { get { return mixedPaints; } }

    public event PropertyChangedEventHandler? PropertyChanged;
    private void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(name));
    }

    private MixedPaint selectedMixedPaint;
    public MixedPaint SelectedMixedPaint
    {
        get { return selectedMixedPaint; }
        set { selectedMixedPaint = value;
            OnPropertyChanged(nameof(SelectedMixedPaint)); }
    }

    private ObservableCollection<MatrixLine> comparisonMatrix;
    public ObservableCollection<MatrixLine> ComparisonMatrix { get { return comparisonMatrix; } }

    public ViewModel()
    {
        ColorBase yellowA = new ColorBase("YellowA", 110);
        ColorBase yellowB = new ColorBase("YellowB", 175);
        ColorBase blueA = new ColorBase("BlueA", 77);
        ColorBase blueB = new ColorBase("BlueB", 135);
        ColorBase redA = new ColorBase("RedA", 95);
        ColorBase redB = new ColorBase("RedB", 225);
        ColorBase whiteA = new ColorBase("WhiteA", 200);

        MixedPaint greenA = new MixedPaint("GreenLight")
            .AddIngredient(yellowA)
            .AddIngredient(blueA);
        MixedPaint greenB = new MixedPaint("GreenDark")
            .AddIngredient(yellowB)
            .AddIngredient(blueB);
        MixedPaint orangeA = new MixedPaint("OrangeLight")
            .AddIngredient(yellowA)
            .AddIngredient(redB)
            .AddIngredient(whiteA);
        MixedPaint orangeB = new MixedPaint("OrangeDark")
            .AddIngredient(yellowB)
            .AddIngredient(redB);
        MixedPaint violet = new MixedPaint("Violet")
            .AddIngredient(redA)
            .AddIngredient(blueB);

        mixedPaints = new ObservableCollection<MixedPaint>() { greenA, greenB, orangeA, orangeB, violet };
        SelectedMixedPaint = greenA;

        List<ColorBase> uniqueColorsBase = new List<ColorBase>();

        foreach (var item in mixedPaints)
            foreach (var item2 in item.Ingredients)
                if (!uniqueColorsBase.Contains(item2))
                    uniqueColorsBase.Add(item2);

        uniqueColorsBase = uniqueColorsBase.OrderBy(o => o.Name).ThenBy(o => o.Saturation).ToList();

        comparisonMatrix = new ObservableCollection<MatrixLine>();

        foreach (var color in uniqueColorsBase)
        {
            MatrixLine line = new MatrixLine(color);
            foreach (var mixed in mixedPaints)
                line.AddToMatrix(mixed);

            comparisonMatrix.Add(line);
        }

        values = new ObservableCollection<ObservableCollection<bool>>();
        foreach (var item in comparisonMatrix)
        {
            ObservableCollection<bool> oc = new ObservableCollection<bool>();

            foreach (var item2 in item.Matrix)
            {
                oc.Add(item2.Value);
            }
            values.Add(oc);
        }
    }

    public ObservableCollection<MatrixLine> RowHeaders
    {
        get { return comparisonMatrix; }
    }

    public ObservableCollection<MixedPaint> ColumnHeaders
    {
        get { return mixedPaints; }
    }

    private ObservableCollection<ObservableCollection<bool>> values;
    
    public ObservableCollection<ObservableCollection<bool>> Values
    {
        get { return values; }
    }

Converter

public class MatrixToDataViewConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var myDataTable = new DataTable();
        var colums = values[0] as ObservableCollection<MixedPaint>;
        var rows = values[1] as ObservableCollection<MatrixLine>;
        var vals = values[2] as ObservableCollection<ObservableCollection<bool>>;

        myDataTable.Columns.Add("---");    

        if (colums != null)
            foreach (var value in colums)
                myDataTable.Columns.Add(value.PaintName);

        int index = 0;

        if (rows != null)
            foreach (MatrixLine row in rows)
            {
                var tmp = new string[1 + vals[index].Count()];
                var tmpboolean = new bool[1 + vals[index].Count()];
                vals[index].CopyTo(tmpboolean, 1);
                tmp[0] = row.ColorIngredient.Name;
                myDataTable.Rows.Add(tmp);
                index++;
            }
        
        return myDataTable.DefaultView;
    }

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

View

<Window x:Class="WPF_multi_próby.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_multi_próby"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="800">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <local:MatrixToDataViewConverter x:Key="MatrixToDataViewConverter"/>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="150"/>
            <RowDefinition Height="200"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Name="ListOfMixedPaint" 
                    Grid.Row="0" Grid.Column="0" Orientation="Vertical">
            <TextBlock Text="List of MixedPaint:"/>
            <ListView ItemsSource="{Binding MixedPaints}" SelectedItem="{Binding SelectedMixedPaint}" Margin="10">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="PaintName: "/>
                            <TextBlock Text="{Binding PaintName}" Width="120" FontWeight="Bold"/>
                            <TextBlock Text="IngradientCount: " Margin="0,0,10,0"/>
                            <TextBlock Text="{Binding Ingredients.Count}" FontWeight="Bold"/>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackPanel>
        <StackPanel Name="BOM"
                    Grid.Column="1" Grid.Row="0">
            <TextBlock Text="Ingredients of selected MixedPaint"/>
            <ListView ItemsSource="{Binding SelectedMixedPaint.Ingredients}" Margin="10">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Color Name" DisplayMemberBinding="{Binding Name}" Width="100"/>
                        <GridViewColumn Header="Color Saturation" DisplayMemberBinding="{Binding Saturation}" Width="100"/>
                    </GridView>
                </ListView.View>
            </ListView>
        </StackPanel>
        <StackPanel Name="MultiComparerOfPaints"
                    Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Orientation="Vertical">
            <TextBlock Text="Multicomparer of paints"/>
            <ListView ItemsSource="{Binding ComparisonMatrix}" Margin="10" FontFamily="Cascadia Code" >
                <ListView.Resources>

                </ListView.Resources>
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="Unique ingredient" DisplayMemberBinding="{Binding ColorIngredient}" Width="200"/>
                    </GridView>
                </ListView.View>
            </ListView>
        </StackPanel>


        <!--https://stackoverflow.com/questions/28018974/binding-matrix-arrays-to-wpf-datagrid-->
        <!--jak dodac checboxy w komorkach tego DataGrid-->
        <DataGrid Grid.Column="0" Grid.Row="2"  CanUserAddRows="False" >
            <DataGrid.ItemsSource>
                <MultiBinding Converter="{StaticResource MatrixToDataViewConverter}">
                    <Binding Path="ColumnHeaders"/>
                    <Binding Path="RowHeaders"/>
                    <Binding Path="Values"/>
                </MultiBinding>
            </DataGrid.ItemsSource>
        </DataGrid>
    </Grid>
</Window>

Przechwytywanie.PNG

1 użytkowników online, w tym zalogowanych: 0, gości: 1