Laden...

DataGrid an eine List mit variabler Spaltenzahl binden

Erstellt von NoSonOfMine vor einem Jahr Letzter Beitrag vor einem Jahr 1.063 Views
N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr
DataGrid an eine List mit variabler Spaltenzahl binden

Hallo Forum,

habe eine Klasse mit folgenden Properties:


    public class JahresTabelle
    {
        public String Kunde { get; set; }
        public String Projekt { get; set; }
        public List<Double> Volumen;
    }

Daraus erzeuge ich eine Liste:


        public List<JahresTabelle> UmsatzJahresTabelle = new List<JahresTabelle>();

Wobei Volumen eine undefinierte Anzahl von Elementen (Anzahl Jahre) darstellt. Das kann von 1 bis 40 alles sein. Wichtig: die Anzahl aller Elemente der Liste Volumen sind immer gleich viele, können aber von Prozedurdurchlauf zu Durchlauf variieren. Deshalb brauche ich eine Variabilität in der Darstellung:

Nun möchte ich diese Liste an mein WPF DataGrid binden. Habe allerdings keine Ahnung wie (oder ob das überhaupt) gehen kann.
Vielleicht habe ich auch den falschen Lösungsweg gewählt?

Danke und Gruß

4.939 Beiträge seit 2008
vor einem Jahr

Für ein einzelnes Element eines Arrays (bzw. einer List<T>) geht dies z.B. mit


{Binding Volumen[0]}

Du müßtest also (am besten per Schleife im Codebehind) die einzelnen Spalten erzeugen und entsprechend den Index zuweisen.
Ich weiß nicht, was passiert, wenn es den Index nicht gibt (also evtl. jeweils per Durchlauf mit passender Arraygröße erzeugen).

J
18 Beiträge seit 2021
vor einem Jahr

Mir ist nicht ganz klar, wie das Ergebnis aussehen soll oder darf:

Kunde 1 | Projekt 1 | [44,5] [2,3] [0,8]
Kunde 1 | Projekt 2 | [2,3] [4,5] [9,8] [3,3]
Kunde 2 | Projekt 1 | [9,8] [2,5]

oder eher so?

Kunde 1 | Projekt 1 | 44,5
Kunde 1 | Projekt 1 | 2,3
Kunde 1 | Projekt 1 | 0,8
Kunde 1 | Projekt 2 | 2,3
Kunde 2 | Projekt 2 | 4,5
Kunde 2 | Projekt 2 | 9,8
Kunde 2 | Projekt 2 | 3,3
usw...

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Hallo jbrown,

so wie im 1. Teil Deines Posts:

Spalte Kunde | Spalte Projekt | Volumen[0] | Volumen[1] | Volumen[2] | Volumen[...]

Hoffe so habe ich es verständlicher ausgedrückt.

Gruß und Danke

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Leider bin ich zu doof es umzusetzen. Probiere noch rum...

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Hallo,
mir ist klar, dass es so nicht funktionieren kann, habe aber keine bessere Idee. Deshalb hier mal der Code.
Irgendwie muss ich eine Structure oder Class mit Properties für die einzelnen Spalten von "Volumen" erstellen. Aber wie?
Ein nachträgliches Anfügen von Spalten macht hier einfach keinen Sinn.
Bin planlos...

Binden:


            Dgd_Umsatzvorschau.ItemsSource = ProjektBezeichnungsTabelle;


           ProjektBezeichnungsTabelle.Clear();

            foreach (Projekt Pro in ZKUmsatzPro)
            {
                JahresTabelle Tab = new JahresTabelle();
                Tab.Kunde = Pro.Kundenname;
                Tab.Projekt = Pro.Projektname;

                List<Double> Vol = new List<double>();
                for (int j = ProgStartJahr; j <= ProgEndJahr; j++)
                {
                    Vol.Add(...Abfrage...);
                }
                Tab.Volumen = Vol;
                ProjektBezeichnungsTabelle.Add(Tab);

            }

            for (int j = ProgStartJahr; j <= ProgEndJahr; j++)
            {
                DataGridTextColumn Spalte = new DataGridTextColumn();
                Spalte.Header = j.ToString();
                Binding SpalteBinding = new Binding(string.Format("ProjektBezeichnungsTabelle.Volumen[{0}]", j - ProgStartJahr));
                Spalte.Binding = SpalteBinding;
                Dgd_Umsatzvorschau.Columns.Add(Spalte);
            }

J
18 Beiträge seit 2021
vor einem Jahr

Ich verstehe immer noch nicht ganz wie das aussehen soll, aber mein erster Gedanke ging in diese Richtung (vor meinem eigentlichen Job schnell getippt 🙂


public class JahresTabelle : ObservableCollection<VolumeDetail>
        {
            public JahresTabelle(string kunde, string projectName)
            {
                Kunde = kunde;
                ProjektName = projectName;
            }

            public string Kunde { get; set; }
            public string ProjektName { get; set; }
        }


public class VolumeDetail
        {
            public VolumeDetail(int jahr, double[] volumen)
            {
                Jahr = jahr;
                Volumen = volumen.ToList();

                Summe = Volumen == null ? 0 : Summe = Volumen.Sum();
            }

            public int Jahr
            { get; private set; }

            public List<double> Volumen
            { get; private set; }

            public double Summe
            { get; private set; }
        }


            Random random = new Random();

            var projektBezeichnungsTabelle = new List<JahresTabelle>();
            var progStartJahr = 2022;
            var progEndJahr = 2030;

            //erstellt 50 Kunden mit einem Projekt
            for (int tempID = 0; tempID < 50; tempID++)
            {
                var data = new JahresTabelle("Kunde" + tempID, "Projekt" + tempID);
                for (int jahr = progStartJahr; jahr < progEndJahr; jahr++)
                {
                    var volumesOfYear = new double[]
                    {
                        random.NextDouble(),
                        random.NextDouble(),
                        random.NextDouble(),
                    };                    
                    data.Add(new VolumeDetail(jahr, volumesOfYear));                
                }
                projektBezeichnungsTabelle.Add(data);
            }


            //create columns
            DataGridTextColumn column;

            var columnCollection = new List<DataGridTextColumn>();
            {
                //kunde
                column = new DataGridTextColumn()
                {
                    Header = "Kunde",
                    Binding = new Binding("Kunde")
                };
                columnCollection.Add(column);

                //projekt
                column = new DataGridTextColumn()
                {
                    Header = "ProjektName",
                    Binding = new Binding("ProjektName")
                };
                columnCollection.Add(column);

                //dynamic year columns               
                for (int j = progStartJahr; j < progEndJahr; j++)
                {
                    column = new DataGridTextColumn()
                    {
                        Header = "Vol. im Jahr " + j,
                        Binding = new Binding(string.Format("[{0}].Jahr.Summe", j - progStartJahr))
                    };
                    columnCollection.Add(column);
                }

4.939 Beiträge seit 2008
vor einem Jahr

@NoSonOfMine: Laß ProjektBezeichnungsTabelle. weg beim Binding:


Binding SpalteBinding = new Binding(string.Format("Volumen[{0}]", j - ProgStartJahr));

Und vor dem Anfügen mußt du dann die Spalten neu erstellen (also so wie im Code von jbrown).

PS:
@jbrown: "[{0}].Jahr.Summe" ergibt so aber auch keinen Sinn (also wenn dann nur jeweils eine der drei Eigenschaften).

190 Beiträge seit 2012
vor einem Jahr

Also ich würde ja MVVM einsetzen. Und anstelle Datagrid ein Listview.


        public ObservableCollection<JahresTabelle> Tabelle { get; set; }


        <ListView ItemsSource="{Binding Tabelle}" >
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Kunde" DisplayMemberBinding="{Binding Kunde}"/>
                    <GridViewColumn Header="Projekt" DisplayMemberBinding="{Binding Projekt}"/>
                    <GridViewColumn Header="Volumen">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <ItemsControl ItemsSource="{Binding Volumen}">
                                    <ItemsControl.ItemsPanel>
                                        <ItemsPanelTemplate>
                                            <UniformGrid Rows="1"/>
                                        </ItemsPanelTemplate>
                                    </ItemsControl.ItemsPanel>
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <TextBlock Text="{Binding}" Margin="0,0,3,0"/>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>

Und dann noch etwas schön machen.

  • Wer lesen kann, ist klar im Vorteil
  • Meistens sitzt der Fehler vorm Monitor
  • "Geht nicht" ist keine Fehlermeldung!
  • "Ich kann programmieren" != "Ich habe den Code bei Google gefunden"

GidF

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Hallo zusammen,

hat etwas gebraucht bis ich dazu gekommen bin den Vorschlag umzusetzen. Dabei habe ich erst gemerkt, warum jbrown das Aussehen nicht nachvollziehen konnte. Mein Programmcode ist massiv mit meiner Beschreibung kollidiert. Habe nun den Code etwas angepasst. Leider bekomme ich das Binding nicht hin. Könnt Ihr mir hier noch einmal auf die Sprünge helfen? Hier der neue Code:


    public class JahresTabelle: ObservableCollection<VolumenDetails>
    {
        public JahresTabelle(String m_Kunde, String m_Projekt, String m_Vertriebler)
        {
            Kunde = m_Kunde;
            Projekt = m_Projekt;
            Vertriebler = m_Vertriebler;
        }
        public String Kunde { get; set; }
        public String Projekt { get; set; }
        public String Vertriebler { get; set; }
    }

    public class VolumenDetails
    {
        public VolumenDetails(int m_Jahr, double m_Volumen)
        {
            Jahr = m_Jahr;
            Volumen = m_Volumen;
        }
        public Int32 Jahr { get; private set; }
        public Double Volumen { get; private set; }
    }

und hier der Code zum Erzeugen und (leider Nicht-)Befüllen der Spalten. Wie muss das Binding aussehen? Ach ja, habe die ColumnCollection durch das DataGrid selbst ersetzt. Sollte doch trotzdem gehen?


        public List<JahresTabelle> ProjektUmsatzTabelle = new List<JahresTabelle>();


            foreach (Projekt Pro in ZKUmsatzPro)
            {
                JahresTabelle JaTab = new JahresTabelle(Pro.Kundenname, Pro.Projektname, Pro.Vertriebler);

                for (int j = ProgStartJahr; j < ProgEndJahr; j++)
                {
                    Double Vol = Pro.QuartalsZahlen.Where(x => x.Jahr == j).Sum(x => x.EntwicklungsU + x.ProduktU);
                    JaTab.Add(new VolumenDetails(j, Vol));
                }
                ProjektUmsatzTabelle.Add(JaTab);
            }

            DataGridTextColumn Spalte;
            {
                Spalte = new DataGridTextColumn() { Header = "Kunde", Binding = new Binding("ProjektUmsatzTabelle.Kunde") };
                Dgd_Umsatzvorschau.Columns.Add(Spalte);
                Spalte = new DataGridTextColumn() { Header = "Projekt", Binding = new Binding("ProjektUmsatzTabelle.Projekt") };
                Dgd_Umsatzvorschau.Columns.Add(Spalte);
                Spalte = new DataGridTextColumn() { Header = "Vertriebler", Binding = new Binding("ProjektUmsatzTabelle.Vertriebler") };
                Dgd_Umsatzvorschau.Columns.Add(Spalte);

                for (int j = ProgStartJahr; j <= ProgEndJahr; j++)
                {
                    Spalte = new DataGridTextColumn()
                    {
                        Header = j,
                        Binding = new Binding(string.Format("ProjektUmsatzTabelle.[{0}].Volumen", j - ProgStartJahr))
                    };
                    Dgd_Umsatzvorschau.Columns.Add(Spalte);
                } 
            }

Viiiiiiiieeeelen Dank

J
18 Beiträge seit 2021
vor einem Jahr

<DataGrid ItemsSource="{Binding Path=TestViewModel.ProjektUmsatzTabelle}"/>

Du bindest damit bereits an die Auflistung deines Typs "Jahrestabelle".
Analog zu Th69´s Vorgehen sollte es dann so in etwa gehen:


...
Spalte = new DataGridTextColumn() { Header = "Kunde", Binding = new Binding("Kunde") };
Spalte = new DataGridTextColumn() { Header = "Projekt", Binding = new Binding("Projekt") };
...
for (int j = ProgStartJahr; j ≤ ProgEndJahr; j++)
    {
        Spalte = new DataGridTextColumn()
        {
            Header = j,
            Binding = new Binding(string.Format("[{0}].Volumen", j - ProgStartJahr))
        };
        Dgd_Umsatzvorschau.Columns.Add(Spalte);
    }

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Hallo,

das Thema macht mich echt fertig. Hier sieht man den VB.net + Forms Hobbyprogrammierer, der sich jetzt in C# versucht. Komme mit der Bindung nicht klar. Habe kein MVVM gebastelt, sondern einfach nur ObservableCollections:



public ObservableCollection<JahresTabelle> ProjektUmsatzTabelle = new ObservableCollection<JahresTabelle>();


    public class JahresTabelle: ObservableCollection<VolumenDetails>
    {
        public JahresTabelle(String m_Kunde, String m_Projekt, String m_Vertriebler)
        {
            Kunde = m_Kunde;
            Projekt = m_Projekt;
            Vertriebler = m_Vertriebler;
        }
        public String Kunde { get; set; }
        public String Projekt { get; set; }
        public String Vertriebler { get; set; }
    }

    public class VolumenDetails
    {
        public VolumenDetails(int m_Jahr, double m_Volumen)
        {
            Jahr = m_Jahr;
            Volumen = m_Volumen;
        }
        public Int32 Jahr { get; private set; }
        public Double Volumen { get; private set; }
    }

im XAML Dokument habe ich folgende Bindung:


                                <DataGrid Name="Dtg_Umsatzvorschau" IsReadOnly="True" ItemsSource="{Binding Source=ProjektUmsatzTabelle}"/>

und die Spalten versuche ich so zu befüllen:


            foreach (Projekt Pro in ZKUmsatzPro)
            {
                Pro.QuartaleBerechnen();
                JahresTabelle JaTab = new JahresTabelle(Pro.Kundenname, Pro.Projektname, Pro.Vertriebler);

                for (int j = ProgStartJahr; j < ProgEndJahr; j++)
                {
                    Double Vol = Pro.QuartalsZahlen.Where(x => x.Jahr == j).Sum(x => x.EntwicklungsU + x.ProduktU);
                    JaTab.Add(new VolumenDetails(j, Vol));
                }
                ProjektUmsatzTabelle.Add(JaTab);

                var Spalte = new DataGridTextColumn() { Header = "Vertriebler",Binding = new Binding("Vertriebler") };
                    Dtg_Umsatzvorschau.Columns.Add(Spalte);

Ich sehe die Spaltenüberschriften und eine (nicht in Zusammenhang mit der Zeilenanzahl von ProjektUmsatzTabelle stehenden) Anzahl von Leerzeilen, allerdings ohne Inhalt. Könnt Ihr mir die korrekte Bindung im XAML und bei der Spalte nennen?

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Habe eine Lösung gefunden. Habe anstatt im XAML den Programmcode ergänzt mit:


            Dtg_Umsatzvorschau.ItemsSource = ProjektUmsatzTabelle;

Was mir noch nicht gefällt, ist, dass im DataGrid nun 2x die Spalten stehen: die ersten Spalten (Kunde, Projekt, Vertriebler, [1 .. x]Jahre) sind programmtechnisch erzeugt.
Danach werden allerdings noch einmal die Spalten (Kunde, Projekt, Vertriebler, Collection) angehängt. Kann diese auch im ersten Durchgagn der Erzeugung durch RemoveAt() nicht löschen.

Zusätzlich: wie bekomme ich hier die Daten formatiert?


         Spalte = new DataGridTextColumn()
                    {
                        Header = j,
                        Width = 80,
                        Binding = new Binding(string.Format("[{0}].Volumen", j - ProgStartJahr))
                    };

Ich möchte 1.000er Punkte setzen und alle Nachkommastellen abschneiden. Zusätzlich den Spalteninhalt rechtsbündig ausrichten. Wenn ich das richtig gelesen habe, muss das alles in die Binding -Zeile rein.

J
18 Beiträge seit 2021
vor einem Jahr

Nicht einfach aufgeben 🙂

Die DataGrid Eigenschaft AutoGenerateColumns sollte das erste Problem lösen.


<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding ProjektUmsatzTabelle}"/>

Diese kannst du natürlich auch so setzen:


Dtg_Umsatzvorschau.AutoGenerateColumns=false;
Dtg_Umsatzvorschau.ItemsSource = ProjektUmsatzTabelle;

Formatierung:
Standard numeric format strings

Wenn du das erledigt hast, ist dies eine Variante für das nächste Problem:


<DataGrid.ColumnHeaderStyle>
    <Style TargetType="{x:Type DataGridColumnHeader}">
        <Setter Property="FontWeight"  Value="Bold" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="HorizontalContentAlignment" Value="Center" />
    </Style>
</DataGrid.ColumnHeaderStyle>

Du solltest neben all dem aber versuchen zu verstehen, warum deine Bindung in XAML nicht klappt, die im Codebehind jedoch schon.

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Hallo und Danke für die tatkräftige Unterstützung.


<DataGrid AutoGenerateColumns="False" />

hat hier geholfen.

Habe mich während dem Einwühlen in die Programmierung an der ein- oder anderen Stelle gefragt was die Entwickler von Microsoft da geraucht haben oder was muss ich rauchen muss, um die Denkstruktur zu verstehen.

Da ich ja nicht alle Spalten rechtsbündig formatiert haben will, habe die Formatierung zwischenzeitlich so gelöst:


                Style s = new Style();
                s.Setters.Add(new Setter(TextBlock.TextAlignmentProperty, TextAlignment.Right));
               
                for (int j = ProgStartJahr; j <= ProgEndJahr; j++)
                {
                    Spalte = new DataGridTextColumn()
                    {
                        Header = j,
                        Width = 80,
                        Binding = new Binding(string.Format("[{0}].Volumen", j - ProgStartJahr)),
                        CellStyle = s
                    };
                    Dtg_Umsatzvorschau.Columns.Add(Spalte);
                }

Wer kann denn auch erahnen, dass die Textausrichtung in Cellstyle.TextBlock versteckt ist?

Die Formatierung mit Punkten mache ich nun eben früher und übergebe schon fertig formatierte Strings in die DataGridTextColumns.

P
257 Beiträge seit 2010
vor einem Jahr
MVVM-Lösung

Hallo NoSonOfMine!

Warum in aller Welt möchtest du denn deine DataGrid-Spalten manuell erstellen???

In deinem DataModel hast du in der Klasse: JahresTabelle alle Eigenschaften, inclusive der JahresVolumen-Liste, definiert und brauchst nun diese nur noch an deine View binden. Für die einfachen Eigenschaften definierst du im XAML eine DataGridTextColumn (mit ein wenig Formatierung) und für die Liste, die du horizontal darstellen möchtest, bietet sich eine DataGridTemplateColumn mit einem DataTemplate das ein UniformGrid benutzt, an. Diese Lösung hat dir Wilfried bereits vorgeschlagen!

Mit 39 XAML-Zeilen für das gesamte DataGrid (incl. Formatierung) hast du dein gesamtes Problem erschlagen!

Am Anfang ist es etwas ungewohnt sich in MVVM einzuarbeiten, aber wenn du WPF nutzen möchtest, ist das der effektivste und rubusteste Weg Windows Anwendungen zu erstellen. (Zumal du quasi ja schon mit einem DataModel arbeitest.)

Ich habe für dein Problem mal eine kleine Aplikation geschrieben. Die längste Zeit habe ich für die Erstellung von sinnvollen Beispiel-Daten gebraucht!

Zuerst einmal das Ergebnis: (siehe Anhang)

Die View:


<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d" WindowStartupLocation="CenterScreen"
        Title="Umsatzübersicht" Height="600" Width="1000">
    <Window.DataContext>
        <local:VMUmsatzuebersicht/>
    </Window.DataContext>
    <Grid>
        <DataGrid CanUserAddRows="False" IsManipulationEnabled="False" IsReadOnly="True" Padding="10" AutoGenerateColumns="False"
                  ItemsSource="{Binding ProjektUmsatzTabelle }">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Kunde" Binding="{Binding Kunde}"/>
                <DataGridTextColumn Header="Projekt" Binding="{Binding Projekt}"/>
                <DataGridTextColumn Header="Vertriebler" Binding="{Binding Vertriebler}"/>
                <DataGridTextColumn Header="Volumen [Gesamt]" Binding="{Binding GesamtVolumen, StringFormat=N2, ConverterCulture=de-DE }"/>
                <DataGridTemplateColumn Header="Volumen [Jahr(e)]">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ItemsControl ItemsSource="{Binding JahresVolumen}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <UniformGrid Rows="1"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal" Margin="0,0,10,0">
                                            <TextBlock Text="{Binding Jahr}" Margin="0,0,3,0" FontWeight="Bold" />
                                            <TextBlock Text="->" Margin="5,0,5,0" />
                                            <Border BorderBrush="{x:Null}" Width="50">
                                                <TextBlock Text="{Binding Volumen, StringFormat=N2, ConverterCulture=de-DE }" HorizontalAlignment="Right"/>
                                            </Border>
                                        </StackPanel>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
            <DataGrid.ColumnHeaderStyle>
                <Style TargetType="{x:Type DataGridColumnHeader}">
                    <Setter Property="FontWeight"  Value="Bold" />
                    <Setter Property="HorizontalAlignment" Value="Left" />
                    <Setter Property="HorizontalContentAlignment" Value="Left" />
                </Style>
            </DataGrid.ColumnHeaderStyle>
        </DataGrid>
    </Grid>
</Window>

Zu beachten ist die Instanzierung des ViewModels das als DataContext dem Window zur Verfügung gestellt wird!!!


<Window.DataContext>
    <local:VMUmsatzuebersicht/>
</Window.DataContext>

Das DataGrid kannst du so 1:1 in deine Anwendung kopieren.

Jetzt brauchst du nur noch eine Klasse für das ViewModel zu erstellen und in dieser deine Liste der JahresTabellen (ProjektUmsatzTabelle) als öffentliche Eigeschaft zu instanzieren.
Fertig!

Damit die Zusammenhänge leichter nachvollzogen werden können, poste ich mal die komplette CodeBehind-Datei, in der ich das ViewModel und dein DataModel (welche bei größeren Projekten in seperate Dateien ausgelagert werden) befinden.


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    #region ViewModel
    ...

Hoppla ich kann hier nur 8000 Zeichen posten da muss ich die CodeBehind in eine andere Antwort packen ...

P
257 Beiträge seit 2010
vor einem Jahr

So, hier der zweite Teil ...

Die CodeBehind incl. View- und Data-Model. (Um exakt zu sein ... Mir ist bewusst, dass das DataModel kein echtes DataModel darstellt sondern nur zwei Klassen die eine Daten-Struktur modelieren.)


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    #region ViewModel
    public class VMUmsatzuebersicht : INotifyPropertyChanged
    {
        public ObservableCollection<JahresTabelle> ProjektUmsatzTabelle { get; set; }           // Liste der Daten deklarieren

        #region Konstruktor
        public VMUmsatzuebersicht()
        {
            ProjektUmsatzTabelle = GetZufallsDaten();                                           // Zufalls-Beispieldaten erzeugen
        }
        #endregion

        #region Oberfläche aktualisieren  (Achtung: Die akt. Klasse muss von der Schnittstelle: INotifyPropertyChanged abgeleitet sein!!!)
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        #region Methoden des VM
        private ObservableCollection<JahresTabelle> GetZufallsDaten(int anzahl = 50, int projekteProKunde = 5, int anzahlVertriebler = 7, int startJahr = 2022, int endJahr = 2030)
        {
            Random random = new Random();

            ObservableCollection<JahresTabelle> projektBezeichnungsTabelle = new ObservableCollection<JahresTabelle> ();    // Liste der Jahres-Tabellen initalisieren
            int aktKunde = 0;                                                       // Akt. Kunde (Wechsel entsprechend Projekte/Kunde)
            int aktProjekt = 0;                                                     // Akt. Projekt (Pro Kunde)

            for (int n = 0; n < anzahl; n++)                                        // (Zufalls) JahresTabellen in der gewünschten Anzahl erstellen
            {
                List<VolumenDetails> volDetails = new List<VolumenDetails>();       // Liste der Volumendetails initalisieren
                double gesamtVolumen = 0;                                           // Gesamtvolumen initalisieren
                if (n % projekteProKunde == 0 || n == 0)                            // Kundenwechsel?
                {
                    aktKunde++;                                                     // Kunde inkrementieren
                    aktProjekt = 0;                                                 // Projekt zurücksetzen
                }
                aktProjekt++;                                                       // Projekt inkrementieren
                for (int jahr = startJahr; jahr < endJahr; jahr++)                  // (Zufalls) Jahres-Volumen im gewünschten Zeitraum erstellen
                {
                    double jahresVolumen = random.NextDouble() * 10000;             // (Zufalls) Jahres-Volumen erstellen
                    gesamtVolumen += jahresVolumen;                                 // Gesamtvolumen aktualisieren
                    volDetails.Add(new VolumenDetails(jahr, jahresVolumen));        // Volumen-Details der Liste der Volumen-Details hinzufügen
                }
                JahresTabelle data = new JahresTabelle("Kunde" + aktKunde.ToString(), "Projekt" + aktProjekt.ToString(), "Vertriebler" + random.Next(1, anzahlVertriebler).ToString(), gesamtVolumen, volDetails);

                projektBezeichnungsTabelle.Add(data);
            }
            return projektBezeichnungsTabelle;
        }
        #endregion
    }
    #endregion

    #region "DataModel"
    public class JahresTabelle
    {
        public string Kunde { get; set; }
        public string Projekt { get; set; }
        public string Vertriebler { get; set; }
        public double GesamtVolumen { get; set; }
        public List<VolumenDetails> JahresVolumen { get; set; }

        public JahresTabelle(string kunde, string projekt, string vertriebler, double gesamtVolumen, List<VolumenDetails> jahresVolumen)
        {
            Kunde = kunde;
            Projekt = projekt;
            Vertriebler = vertriebler;
            GesamtVolumen = gesamtVolumen;
            JahresVolumen = jahresVolumen;
        }
    }

    public class VolumenDetails
    {
        public int Jahr { get; private set; }
        public double Volumen { get; private set; }

        public VolumenDetails(int jahr, double volumen)
        {
            Jahr = jahr;
            Volumen = volumen;
        }
    }
    #endregion

}

Bei dieser einfachen Anwendung benötigst du die Implementierung der INotifyPropertyChanged-Schnittstelle noch nicht. Da es aber ständig zur Aktualisierung der Oberfläche bei Eigenschaftsänderungen benötigt wird, füge ich sie grundsätzlich hinzu.

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Hallo perlfred,

super lieben Dank für den Support und Deine Lösung inklusive der Erklärung.

Hatte mich jetzt mehrere Tage nicht mehr um das Projekt gekümmert, da ich mit anderen Jahresendspurtthemen meiner eigentlichen beruflichen Tätigkeit mehr als gut eingespannt war.
Das Durchdringen und überarbeiten wird sicherlich ein Teil meines Weihnachtsurlaubs.
Melde mich sicher wieder bei Fragen.

Wünsche Euch allen schon mal eine ruhige und besinnliche Weihnacht.

4.939 Beiträge seit 2008
vor einem Jahr

Bedenke aber, daß der Code von perlfred die Jahresvolumen alle in einer DataGrid-Spalte darstellt, nicht wie bei deiner bisherigen Lösung in verschiedenen Spalten.

P
257 Beiträge seit 2010
vor einem Jahr
Variante 2

Hallo Th69, Hallo NoSonOfMine!

Zuerst einmal Th69 du hast natürlich Recht! Die Lösung geht auch inhaltlich etwas an der Aufgabenstellung der Überschrift des Diskussionsbeitrages vorbei, aber vielleicht benötigt der TS die Spalten ja nur optisch!? Und dann ist diese Lösung Aufwandsseitig wohl nicht mehr zu toppen. 🙂

Aber es geht natürlich auch anders ... deshalb hier der zweite Lösungsansatz.

Bei diesem benutze ich Value-Converter um die Werte der JahresVolumen den einzelnen Spalten des DataGrid's zuzuordnen. Die Zuordnung wird über eine Nummer, die als Convert-Parameter angegeben wird, ausgeführt.

Die Wirkungsweise anhand eines ValueConverter: (Dieser ValueConverter "erzeugt" die Spalten-Überschriften anhand des Jahres des ersten Eintrages der Jahres-Tabelle.)


[System.Windows.Data.ValueConversion(typeof(ObservableCollection<JahresTabelle>), typeof(string))]
public class HeaderBezeichnung_Converter : System.Windows.Data.IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value != DependencyProperty.UnsetValue && value != null && value is ObservableCollection<JahresTabelle> jahresTabelle)    // Value enthält gültige Werte            
            if (System.Convert.ToInt32(parameter) > -1)
            {
                int SpaltenNr = System.Convert.ToInt32(parameter);                                              // Spalten-Nummer 
                if (jahresTabelle.Count > 0 && jahresTabelle[0].JahresVolumen.Count >= SpaltenNr)               // Spalte vorhanden?
                    return jahresTabelle[0].JahresVolumen[SpaltenNr].Jahr.ToString();                           // Jahr (des 1. Eintrages) als Spaltenüberschrift zurückgeben
            }
        return string.Empty;                                                                       // Wenn keine gültigen Werte vorliegen, "Leere" Spaltenbezeichnung zurückgeben
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException(); // Back-Funktion nicht implementiert 
    }
}

Nach dem validieren der JahresTabelle und des Parameter-Wertes prüfe ich, ob die JahresVolumen-Liste einen Eintrag in der Position besitzt, die ich als Parameter spezifiziert habe. Wenn ja gebe ich das Jahr zurück, ansonsten eine leere Bezeichnung.

Als Beispiel: Ich möchte in der DataGrid-Spalte das Jahr des 2. Umsatz-Eintrages, als Header-Text, darstellen:


<!-- Umsatz-Spalte 2 -->
    <DataGridTextColumn.Header>
        <TextBlock Text="{Binding DataContext.ProjektUmsatzTabelle, RelativeSource={RelativeSource FindAncestor,
                            AncestorType=Window}, Converter={StaticResource HeaderBez_Konv}, ConverterParameter=2}" />
    </DataGridTextColumn.Header>

Ich hole mir über den DataContext des Window (=ViewModel) die ProjektUmsatzTabelle und spezifiziere über den ConvertParameter den wievielten Eintrag der JahresVolumen-Liste ich ansprechen möchte.

Adäquate ValueConverter (Zuordnung der JahresVolumen-Einträge über die Parameter-Nummer) benutze ich für die Darstellung der Volumen-Werte und für die Steuerung der Sichtbarkeit der DataGrid-Spalten.

Hier ein komplettes Beispiel für eine Spalte:


<!-- Umsatz-Spalte 2 -->
<DataGridTextColumn Binding="{Binding JahresVolumen, Converter={StaticResource Jahreswerte_Konv}, ConverterParameter=2}"
                    CellStyle="{StaticResource DGCellStyle}" MinWidth="0"
                    Width="{Binding Source={x:Reference DymmyCol2}, Path=ActualWidth}">
    <DataGridTextColumn.Header>
        <TextBlock Text="{Binding DataContext.ProjektUmsatzTabelle, RelativeSource={RelativeSource FindAncestor,
                            AncestorType=Window}, Converter={StaticResource HeaderBez_Konv}, ConverterParameter=2}" />
    </DataGridTextColumn.Header>
</DataGridTextColumn>

An dieser Stelle gibt es zwei "Stolpersteine"!

  1. Die Steuerung der Sichtbarkeit der DataGrid-Spalten kann nicht über einen ValueConverter, der an die Visibility-Eigenschaft gebunden wird, ausgewertet werden, da ausgeblendete DataGrid-Spalten Binding-Fehler verursachen. Genau aus diesem Grund wurde die Sichbarkeit über einen ValueConverter der die Spaltenbreite steuert, gesteuert.
    Bei diesem Detail gleich noch der Hinweis, dass standardmäßig die MinWidth-Eigenschaft einer DataGrid-Spalte mit dem Wert 20 Pixel vorbelegt ist, der überschrieben werden muss.

  2. DataGrid-Spalten sind abstrakte Objekte die nicht in der logischen oder visuellen Struktur eines Fensters erscheinen. Man kann keine Bindings direkt an die Width-Eigenschaft der DG-Spalte binden! (Das Binding wird niemals getriggert / ausgelöst!) Deshalb habe ich (entsprechend diesem Beitrag) die Width-Eigenschaft der DataGrid-Spalte über eine X-Referenz an die ActualWidth-Eigenschaft des Dummy-Objektes gebunden.


    <Grid>
        <FrameworkElement x:Name="DymmyCol0" Width="{Binding ProjektUmsatzTabelle, Converter={StaticResource SpaltenBreite_Konv}, ConverterParameter=0}"/>
        <FrameworkElement x:Name="DymmyCol1" Width="{Binding ProjektUmsatzTabelle, Converter={StaticResource SpaltenBreite_Konv}, ConverterParameter=1}"/>
        <FrameworkElement x:Name="DymmyCol2" Width="{Binding ProjektUmsatzTabelle, Converter={StaticResource SpaltenBreite_Konv}, ConverterParameter=2}"/>
        <FrameworkElement x:Name="DymmyCol3" Width="{Binding ProjektUmsatzTabelle, Converter={StaticResource SpaltenBreite_Konv}, ConverterParameter=30}"/>
        <FrameworkElement x:Name="DymmyCol4" Width="{Binding ProjektUmsatzTabelle, Converter={StaticResource SpaltenBreite_Konv}, ConverterParameter=4}"/>
        <FrameworkElement x:Name="DymmyCol9" Width="{Binding ProjektUmsatzTabelle, Converter={StaticResource SpaltenBreite_Konv}, ConverterParameter=9}"/>
        <DataGrid CanUserAddRows="False" IsManipulationEnabled="False" IsReadOnly="True" Padding="10" AutoGenerateColumns="False" ...">

Da ich der Zeichenbegrenzung dieses Forums wieder bedrohlich nahe komme, habe ich das kleine Beispiel-Projekt hier für dich zum Nachvollziehen abgelegt.

In dem Bild sieht man das DataGrid zur Laufzeit. In den Spalten 3 und 9 habe ich die ConvertParameter so eingestellt, dass diese ausgeblendet werden. In deiner Anwendung müsstest du nun so viele Spalten implementieren (und konfigurieren), wie maximal benötigt werden könnten. Wenn die JahresVolumen-Liste weniger Einträge enthält, werden sie über den Spalten-Breite-Konverter ausgeblendet.

Also falls du die Spalten wirklich einzeln brauchst ... aber bedenke, sortieren kannst du sie so auch noch nicht, da dafür wieder ein spezieller Comparer implementiert werden müsste!

Hinweis 2: In meinem verlinkten Beispiel-Projekt ist natürlich auch die Variante 1 (MainWindow) komplett enthalten.

N
NoSonOfMine Themenstarter:in
17 Beiträge seit 2022
vor einem Jahr

Hallo alle zusammen,

einfach cool, wieviel Zeit Ihr in die Lösung meines Problems investiert habt. Ohne Euch wäre ich sicher nicht zu einer funktionierenden Lösung gekommen.
Die Dummy-Spalten-Lösung habe ich konzeptionell verstanden und setze ich in einem anderen Kontext so schon um. Hatte nur nicht daran gedacht, dass es hier auch gehen könnte.

Wahrscheinlich bin ich noch von meinen ersten Programmierversuchen zu Zeiten eines Apple 2 mit Compiler-Pascal verseucht: Bevorzuge eine Lösung dann, wenn die Laufzeit bzw. der Anzahl der Codezeilen am geringsten ist. Und die scheint mir bei meiner manuellen Generierung der Spalten ideal. Werde die Lösung erst einmal so verwenden.

Vielen Dank noch einmal für Euere Unterstützung.