Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Portal
  • |
  • Mitglieder
Beiträge von GeneVorph
Thema: System.Management.ManagementException - "nicht gefunden"
Am im Forum: Rund um die Programmierung

Hallo Wilfried, danke für deinen Beitrag.

Ich habe mir noch einmal die Dokumentation zu Gemüte geführt (hier und hier) - was aber nur mehr zur Verwirrung beiträgt. Ich kann mir zwar übersetzen was da steht, aber was inhaltlich der Unterschied zwischen "relates a universal serial bus (USB) controller and the CIM_LogicalDevice instance connected to it" und "manages the capabilities of a universal serial bus (USB) controller" ist, bleibt mir damit ein Rätsel (für die Zustände eines Controllers ist eine andere Klasse zuständig als für die Kommunikation zwischen controller und device? Worin liegt dann der Unterschied zwischen Controller und Device?).

Zitat
Ich hab bei mir auf dem PC grade mal geschaut, da gibt es weder bei Win32_USBControllerDevice noch bei Win32_USBController das Property "ProductName".
Stimmt, das habe ich nach dem Post von Th69 ebenfalls herausgefunden. Wie gesagt, das hatte ich in einem Post gefunden und (ganz unschuldig) statt DeviceId eingesetzt.

Tatsächlich funktioniert für meinen ursprünglichen Code "Antecedent", wenn ich die Win32_USBControllerDevice-Klasse ansteuere.

Aber ich bin ein Schrittchen weiter:
Mit ManagementObjectSearcher(@"Select * From Win32_USBControllerDevice")) und cmbDevices.Items.Add((string)device.GetPropertyValue("Antecedent")) bekomme ich 37 Einträge in meiner ComboBox angezeigt (gebs jetzt direkt in der ComboBox aus).
Beispiel (erster Eintrag):
\\ARBEITS-PC\root\cimv2:Win32_USBController.DeviceID="PCI\\VEN_1033&DEV_0194&SUBSYS_84131043&REV_04\\4&DDEC341&0&00E1"

Mit ManagementObjectSearcher(@"Select * From Win32_USBController")) und cmbDevices.Items.Add((string)device.GetPropertyValue("DeviceId")) bekomme ich 4 in meiner ComboBox angezeigt.
Beispiel (erster Eintrag):
PCI\VEN_1033&DEV_0194&SUBSYS_84131043&REV_04\4&DDEC341&0&00E1

Tatsächlich weiß ich jetzt überhaupt nicht, was ich da angezeigt bekomme^^

Erwartet hätte ich z. B. im zweiten Fall die Id's der aktuell angeschlossenen Devices (Keyboard, Mouse) erwartet - ich bekomme aber 4 Einträge. Wenn ich außerdem zusätzlich einen USB-Stick anschließe sind es immer noch 4 Einträge, und zwar genau dieselben wie zuvor.

Die 37 Einträge aus dem ersten Fall erschließen sich mir hingegen überhaupt nicht: sind das nun Geräte, Ports, Geräte und Ports und wenn ja, wie komme ich dann um Himmelswillen auf 37??

BTW: Danke für den Topp zum WMI-CodeCreator - sobald ich kann, werde ich mir den mal genauer ansehen.

Thema: System.Management.ManagementException - "nicht gefunden"
Am im Forum: Rund um die Programmierung

Zitat
Wie kommst du auf "ProductName"?
Zuerst hatte ich DeviceID, dann ProductName, nachdem ich es bei stackoverflow in einem anderen thread gefunden hatte. Beides funktionierte nicht.

Ich habe jetzt folgendes verändert:
aus


var searcher = new ManagementObjectSearcher(@"Select * From Win32_USBControllerDevice"))

wurde jetzt


var searcher = new ManagementObjectSearcher(@"Select * From Win32_USBController"))

Bei "ProductName" stürzt die App weiterhin ab.

Wenn ich nun "Name" eingebe, läuft die Schleife ohne Abbruch durch. Leider ist das Problem damit nicht gelöst (s. Screenshot):
Diesmal finden sich in der devicesCollection nur noch 4 Einträge (ehemals 35, wenn man "ProductName" eingibt): wenn ich nach der Schleife einen BreakPoint setze, dann sieht man unter Ergebnisansicht der devicesCollection dieselbe Fehlermeldung wie zuvor: "Fehler = Die Funktionsauswertung wurde deaktiviert, weil bei einer vorhergehenden Funktionsauswertung das Timeout überschritten wurde."

Also nach meinem Verständnis bedeutet das:

- bei Win32_USBControllerDevice findet das Programm vor der Schleife 35 Einträge; ich kann jedoch nicht feststellen, was genau, denn beim ersten Schleifendurchlauf stürzt die App ab.
- bei Win32_USBController wird die Schleife durchlaufen - 4 Einträge werden gefunden, aber ich kann dennoch nicht feststellen von was, denn ich finde keine Möglichkeit auf die einzelnen Items zuzugreifen.

Kurz zu WMI-Tool: ich hatte gestern abend noch WbemTest unter Windows 10 gestartet - ich meine mich zu erinnern, dass z. B. "DeviceID" im USBControllerDevice-Namespace zu finden war: trotzdem der Absturz. Das eigentiche Problem dürfte m. E. vor der Schleife zu finden sein. Ich werde dennoch mit SimpleWMIView nochmal mein Glück versuchen.

Vielen Dank soweit.

EDIT: im screenshot ist der breakpoint noch vor einstieg in die schleife - unter ergebnisansicht steht "Zeitüberschreitung Funktionsevaluierug". Setzt man den BP nach der Schleife, kommt die Meldung von oben.

Thema: System.Management.ManagementException - "nicht gefunden"
Am im Forum: Rund um die Programmierung

Hallöchen,

vorweg: ich bin mir nicht sicher, ob mein Post hier im richtigen Teil des Forums gelandet ist! Falls nicht: sorry und bitte ggf. verschieben! Danke.

Ich möchte in einer WindowsForms-App alle USB-Geräte in einer Collection auflisten. Dabei habe ich mich an diesem Beispiel orientiert. Meinen Code habe ich abgeändert, so dass er wie folgt ausschaut:


private void frmMainDisplay_Load(object sender, EventArgs e)
        {
            List<string> devices = new List<string>();
            ManagementObjectCollection devicesCollection;

            using (var searcher = new ManagementObjectSearcher(@"Select * From Win32_USBControllerDevice"))
                devicesCollection = searcher.Get();

            foreach (var device in devicesCollection)
            {
                devices.Add((string)device.GetPropertyValue("ProductName"));
            }

            devicesCollection.Dispose();
        }

Da ich an den Devices und nicht an den Hubs interessiert bin, suche ich in Win32_USBControllerDevices.

Wenn ich meinen Code ausführe, stürzt er in der foreach-Schleife ab. Ich erhalte folgende Fehlermeldung:
Fehler
System.Management.ManagementException: "nicht gefunden"
Diese Ausnahme wurde ursprünglich von dieser Aufrufliste ausgelöst:
[Externer Code]
VGACaptureDevice.frmMainDisplay.frmMainDisplay_Load(object, System.EventArgs) in Form1.cs
[Externer Code]

Beim Debugging habe ich festgestellt, dass im Ausgagbefenster folgendes angezeigt wird:
Zitat
Ausnahme ausgelöst: "System.Management.ManagementException" in System.Management.dll
Ein Ausnahmefehler des Typs "System.Management.ManagementException" ist in System.Management.dll aufgetreten.
Nicht gefunden

Durch setzen eines Break-Points habe ich außerdem festgestellt, dass devicesCollection vor Ausführen der foreach-Schleife bei den Properties unter Count = 35 aufweist (ich nehme an, das sind sämtliche USB-devices plus hubs plus BlueTooth plus ?), ich diese 35 Items aber nicht näher Anschauen kann: unter Ergebnisansicht steht: "Fehler = Die Funktionsauswertung wurde deaktiviert, weil bei einer vorhergehenden Funktionsauswertung das Timeout überschritten wurde. Sie müssen die Ausführung fortsetzen, um die Funktionsauswertung wieder zu aktivieren." Tue ich das, stürzt die App allerdings wie zuvor beschrieben ab.

Um wirklich sämtliche Fehlerquellen auszuschließen, hier mal mein Vorgehen bis zu diesem Punkt: ich musste nämlich feststellen, dass es nicht genügt den Namespace System.Management per using hinzuzufügen; man muss auch händisch einen Verweis setzen. Dies habe ich getan mit
- Rechtsklick auf mein Projekt --> Hinzufügen --> Verweis --> Häkchen gesetzt bei System.Management und System.Management.Instrumentation
- OK geklickt.

Eine Google-Suche nach dem Fehler brachte zu Tage, dass möglicherweise das WMI-Repository von Windows beschädigt sein könnte. Ich habe dazu in der Powershell winmgmt /verifyrepository ausgeführt. "Leider" war das Ergebnis "Das Repository ist konsistent".

Damit gehen mir leider derzeit die Ideen aus - mein Code besteht lediglich aus den paar Zeilen weiter oben und die Fehlermeldung lässt mich leider auch im Regen stehen. Hat jemand eine Idee, was da falsch laufen könnte?

Vielen Dank.
Gruß
vorph

Thema: Können Libraries beim Erstellen einer Solution eingebunden werden?
Am im Forum: Entwicklungs- und Laufzeitumgebung (Infrastruktur)

Zitat
Ich bin kein Anwalt, aber nach meinem Verständnis begehst Du damit ein Lizenzvergehen.
Yieks - das sei mir fern :-) !

Danke für die ausführlichen Erläuterungen, Abt; und wieder ein bisschen schlauer ;-)
Zitat
Statisches Linken ist einfach nicht erlaubt.
Na ja, so gesehen...passt auch irgendwie nicht zum dem d in dll, oder?

Zitat
[...]dazu mußt du den Ordner nur in der app.config bekannt machen, s. z.B. Loading .NET Assemblies out of Seperate Folders (Stichwort: privatePath).
Genau so habe ich es jetzt auch gemacht. Vielen Dank.

Gut, dann ist meine Frage somit beantwortet. Vielleicht noch ergänzend der ausdrückliche Hinweis, dass ich mir über die lizenzrechtlichen Bestimmungen diesbezüglich nicht bewusst gewesen bin - nicht, dass meine Frage hier als Hilfe zum Brechen von Lizenzrecht missverstanden wird ;-)

viele Grüße
Vorph

Thema: Können Libraries beim Erstellen einer Solution eingebunden werden?
Am im Forum: Entwicklungs- und Laufzeitumgebung (Infrastruktur)

Hallo,

ich habe eine Windows-Forms-App mit mehreren AForge-Libararies erstellt und möchte diese nun zu einer .exe kompilieren. Das klappt auch problemlos, allerdings erhielt ich beim Start die Fehlermeldung:

Fehler
Microsoft .NET Framework
Unbehandelte Ausnahme in Anwendung. Klicken Sie auf "Weiter" um den Fehler zu ignorieren und die Anwendung fortzusetzen. Wenn Sie auf "Beenden" klicken, wird die Anwendung sofort beendet.
Die Datei oder Assembly "AForge Video.DirectShow Version=2.2.5.0.Culture=neutral PublicKey Token=61ea4348d43881b7" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.

Was ich dazu per Google gefunden habe war folgendes: offensichtlich müssen die Assembly-Dateien im selben Ordner abgelegt werden, wie die .exe auch.
Das habe ich nun getan und damit läuft die App auch.

Meine Frage ist folgende: gibt es eine Möglichkeit, diese Dateien in die .exe mit einzubinden?

Es sind nämlich insgesamt 19 Dateien und ich kann sie auch nicht einfach in einen Sub-Folder der Anwendung unterbringen - das führt dann nur wieder zu selben Fehlermeldung.
Was ich sonst dazu gefunden habe ist, dass man die Dateien ins .exe-Verzeichnis kopieren muss, oder eine Setup-Datei schreiben, die die Assembly aus einem zip-Ordner extrahiert oder ähnliches. Wie gesagt: gibt es keine Möglichkeiten die in der .exe unterzubringen?

Vielen Dank,
Gruß
vorph

Thema: MultiDataTrigger-Binding - Null-Value-Exception
Am im Forum: GUI: WPF und XAML

Zitat
Du kannst dem Template nicht null zuweisen, aber wo genau das auftritt, sollte dir die Fehlermeldung sagen.
Die komplette Fehlermeldung ist ja mit angegeben (s. Post 1) - expliziter wird es leider nicht
Zitat
Ansonsten scheint es in deinem Code noch mehrere andere Merkwürdigkeiten zu geben. Wo defininierst du z.B. das Template für "ExpanderHeaderCollapsedType"
In UserControl.Resources (mir schleierhaft warum ich das vergessen hatte - sorry)

<DataTemplate x:Key="ExpanderHeaderCollapsedType">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock>
                    <TextBlock.Style>
                        <Style TargetType="TextBlock">
                            <Setter Property="Text">
                                <Setter.Value>
                                    <MultiBinding Mode="OneWay" Converter="{StaticResource AverageValueConverter}" ConverterParameter="Type">
                                        <Binding  Path="DataContext" RelativeSource="{RelativeSource FindAncestor, AncestorType=Expander, AncestorLevel=1}"/>
                                        <Binding  RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                                                  Path="DataContext.AllGradesCV.View.Groups"></Binding>
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
            </StackPanel>
        </DataTemplate>
Zitat
... und warum gibst du als TargetNullValue für einen String-Wert ein "true" an?
Gute Frage - weil ich schlicht keine Ahnung hatte, was ich mit dem Fehler anfangen soll, und beim googeln dann auf diese Monstrosität gestoßen bin. Im übrigen ist es egal ob ich true oder PaulePanter eingebe - das ändert schlicht nüscht.

Dürfte ich meine Eingangsfrage vlt. umformulieren? Ich denke mit diesem "Null-Type-Value" verrenne ich mich in eine ganz falsche Richtung. Nachdem ich es jetzt tagelang probiert habe, scheint mir, die eigentliche Frage müsste lauten:
wie erreiche ich es, dass beim Gruppieren der jeweilige Expander abhängig von der angeklickten DataGridColumn UND dem Zustand von ExpandedState eine bestimmte Funktion über einen ValueConverter auslöst?

Hier ein funktionierendes Beispiel (eine andere View in meinem Projekt, die aber fast dieselbe Funktionalität bereitstellt):
Hier wird im ValueConverter lediglich festgestellt, ob ein bestimmtes Objekt (Eintrag im DataGrid) gerade das aktuelle Objekt ist. Wenn ja, werden alle GroupItems collapsed, bis auf dasjenige, zu dem das akutelle Objekt gehört (konkret: du hast Schüler verschiedener Klassen. Aus der Klasse A1 ist Paule Panter angeklickt. Beim Gruppieren werden nun alle Klassen gruppiert und in einem kollabierten Expander zusammengefasst, außer die Klasse, in der Paule Panter ist):

<UserControl.Resources>
       <!-- Hier wird der ValueConverter deklariert. Dieser ist eine .cs-Datei in meinem Projekt (s. unten) -->
        <conv:ExpandConverter x:Key="ExpanderStateConverter"/>

<!-- Ein enum (ExpandedState) entscheidet darüber, wie der Expander sich verhalten soll (collapsed, expanded, alle collapsed, bis auf das GroupItem, das den aktuell im DataGrid ausgewählten Eintrag erhält. Da im ExpanderHeader unterschiedliche Infos je nach Zustand angezeigt werden, gibt es zwei verschiedene Templates-->

        <DataTemplate x:Key="ExpanderHeaderExpanded">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock Text="{Binding Path=DataContext.ItemCount, StringFormat=Schüler: {0}, RelativeSource={RelativeSource AncestorType=Expander}}"/>
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="ExpanderHeaderCollapsed">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock Text="{Binding Path=DataContext.ItemCount, StringFormat=Schüler: {0}, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="12" FontWeight="Bold"/>
            </StackPanel>
        </DataTemplate>
       
<!-- GroupItem-Style für den Expander. Je nachdem, ob das ExpanderHeader expanded oder collapsed ist wird hier das Header-Template geladen-->
        <Style x:Key="GroupItemStyle" TargetType="Expander">           

            <Setter Property="Background" Value="#FF5CB9EE"/>
            <Setter Property="ExpandDirection" Value="Down"/>
            <Style.Triggers>
                <Trigger Property="IsExpanded" Value="False">
                    <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderCollapsed}"/>
                </Trigger>
                <Trigger Property="IsExpanded" Value="True">
                    <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderExpanded}"/>
                </Trigger>

<!-- GroupItem-DAtaTrigger: Hier wird an den enum ExpandedState im ViewModel gebunden... -->
                <DataTrigger  Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="expandAll">
<!-- ...und wenn dessen Value "IsExpanded ist, dieser Code ausgeführt: -->
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="expandAll">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding  RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

<!-- ...wenn der Wert "collapseAll" ist, wird dieser Code ausgeführt: -->
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="collapseAll">
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="collapseAll">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding
                RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

<!-- ...und wenn der Wert "collapseAllButSelected" ist, wird dieser Code ausgeführt: -->
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="collapseAllButSelected">
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="collapseAllButSelected">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding
                RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

            </Style.Triggers>
            
        </Style>
        
    </UserControl.Resources>

Im DataTrigger findet ja so eine Art if/then/else Abfrage statt (wenn ExpandedState == collapseAll -> then...).
Alles was ich jetzt mit diesem Post erreichen wollte ist eigentlich: wie mache ich das, wenn nun noch eine Condition hinzukommt? Also:
if (ExpandedState == collapseAllButSelected && DataGrid.ColumnName == "xyu")?
Dieser Gedankengang hat mich überhaupt erst auf MultiDataTrigger gebracht.

Der Vollständigkeit halber hier mal noch der ValueConverter:


public class ExpandConverter : IMultiValueConverter
    {
        private bool ItemInFilterGroup(object[] value)
        {
            CollectionViewGroup oc;
            Student x = new Student();

            //CollectionViewGroup 
            //1. Evalute type on [0]
            if (value[0] is CollectionViewGroup)
            {
                oc = value[0] as CollectionViewGroup;
            }
            else
            {
                oc = null;
            }

            if (value[1] is Student)
            {
                x = value[1] as Student;
            }

            if (oc != null && x != null)
            {
                if (oc.Items.Contains(x))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            return false;
            
        }

        public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
        {
            var x = parameter as string;

            if (x == "expandAll")
            {                
                return true;
            }
            else if (x== "collapseAll")
            {
                return false;
            }
            else if (x == "collapseAllButSelected")
            {
                return ItemInFilterGroup(value);
            }

            return false;
        }       

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


Thema: MultiDataTrigger-Binding - Null-Value-Exception
Am im Forum: GUI: WPF und XAML

Hallo,
folgendes Problem versuche ich derzeit zu lösen:

Ich gruppiere Daten in einem DataGrid. Dazu verwende ich einen Expander. In meinem ViewModel ist ein enum, der drei Expander-Zustände deklariert:
- collapseAll
- expandAll
- collapseAllButSelected

collapseAll: alle GroupItems werden kollabiert, d.h. die Expander aller GroupItems sind collapsed
expandAll: alle GroupItems sind geöffnet, d.h. die Expander aller GroupItems sind expanded
collapseAllButSelected: alle GroupItems sind kollabiert, es sei denn, ein Item aus der Gruppe wurde ausgewählt.

Ich habe dazu im XAML code bereitgestellt, der immer dann, wenn man auf einen ColumnHeader des DAtaGrids klickt ein Command auslöst.
Das Command initiiert einen Zähler, der je nach Anzahl der Klicks den ExpanderState verändert:
Beim ersten Klick auf den ColumnHeader collapseAll, beim zweiten Klick expandAll, beim dritten Klick collapseAllButSelected.
Diese Funktionalität habe ich bereits bereitgestellt – das funktioniert also.

Was ich nun vorhabe ist folgendes:1. Wenn die Elemente im DataGrid gruppiert werden…
a) frage den ExpanderState ab
b) prüfe, welche Column angeklickt wurde (welcher Name hat der Header)
c) je nachdem welche Bedingung zutrifft wähle ein entsprechendes Expander-Header-Template
Das könnte dann also so aussehen:
Wenn der ExpanderState collapseAll ist (a) UND die Column mit dem Header „Fach“ (b) angeklickt wurde lade ExpanderHeader-Template x (c).
Wenn der ExpanderState collapseAll ist (a) UND die Column mit dem Header „Note“ angeklickt wrude lade ExpanderHeader-Template y (c).
Wenn der ExpanderState expandAll ist (a) UND die Column mit dem Header „Fach“ (b) angeklickt wurde lade ExpanderHeader-Template z (c).
Wenn der ExpanderState expandAll ist (a) UND die Column mit dem Header „Note“ angeklickt wrude lade ExpanderHeader-Template u (c). … usw. usf.

Natürlich wollte ich „einfach“ anfangen und erstmal einen Prüffall durchspielen.
In meinem xaml-code sieht das so aus:


<!-- DataTemplates for the EXPANDER HEADERs -->
        <DataTemplate x:Key="ExpanderHeaderExpanded">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock Text=""/>
            </StackPanel>
        </DataTemplate>

        <DataTemplate x:Key="ExpanderHeaderCollapsed">
            <StackPanel>
                <TextBlock Text="{Binding Path=DataContext.Name, RelativeSource={RelativeSource AncestorType=Expander}}"
                           FontSize="16" FontWeight="Bold"/>
                <TextBlock>
                    <TextBlock.Style>                        
                        <Style TargetType="TextBlock">
                            <Setter Property="Text">
                                <Setter.Value>
                                    <MultiBinding Mode="OneWay" Converter="{StaticResource AverageValueConverter}" ConverterParameter="Subject">
                                        <Binding  Path="DataContext" RelativeSource="{RelativeSource FindAncestor, AncestorType=Expander, AncestorLevel=1}"/>
                                        <Binding  RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                                                  Path="DataContext.AllGradesCV.View.Groups"></Binding>
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>                            
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
            </StackPanel>
        </DataTemplate>

<!-- Style for GroupItem Behaviour of EXPANDER -->
        <Style x:Key="GroupItemStyle" TargetType="Expander">
            <Setter Property="Background" Value="#FF5CB9EE"/>
            <Setter Property="ExpandDirection" Value="Down"/>
            <Style.Triggers>                  
                <Trigger Property="IsExpanded" Value="True">
                    <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderExpanded}"/>
                </Trigger>
                <!-- Diesen Trigger gegen MultiDataTrigger ersetzen?= -->
                <!--<Trigger Property="IsExpanded" Value="False">
                    <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderCollapsed}"/>
                </Trigger>-->

                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Property="IsExpanded" Value="False"/>
                        <Condition Binding="{Binding TargetNullValue=true, RelativeSource={RelativeSource AncestorType={x:Type DataGridTemplateColumn}}, Path=Header}" Value="Fach"/>
                    </MultiDataTrigger.Conditions>
                    <MultiDataTrigger.Setters>
                        <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeaderCollapsedType}"/>
                    </MultiDataTrigger.Setters>
                </MultiDataTrigger>

Der Part mit dem MultiDataTrigger ist das Problem: wenn ich die App starte wird folgende Exception ausgegeben:
Fehler
System.Windows.Markup.XamlParseException: "Nicht-NULL-Wert für "Binding" erforderlich."

Innere Ausnahme:
InvalidOperationException: Nicht-Null-Wert für “Binding” erforderlich.

Ich vermute es fehlt ein Nicht-Null-Wert – was auch immer das bedeuten mag.
Ich bin mir nicht mal sicher, ob ich mit dem MultiDataTriffer für mein Vorhaben da auf dem richtigen Weg bin.
Daher hier noch kurz der Versuch einer Konkretisierung:
- ich möchte im Style mit dem Target-Type “Expander” auf die Property “IsExpanded” des Expanders binden (prüfen, ob der Value false ist”
- gleichzeitig auf das Property des DataGrids binden (Column --> Header--> Bezeichnung) und wenn letzterer den Value “Fach aufweist” im Setter das Property “HeaderTemplate” des Expanders setzen.
Gruß
Vorph

Thema: EFCore Daten werden nicht in DB geschrieben
Am im Forum: Datentechnologien

Sodale - vielen Dankf für die Hilfe(n) - ich habe mich einige Zeit jetzt mit Logging beschäftigt; mit Sicherheit nicht umsonst. Ich kapier zwar immer noch nicht alles, aber so hab ich wenigstens den Fehler schnell gefunden.

Die Lösung ist mal wieder banal - auch wenn ich sie mir nicht erklären kann:
- soweit ich weiß, wird bei der von mir verwendeten Schreibweise "FileName =./Test_DataBase00.sqlite" die Datenbank immer relativ zum Projektordner erstellt, und zwar unter Projekt-->bin-->Debug-->netcoreapp3.1

Dort fand ich auch bei mir die Test_DataBase00.sqlite

Nur, dass sie bei mir leer war, obwohl scheinbar soweit alles in Ordnung war. War es auch. Allerdings wurde eine identische Datei unter 'Projekt' erstellt. Und diese enthielt alle Daten.

Ich habe die Datenbank jetzt schon umbenannt, gelöscht, ausgetauscht - egal: nach der ersten Migration (erst nach 'update-database') habe ich die Datenbankdatei einmal unter Debug, und einmal direkt im primären Ordner.

Soll das so sein? Ich hatte bisher Datenbanken in der Konstellation EFcore und sqlite nur in Verbindung mit wpf-Projekten genutzt. Sollte das bei einer Konsolen-App anders sein? Wohl kaum, oder?

Jedenfalls - jetzt, da ich mit SQLite-Studio die richtige Datei öffne, klappt auch alles.

Zitat
Seit wann unterstützt der Connection String den führenden Punkt?

optionsBuilder.UseSqlite(connectionString: "FileName =./Test_DataBase00.sqlite");

Und der Space wird meines wissens auch nicht gemocht.
Also, bei meinen wpf-Projekten hatte ich das immer so gemacht, seit ich das mal in Zusammenhang mit einem wpf-Tutorial so gesehen hatte - da gab es bislang keinen Stress. Es stünde natürlich zu vermuten, dass hier ein Grund für den Fehler liegt - aber das wäre schon strange, wenn die Art des Projekts (Konsole vs. wpf) Auswirkungen auf die Erstellung der DB haben sollte...
Wie gesagt, ich kenne keine andere Form: wie wäre es denn besser, wenn man möchte, dass die DB sich immer in einem Ordner relativ zur App befindet?

Ansonsten nochmal vielen Dank für den Logging Hinweis - wieder was gelernt

Thema: EFCore Daten werden nicht in DB geschrieben
Am im Forum: Datentechnologien

Yes - ich hatte schon befürchtet, dass es nichts Offensichtliches ist^^

@Exception handling --> setzt das nicht voraus, dass überhaupt eine Exception geworfen wird?

Das mit dem Logging - keine Ahnung was das ist oder was es bedeutet; meintest du sowas Logging in .NET Core and ASP.NET Core

Thema: EFCore Daten werden nicht in DB geschrieben
Am im Forum: Datentechnologien

Verwendetes Datenbanksystem: EntityFrameworkCore 5 w/ SQLite

Hallo,
ich habe mir ein Test-Projekt (Konsolenanwendung) mit einer Test-Datenbank angelegt. Das Problem besteht darin, dass


using (var context = new DataBaseContext())
{
    context.MidiItems.Add(mI);
    context.SaveChanges();
}
NICHT dazu führt, dass der Wert in die Datenbank geschrieben wird.

Hier das Model


public class MidiItem
{
public int ItemID {get;set;}
public string MyValue {get;set;}
}

Mein DataBaseContext:


public class DataBaseContext: DbContext
    {
        public DbSet<MidiItem> MidiItems{ get; set; }
      
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite(connectionString: "FileName =./Test_DataBase00.sqlite");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new MidiItemEntityConfiguration());
         }
    }

Meine MidiItemEntityConfiguration


public class MidiItemEntityConfiguration : IEntityTypeConfiguration<MidiItem>
    {
        public void Configure(EntityTypeBuilder<MidiItem> builder)
        {
            builder.HasKey(s => s.ItemID);         
        }
    }

Und mein Program.cs-Code


static void Main(string[] args)
  {
       // var context = new DataBaseContext(); --> mein erster Versuch...
       using (var context = new DataBaseContext())
       {
           context.DataBase.EnsureCreated();
       }

       MidiItem mI = new MidiItem();
       mI.MyValue = "Test";

     // context.MidiItems.Add(mI);
     // context.SaveChanges(); --> erster Versuch....
      using (var context = new DataBaseContext())
      {
           context.MidiItems.Add(mI);
           context.SaveChanges();    
      }
       
  }

Mehr Code habe ich bisher nicht.

Der Code läuft ohne Fehler durch - wenn ich einen Breakpoint setze, kann ich sehen, dass der Eintrag unter context-->local-->MidiItems auftaucht.
Leider landet der Wert nicht in der Datenbank.
Ich kann zwar die Struktur in SQLite-Studio sehen (ItemID | MyValue), aber das item wird nicht hinzugefügt.

Ich stehe gerade vollkommen auf dem Schlauch - in meinen wpf-Projekten habe ich das im Prinzip auch nicht anders gelöst - warum sollte es bei einer Konsolenanwendung nicht klappen.

Wenn ich Migrations anwende, sehe ich, dass die Struktur der Datenbank so wiedergespiegelt wird, wie sie auch intendiert ist. Habe ich evtl. eine Library nicht implementiert? Bisher:
- Microsoft.EntityFrameworkCore.SQLite 5.0.3
- Microsoft.EntityFrameworkCore.SQLite.Design 1.1.6
- Microsoft.EntityFrameworkCore.Tools 5.0.3

Gruß
vorph

Thema: Wie kann ich eine Klasse festlegen, der erst zur Laufzeit feststeht?
Am im Forum: Grundlagen von C#

Zitat
Sorry, das ist eine absolute Quatschargumentation.
Mir ein Rätsel, wie man das unterschlagen / abwimmeln kann.
Heute wieder viel Code zum Weinen gesehen?

Ich denke, wer sich meine Posts in Ruhe durchgelesen hat, der wird feststellen, dass ich lediglich meinen Beweggrund dargelet habe, indem ich auf tribs Frage geantwortet habe. Ich dachte, das kann man so machen - jetzt weiß ich es besser. Ich fände es schade, wenn der Eindruck aufkommen würde, ich täte etwas unterschlagen/abwimmeln - ich bin hier, um mich in meinem Hobby zu verbessern und nehme diese Ratschläge auch gerne an. Um mich mal selbst zu zitieren "Ich war davon ausgegangen..." --> Präteritum, und alles is jut

Vielen Dank für eure Hilfe!

Thema: Wie kann ich eine Klasse festlegen, der erst zur Laufzeit feststeht?
Am im Forum: Grundlagen von C#

@trib: na ja, es gibt genau eine Stelle in der App, an der man ein Datum "eingeben" kann - nämlich beim Anlegen eines neuen Schülers. Und dort auch nur per DatePicker, der in XAML so angelegt wurde, dass man keine händische Eingabe vornehmen kann. Normalerweise mag ich diese User-Gängelung nicht, aber es handelt sich hier um einen überschaubaren Personenkreis (außer mir vlt. noch vier, fünf Kollegen), die später einmal diese App nutzen. Da hielt ich es für vertretbar, das eingegebene Datum direkt in einen String umzuwandeln, bevor es in die Datenbank geschrieben wird.
Tatsächlich wird beim Auslesen der Daten der String nochmal in DateTime konvertiert, um das aktuelle Alter der Schüler zu berechnen. Ich glaub', das waren vier Zeilen Code...

@Abt: stimmt. Ich glaube, das hattest du mir schon mal empfohlen. Auch hier war ich einfach davon ausgegangen, dass - solange keine anspruchsvollen Datumsberechnungen vorliegen - der alte DateTime reicht.

Thema: Wie kann ich eine Klasse festlegen, der erst zur Laufzeit feststeht?
Am im Forum: Grundlagen von C#

Zitat
Das macht sogar sehr viel Sinn, du erbst ja vom selben Interface

Ja - ich meinte eher im echten Leben. Vom Code her klar. Die Idee mit der überladenen Methode gefällt mir.

Thema: Wie spricht man eine USB-Schnittstelle an, die ein VGA-Signal überträgt?
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Zitat
wobei das installierte .NET Framework als Restriktion wohl auch beachtet werden will
Welche Einschränkungen wären das im Besonderen?

Zitat
diverse Ergebnisse unter nuget.org

Danke für die Links - hast du Erfahrungen mit einem dieser Packages? Eventuell eines, das dokumentiert ist? USB.Net sieht erst mal gut aus (nicht, weil es das erste in der Liste ist, sondern weil es scheinbar regelmäßig aktualisiert wird), scheint aber keine Dokumentation zu haben? Ansonsten werde ich mir diese einfach schon mal ansehen.

Wie sieht es auf der technischen Seite aus? Gibt es da etwas spezielles zu beachten?

Gruß
Vorph

Thema: Wie spricht man eine USB-Schnittstelle an, die ein VGA-Signal überträgt?
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Hallo,

Ich arbeite seit über 10 Jahren mit einem digitalen Mischpult, das einen eingebauten 10"-Screen hat. Damals gab es von einem Drittanbieter eine Adapter-Karte zu kaufen, die einen VGA-Ausgang und einen USB-Ausgang hat.
Mit dem VGA-Ausgang konnte man sich die Anzeige des Digitalpults auf einem externen Monitor ansehen.
Den USB-Anschluss konnte man mit einem PC verbinden und mittels einer kostenlosen App vom Hersteller konnte man sich den Screen des Pults in einem eigenen Fenster in der App ansehen.

Leider ist die Software total veraltet, das Pult wird schon ewig nicht mehr hergestellt (läuft hier bei mir aber noch tadellos in einer Win10-Umgebung).

Mal eine Frage (und man verzeihe mir bitte, wenn sie vielleicht furchtbar naiv klingt, aber ich bin lediglich Hobbyist und dann ist Hardware auch nicht unbedingt meine Stärke):

Wenn ich mir nun selbst ein Programm schreiben wollte, das lediglich in einem eignen Fenster einfach das ausgeben soll, was auch auf dem 10"-Screen zu sehen ist, mit welchen Techniken müsste ich mich dann beschäftigen?

Wahrscheinlich bräuchte ich zunächst eine Library oder ein Package, mit dem ich mit c# USB-Schnittstellen ansprechen kann - oder ist das schon in System.IO?

Ich vermute, dass über USB einfach der VGA-Stream (Stream? Ist das in diesem Zusammenhang der richtige Begriff?) übertragen wird. Gibt es hier spezielle Stolpersteine zu beachten?

Gruß
Vorph

Thema: Wie kann ich eine Klasse festlegen, der erst zur Laufzeit feststeht?
Am im Forum: Grundlagen von C#

So, hier also die versprochene Rückmeldung. Zunächst - es hat für meine Zwecke super geklappt, tut was es soll; und dabei, ist es auch noch schön praktisch Vielen Dank also an alle Hilfesteller!

Ohne mich groß zu wiederholen, hier kommt der Code:

Die Klasse Student wurde also um eine List<> ergänzt, in der sowohl Ziffern, als auch Verbalzbenotungen untergebracht werden können. Dazu hab ich folgendes Interface erstellt


   public interface IGrading 
     {
        int GradeId {get; set;}
        string DateOfGrading {get;set;}
     }

Da nun zwei verschiedene Benotungsweisen unter einen Hut zu bringen waren, brauchte es natürlich zwei entsprechende Klassen:


   public class RegularGrading : IGrading 
     {
        public int GradeId {get; set;}
        public string DateOfGrading {get;set;}

        public string GradeAsWord {get;set;}
        public string GradeWithLeadingZero {get;set;}
     }


   public class SpecialGrading : IGrading
     {
        public int GradeId {get;set;}
        public string DateOfGrading {get;set;}
        public string VerbalGrade [get;set;}
      }

Dementsprechend sieht meine Schüler-Klasse nun so aus:


   public class Student  
     {
        //...andere Properties s. oben
        public List<IGrading> Grading {get;set;}
     }

Tja, und das war es dann tatsächlich auch schon.

Und wie wird das Ganze nun genutzt? Nun, wenn eine neue Bewertung erstellt werden soll, sieht das Ganze in etwa so aus:


//Methode im ViewModel   
     public void CreateGrading(Student selectedStudent) 
     {
        if (selectedStudent.StudentType == StudentType.Regular)
	  {
	      RegularGrading r = new RegularGrading();
              r = CalculateValues(points);
	      
	      selectedStudent.Grading.Add(r);
          }
	 else
	  {
	      SpecialGrading s = new SpecialGrading();
	      s = GetVerbalGrade(textBody);    

              selectedStudent.Grading.Add(s);
           }

Wie gesagt, das funktioniert und wirkt auf mich recht elegant gelöst.

Der einzige Nachteil ist: you gotta get your sh** together!
Denn folgendes ist im Code möglich, macht aber von der Logik her keinen Sinn:


   public void UnLogicalMethod() 
     {
        RegularGrading r = new RegularGrading();
        SpecialGrading s = new SpecialGrading();

        r = FillRGWithSomeValues();
        s = FillSGWithAValue();

        selectedStudent.Grading.Add(r);
        selectedStudent.Grading.Add(s); //funktioniert beides, macht aber keinen Sinn, da ein Schüler nur die eine Art der Bewertung erhält ODER die andere!
     }

@Witte:
Danke für die Hinweise.

Zitat
Du denkst nicht objektorientiert - man sieht nur Daten, es wird aber kein Verhalten gezeigt.
OK, meinst du damit man sieht nicht, wie die Objekte im Code genutzt werden? Falls ja, habe ich vielleicht zu kurz gedacht, weil ich eigentlich einen ganz anderen Weg eingeschlagen hatte und mein Thread einen ganz anderen Titel hatte. So wie es sich jetzt liest, klingt es wirklich ein bisschen knapp, vor allem das, was ich im ersten Post geschrieben habe. Ansonsten erläutere bitte, was du mit "nicht objektorientiert denken" meinst, ich lerne gerne dazu und vielleicht gehe ich ja grundsätzlich nicht so an die Sache heran, wie es für objektorientiert angebracht wäre.


Grüße
Vorph

Thema: Wie kann ich eine Klasse festlegen, der erst zur Laufzeit feststeht?
Am im Forum: Grundlagen von C#

Danke für den Einwurf bezgl. Basisklasse - ich hatte das kurz in Erwägung gezogen, aber es macht IMHO hier nicht wirklich Sinn, weil die Bewertungen wirklich grundverschieden sind. Ich versuche das nochmal ganz kurz zu skizzieren - ich weiß, die Thematik ist sehr speziell.

Regelschüler = R
Förderschüler = F

Grundsätzlich erhalten beide ein Zeugnis. Bei R sind das Ziffernnoten von 15 bis 00. Bei bestimmten F (z. B. geistige Behinderung) besteht das Zeugnis nur aus einer verbalen Beurteilung - keine Ziffernnoten. Die Krux: an einer Förderschule können Schüler aus verschiedenen Bildungsgängen dieselbe Klasse besuchen, z. B. jemand der den Hauptschulabschluss macht und jemand, der im Bereich geistige Behinderung unterrichtet wird. Formal unterschieden sich die Schülertypen nur in der Bewertung (eine Schülerakte wäre also völlig identisch aufgebaut, würde sich aber in diesem Punkt fundamental unterscheiden).

Daraus ergibt sich, dass ich in meiner App zwei verschiedene Typen Schüler in ein und derselben Klasse unterbringen möchte. Ich hätte gleich zwei Typen von Schülern anlegen können, dabei aber jede Menge Code-Duplikate produziert, was sehr unschön gewesen wäre, da sie sich - formal - nur im Bewertungsteil so substantiell unterscheiden.

Daher bin ich auch von der Basisklasse abgerückt - es gibt in diesem Punkt keinen wirklich gemeinsamen Nenner.
Aber: ich glaube, die Idee von Palladin gestattet mir eine elegante Lösung, die zumindest praktikabel gehandhabt werden kann. Wie gesagt: für diesen Part existiert noch kein konkreter Code, ich kann mich erst übermorgen ausgiebig damit beschäftigen.

Zitat
Und lieber Klasse als Interface bei diesem "is-a", vorallem wenn es mal in eine Datenbank soll.
Könntest dur mir erläutern, wo genau du da Probleme siehst? Ja, später soll es in eine Datenbank (defacto entwickle ich meine DB parallel), aber ich sehe jetzt beim Interface eigentlich nur den Nachteil (wenn man das so nennen will), dass ich mehr Code zu schreiben habe. Ansonsten sehe ich einen Benefit in Sachen Wartbarkeit, Erweiterung und ease-of-use. Bin aber nur Hobby-Coder von daher kann es natürlich gut sein, dass ich offensichtliche Schwachstellen nicht sehe/bedacht habe.

Grüße
vorph

Thema: Wie kann ich eine Klasse festlegen, der erst zur Laufzeit feststeht?
Am im Forum: Grundlagen von C#

Hallo Palladin - sorry für die späte Rückantwort! Wo fange ich an? Erstmal vielen Dank für deine Vorschläge und die Richtigstellungen - meine Verwendung des Begriffs 'boxing' war natürlich falsch - und ich wusste es nicht mal

Gestern Abend kam ich dazu ein bisschen zu coden und ich muss sagen: die Idee mit den Interfaces gefällt mir ziemlich gut. Ich weiß nicht, ob ich das alles richtig verstanden habe, aber über's Wochenende dürfte ich etwas ausgiebiger uzm Programmieren kommen und dann werde ich meine Lösung mal hier posten.

Zitat
Wenn man "GradeWord" weg lässt, sind die beiden Klassen auch gar nicht mehr so verschieden [...]

Dein ganzes Problem löst sich mit ein bisschen Umdenken also einfach in Luft auf

Ganz so einfach ist es dann doch nicht Wenn man versucht, die Realität abzubilden, braucht man für ein Regelschulzeugnis zwei Werte: Note als Punktwert mit vorangestellter Null plus Notenname (GradeWord), z. B.: '08' befriedigend.
Wohingegen eine verbale Beurteilung einfach ein Text ist, der Aufschluss über den Entwicklungsstand von Förderschülern in bestimmten Bildungsgängen geben soll, die prinzipiell nicht durch Ziffernnoten beurteilt werden, sozusagen eine Beurteilung im Fließtext. Beides zählt aber als 'Benotung', wenn es auch faktisch zwei verschiedene Dinge sind. So kommt man dann über's Programmieren zu der Erkenntnis, dass das derzeitige Bildungssystem erhebliche Schwachstellen haben muss

OK, wie gesagt, die Lösung werde ich hier noch posten, sobald ich weiß, dass mein Code tut was er soll.

Gruß
Vorph

P.S:
Ich stelle fest, dass meine Überschrift geändert wurde !? Fände ich gut, wenn man da eine automatisierte Info per Mail bekäme - ich habe jetzt erstmal im Forum gesucht und war schon verblüfft, weil ich dachte mein Thread sei weg!
Außerdem: das mit dem Festlegen der Klasse hat sich ja nun eigentlich erst aus dem Lösungsvorschlag von Palladin ergeben - grundsätzlich ging es ja schon erst mal um die Feststellung, dass ein Property da ist, dessen Typ zur Compile-Time noch nicht feststeht. Finde ich sehr verwirrend.

Thema: Wie kann ich eine Klasse festlegen, der erst zur Laufzeit feststeht?
Am im Forum: Grundlagen von C#

Hallo,

beim Frickeln habe ich ein Problem entdeckt, dessen Lösung mir gerade einiges Kopfzerbrechen bereitet. Alleine schon ein anschauliches Beispiel zu finden mit einem praktischen Bezug ist nicht so einfach - mal wieder taugt aber ein Schul-Besipiel.

Es existiert nur Pseudo-Code; ich suche nicht nach konkretem Code, eher einem Konzept, einer Roadmap sozusagen.

Folgendes Szenario: stellt euch eine Schule vor. Es gibt (Schul-)Klassen und Schüler. Jeder Schüler bekommt Noten.

Das könnte also so aussehen:


public class SClass
{
     List<Student> Students {get;set;}
}

public class Student
{
     List<Grading> Grades {get;set;}
}

Wie euch sicher aufgefallen ist, habe ich die Klasse Grading (Bewertung) weggelassen, denn sie stellt den Kern des Problems dar.
Es gibt nämlich Bewertungen rein in Ziffernform (Grundschule ab Kl.3, Sekundarstufe) und rein verbale Bewertungen (z. B. Förderschüler - nicht alle, aber bestimmte Gruppen).

Ich brauche also zwei bestimmte Bewertungs-Klassen:


public class RegularGrading
{
     //Regelschulbewertung
     public string GradeWord {get;set;}
     public string Grade {get;set;}
}

public class SpecialGrading
{
     //Förderschulbewertung
     public string VerbalGrade {get;set;}
}

Der Typ der Bewertung hängt ab vom Schülertyp (Regel-/Förderschüler).
Meine Idee war, dass die Bewertung ein generischer Typ ist. Dann würde das so aussehen:


public class Student<T>
{
     List<T> Grades {get;set;}   
}

Da kein Schüler-Objekt (im späteren Code) erstellt werden kann, ohne dass der Schülertyp festgelegt wurde, wäre das eigentlich schon die Lösung, es bräuchte lediglich


     Student<RegularGrading> NewStudent = new Student<RegularGrading>();
und die Sache wäre geritzt. Zwar müssten bei Zuweisungen im Code vorher immer noch mal der Typ geprüft werden (typeOf()), aber das wäre zu verschmerzen.
Das große Problem, das sich abzeichnet ist folgendes: die Schulklasse enthält eine Liste mit Schüler-Objekten. Diese sind nun aber generische Klassen, was wiederum bedeutet, dass auch die Schulklasse einen generischen Typ <T> bereithalten muss:


public class SClass<T>
{
     List<Student<T>> Students {get;set;}
}
Damit erklärt sich das Problem womöglich von selbst: ich kann nun keine Schulklassen mehr instanziieren, ohne explizit den Typ der Bewertung für Student angeben zu müssen. Das kann ich aber gar nicht, weil es
a) zum Zeitpunkt der Klassenerstellung noch gar keine Schüler geben kann (zuerst braucht man eine Schulklasse, und dann Schüler, die ihr zugeteilt werden können) und
b) jeder Schüler nur eines von zwei möglichen Bewertungsschemata haben kann.

Da es keine generischen Properties gibt, muss ich hier einen anderen Weg einschlagen - spontan fiel mir nur ein, statt das Grading generisch zu halten, vielleicht einfach eine List<object> in den Grading-Klassen anzulegen. Prinzipiell natürlich eine Lösung, hat jedoch zwei Schönheitsfehler:
a) jede Menge boxing und unboxing im Code
b)


//Gradings unter der Annahme, dass die Notenlisten den Typ <object> entgegennehmen:
RegularGrading g = new RegularGrading();
SpecialGrading s = new SpecialGrading();

MyStudent.Grades.Add(g);
MyStudent.Grades.Add(s); //da die Liste Items vom Typ object entgegennimmt funktioniert das zwar, kann aber zu Fehlern führen, da ein Schüler stets nur 1 einem Bewertungsschema zugeordnet werden kann.

Wie könnte ich dieses Problem angehen? Müsste ich konzeptuell etwas verändern?

Gruß
Vorph




Thema: EFCore - Fragen zu Fluent API und Navigation Property
Am im Forum: Datentechnologien

Zitat
Werde Berater wie ich; siehst viel Code, der Dir Kopfschmerzen macht und Du eigentlich lieber Schafshirte geworden wärst :-)

Hehe - der ist gut ;-) Auf 'nem T-Shirt würde ich den Spruch sofort kaufen :-D

Thema: EFCore - Fragen zu Fluent API und Navigation Property
Am im Forum: Datentechnologien

So, ich konnte mich jetzt die letzten Tage noch einmal eingehender mit der Thematik beschäftigen. Vielen Dank, Abt, ich kann mittlerweile tatsächlich Erfolge verweisen - die Datenbank (das DB-Schema, das erstellt wird) sieht wirklich so aus, wie ich es konfiguriert habe und verhält sich auch wunschgemäß.

Super vielen Dank hierfür ;-)

Eine letzte Frage sei mir vielleicht noch gestattet: es ist das erste Mal, dass ich bewusst auf DataAnnotations verzichtet habe und mich ganz bewusst für die sauberere Implementierung und das "mehr" an Power der Fluent Api entschieden habe. Leider bekomme ich zu 99,9% nur meinen eigenen Code zu Gesicht und so fällt es schwer, vergleiche zu ziehen. Daher muss ich jetzt bei einer Sache noch einmal nachhaken:

Zitat
Entitites sind Datenmodelle. Aber Datenmodelle sind nicht gleich Entities:
Entities sind (bei relationalen Datenbanken) die genaue Darstellung einer Tabellenstruktur.

Ich glaube, das habe ich verstanden - aber ich möchte sichergehen, dass ich nicht völlig auf dem Holzweg bin!

Mein ViewModel --> geschenkt!
Meine Business-Logik --> geschenkt! Ich habe eine Klasse Student, eine Klasse SchoolClass, eine Klasse Person...usw., die Properties und Methoden beinhalten, die die jeweilige Klasse definieren (i. Sinne von: welche Eigenschaften das Objekt haben soll) und Funktionalität bereitstellen (Methoden).

Dann habe ich noch das, was ich mein Data-Model nenne: Klassen, die nur die Properties der Models implementieren, also als kurzes Beispiel:


public class Student
{
    public int StudentId {get;set;}
    public string FullName {get;set;\
    //...noch ein paar Properties

    private int CalculateAlge() //Methode zum Berechnen des Alters
    private void ChangeStudentType () // Methode, die den Typ des Schülers ändert
}


public class StudentEntity
{
    public int StudentId {get;set;}
    public string FullName {get;set;}
    //...und die restlichen Properties
}

Soweit, so gut. (Übrigens: ich hatte meine Klassen in <MeineKlasse>Entity umbenannt - auch das hat vieles übersichtlicher gemacht.)

Um das Datenbank-Schema zu erstellen (erinnere dich kurz an meinen ersten Post) war es notwendig, dass die One-To-Many-Relation zw. Student und SchoolClass deklariert wird: Eine Klasse kann viele Schüler haben, ein Schüler immer nur eine Klasse.

Im Code oben hatte ich ja eine Liste List<Students> Students in der SchoolClass-Klasse (ist jetzt ein HashSet). Gleichzeitig habe ich jetzt meine StudentEntity so abgeändert:


public class StudentEntity
{
    public int StudentId {get;set;}
    public string FullName {get;set;}
    //...und die restlichen Properties, ergänzt um:
    
    public int SchoolClassId {get;set;}
    public SchoolClass StudentClass {get;set;}

}

Dadurch ließ sich nun im DataContext die Relation zwischen SchoolClass und Student darstellen (nur vorweg - ich hab's im Code mit einer Klasse, die IEntityTypeConfiguration<StudentEntity> implementiert gelöst, hier also nur der kürze wegen so:)


  protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<SchoolClassEntity>()
                              .HasKey(s => s.SchoolClassId);

           modelBuilder.Entity<SchoolClassEntity>()
                             .HasMany<StudentEntity>(s => s.Students)
                             .WithOne(s => s.StudentClass)
                             .HasForeignKey(s => s.StudentId);

Was mich verunsichert ist folgendes:

Im Code, übergebe ich in einer Methode Daten an mein Daten-Objekt (StudentEntity), in etwa so:


       newStudentEntity.Person.FirstName = FirstName; 
       newStudentEntity.Person.LastName = LastName;
//...

Jedesmal, wenn ich die Instanz newStudentEntity von StudentEntity eintippe, zeigt mir Intellisense nun natürlich auch das StudentClass Property vom Typ SchoolClass. Theoretisch könnte ich hier also versehentlich richtig Murks machen, denn im Code (in der BusinessLogik?) sollte Student überhaupt nicht auf SchoolClass zugreifen können - im Prinzip braucht Student von SchoolClass gar nichts zu wissen! Allerdings brauche ich das Property ja hier, um es im DataContext als Navigation-Property nutzen zu können.

Wie gesagt: vielleicht mache ich mir jetzt Gedanken um Sachen, die völlig irrelevant sind.

Oder ich habe ebend doch noch etwas nicht verstanden?

Gruß
Vorph

EDIT: NNNGH! Jetzt, wo ich gerade zum x-ten Mal die Klassen durchgehe und Code optimiere, fällt mir ein, dass ich ja in meiner ModelBuilder-Klasse (z. B. StudentEntity) einen Type T definiere! Damit hätte sich ja die Frage geklärt:
- ich schreibe Entities, die sich nur darum "kümmern", wie das Datanbankschema aussehen wird
- Meine Daten übergebe ich nich an die Entities - sondern an DataModels (was bei mir mal StudentData war und ich nun in StudentEntity umbenannt habe)
Ich hatte einfach den Begriff DataModels falsch verstanden (OK, zumindest zu eng gefasst), weil ich dachte, dass sind nur die Klassen, die Daten für die Datenbank entgegennehmen.

Wenn das Edit stimmt, verstehe ich auch völlig was
Zitat
Entitites sind Datenmodelle. Aber Datenmodelle sind nicht gleich Entities
bedeuten soll. (Ich kreuze mal die Finger...)

Thema: .Net 5 und EF6 mit ADO Sqlite Databse First
Am im Forum: Datentechnologien

Hallo Thron,

könntest du deine Problematik etwas genauer beschreiben?

Da SQLite eher ein Client-based Datenbanksystem ist, würde ich dir EntityFrameworkCore vorschlagen (es entällt die clientseitige Installation eines kompletten Datenbanksystems, aber das möchtest du ja eh nicht, da du SQLite verwendest).

Die Herangehensweie ist prinzipiell dieselbe:
Du erstellst deine Data-Models und erstellst deine Entities anhand dieser Models. Außerdem benötigst du eine Klasse die von DbContext erbt - das wird dein DataContext.

Das ist jetzt sehr, sehr sporadisch umrissen, aber es lohnt sich eigentlich erst dann in die Tiefe zu gehen, wenn wir das konkrete Problem kennen.

Gruß
GeneVorph

Thema: EFCore - Fragen zu Fluent API und Navigation Property
Am im Forum: Datentechnologien

Hallo Abt,

erstmal vielen Dank für die ausführlichen Erklärungen, und das du so viel Mühe in meinen Code gesteckt hast!

Bis ins kleinste Detail ist mir noch nicht klar, was da passiert, aber hier vlt. eine Frage, mit der ich etwas Licht ins Dunkel bekomme:

sind die Daten-Models etwas anderes als die Entity-Models? Oder blöd gefragt: in meiner Anwendung stelle ich z. B. Informationen in Textboxen bereit, die übergebe ich an die entsprechenden Properties der entsprechenden Data-Models (z. B. SchoolClass in meinem Fall), dieses übergibt dan diese Daten mit Hilfe eines DataService an die Datenbank weiter. Aber dieses Data-Model ist nochmal getrennt von den Models für die Entities, oder?

Zitat
Weil es auch falsch ist. Du wirst die Methode auch nirgends in der Doku finden.
Das will ich dir auch gerne glauben, aber zumindest hier wird die Methode beschrieben/gezeigt. Und auch in msnd.
Zitat
Nein. EF ist der FK hier im Endeffekt egal; der Datenbank aber nicht, weil Du durch das Schema Regeln erzeugt hast, dass der FK gesetzt sein muss.
Das prüft hier EF aber nicht automatisch und auch bei Queries spielt das für EF keine Rolle.
Ah - Danke! Auch das hab ich noch nirgendwo so gelesen, aber es holt einige Fragezeichen von meiner Liste! Jetzt verstehe ich auch, warum es EFC bis zur Migration völlig egal ist (und eigentlich auch darüber hinaus), außer beim PK. Ich dachte mir ja schon, dass da keine Magie im Spiel ist ;-)

Thema: EFCore - Fragen zu Fluent API und Navigation Property
Am im Forum: Datentechnologien

verwendetes Datenbanksystem: SQLite
betrifft: EntityFrameworkCore

Hallo,

ich versuche gerade komplett von DataAnnotations wegzukommen und meine Datenbank mit Hilfe von fluent Api zu konfigurieren.

Ich habe mich jetzt hauptsächlich auf entityframeworkcore.com, www.entityframeworktutorial.net und www.learnentityframeworkcore.com gestürzt.

Am besten ich zeige euch erst mal mein Data-Model und dann meine Fragen.

Ich kürze es ein wenig, weil es im Wesentlichen um das Verständnis geht (die Beispiele findet man so auch in etwa auf den vorgenannten Sites):

nehmen wir mal eine Schulklasse und einen Schüler:


public class SchoolClass
{
public int SchoolClassId {get;set;}

// ... noch ein paar Properties

public List<Students> Students {get;set;}
}

public class Student
{
public int StudentId {get;set;}
public Person PersonData {get;set;}
public Address AddressData {get;set;}
public Education EducationData {get;set,}
}

public class Person
{
public int PersonId {get;set;}

//...Properties wie Vorname, Nachname, Geburtsdatum, Geburtsort...etc.
}

public class Address
{
public int AddressId {get;set;}

//...Properties wie Straße, Hausnummer, Wohnort, Postleitzahl, etc.
}

public class Education
{
public int EducationId {get;set;}

//...Properties wie Schulform, Datum Schuleintritt, Klassenstufe, etc.
}

Nichts spannendes soweit. Wenn ich nun meinen DataContext so belasse...


public class DataBaseContext : DbContext
    {
        public DbSet<SchoolClass> SchoolClasses { get; set; }        
        public DbSet<Student> Students { get; set; }
        public DbSet<Person> Persons { get; set; }
        public DbSet<Address> Addresses { get; set; }
        public DbSet<Education> Educations { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //optionsBuilder.UseSqlite("Data Source=Test_lite.sqlite");
            optionsBuilder.UseSqlite(connectionString: "FileName =./Test_DataBase.sqlite");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {                   
                        
        }
    }

...dann erstellt mir Entitiframeworkcore eine Datenbank mit den Tables SchoolClasses, Students, Persons, Addresses und Educations. In jedem Table werden außerdem Columns für die ForeignKeys erstellt, um die Objekt-Relationen abzubilden. Soweit so gut.

Nun möchte ich wie gesagt einige Eigenschaften konkretisieren. Ich möchte z. B. meiner Student-Klasse noch drei Properties vom Typ int hinzufügen:
public int PersonKey
public int AddressKey
public int EducationKey
Diese möchte ich im Code auslesen können, um z. B. ganz gezielt Informationen aus der Datenbank abfragen zu können - dazu müsste ich doch im ModelBuilder deklarieren, dass der ForeignKey z. B. PersonKey sein soll, oder? Nur


 protected override void OnModelCreating(ModelBuilder modelBuilder)
        {                   
            modelBuilder.Entity<Student>()
                              .HasForeignKey()  //<-- funktioniert bei mir nicht, diesen Eintrag bekomme ich                    
                                                                durch Intellisense nicht vorgeschlagen, bzw. wir als Fehler 
                                                                 angezeigt.

        }

Ich bin mir nicht sicher, warum das nicht funktioniert - wenn ich mir die Beispiele auf den erwähnten Sites so ansehe, kommt mir allerdings der Verdacht, dass ich ein Navigation-Property benötige.
Dessen Funktion ist mir im Prinzip klar, allerdings verstehe ich nicht, wie sich das auf meinen Code auswirkt, denn:



public class Student
{
public int StudentId {get;set;}

public int SchoolClassId {get;set;}
public SchoolClass AssignedSchoolClass {get;set;}
}

//Beispiel hier anhand der Person-Klasse
public class Person
{
public int PersonId {get;set;}

public int StudentId{get;set;}
public Student AssignedStudent {get;set;}
}
- jetzt hätte meine Student-Klasse ein SchoolClass-Objekt (Navigation-Property) um eine One-to-Many-Relation herzustellen (Eine Klass kann viele Schüler enthalten <-> ein Schüler gehört immer nur einer Klasse an), bzw meine Person-Klasse ein Student-Objekt und nach den EFC-Konventionen hätten wir eine One-to-One-Relationship (zumindest möchte ich das abbilden: Ein Schüler hat genau ein ihm zugeordnetes Personen-Objekt mit seinen Stammdaten <->ein Personen-Objekt mit seinen spezifischen Stammdaten gehört zu genau einem Schüler)

Was ich jetzt nicht so ganz kapiere: in meinem Daten-Model ist jetzt in der Person-Klasse plötzlich ein Objekt vom Typ Schüler - und dieses besitzt ja wiederum ein Person-Property und dieses ... immer so weiter. Ebenso in der Student-Klasse: ein SchoolClass-Objekt, das ja wiederum eine List<Student> enthält. Soll das so sein?

Im Prinzip müsste es doch so sein, dass mein Navigation-Property im Code gar nicht auftaucht (denn dort wird es ja auch nicht gebraucht) - aber andererseits muss es ja da sein, damit ich es als Navigation-Property nutzen kann (denn für den DataContext wird es ja gebraucht).
So ganz bin ich mir noch nicht sicher, wie das läuft - ich hoffe, ihr könnt mir das etwas einfacher erklären :)

Gruß
Vorph

Thema: System.InvalidOperationException: ""EditItem" ist für diese Ansicht nicht zulässig."
Am im Forum: GUI: WPF und XAML

Hallo ToXit,

ich trage jetzt zwar Säulen nach Athen (denn die einzig richtige Antwort hat FZelle schon gegeben), aber ich sehe hier schweren "System-Missbrauch"!

Was ihr umsetzen möchtet ist das Hinzufügen eines Eintrages unter XAML, der bei Verwendung von MVVM eine reine Routineeinstellung ist - durch Code-behind und das direkte ansprechen von Controls aber zum Ding der Unmöglichkeit wird!

Aber im Prinzip ließe sich das noch einfach umsetzen, indem du/ihr zuerst eine View (das eigentliche UserControl oder Window) erstellt und dann ein Model für diese View (ein ViewModel) --> das ist dann eine .cs-Datei, die sich um die Logik der Darstellung kümmert.

Um überhaupt mal ein Gespür dafür zu bekommen, würde ich vorschlagen sich mal in Grundzügen mit dem Bezug zwischen View und ViewModel auseinander zu setzen. Ich habe hier schon oft gelesen, das sei schwer, umständlich, kompliziert, aufwendig u. ä. Dem kann ich mich nicht anschließen.

Daher hier ein kleiner Kick-Start:

1. Lege in deinem Projekt eine Datei an nach dem Schema (MyControlName)ViewModel --> z. B. MainWindowViewModel

2. Dein ViewModel muss INotifyPropertyChanged implementieren, z.B. so:

public class MainWindowViewModel: INotifyPropertyChanged

3. Im XAML-Code deiner View (z. B. MainWindow.xaml) fügst du folgenden Code hinzu:

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

Fertig :) Du hast nun ein ViewModel, das mit deiner View verbunden ist.

Für deinen Fall erstellst du nun eine ObservableCollection<Sources> MyCollection. Und im DataGrid deiner View legst du diese Collection als ItemsSource fest:
<DataGrid ItemsSource="{Binding MyCollection}"
Und wir sind da...

Kleiner Tipp noch: Sources ist bei dir eine Liste von Listen. Kann man machen, wird aber sehr unübersichtlich; mir ist nicht ganz klar, wie alle diese Infos in ein und dasselbe DataGrid sollen. Es könnte also durchaus Sinn machen entweder das Sources-Objekt anders zu konzipieren (denkt auch an entsprechende Properties) oder mehrere Collections anzulegen, etwa für ComboBoxZellen im Grid.
Zitat
Ich habe bisher herausgefunden, dass die ItemsSource nicht bearbeitet werden kann.

Wo immer du das herausgefunden hast - das ist komplett falsch! Genau für diesen Fall hast du die ItemsSource und DataBinding...und MVVM.

Gruß
Vorph

Thema: ComboBox SelectedItem update - wird aber nicht unmittelbar angezeigt
Am im Forum: GUI: WPF und XAML

Zitat
Es ist kein übliches Vorgehen, daß sich einzelne Einträge ändern (sondern meistens ändert man ja die komplette Liste und setzt dann die Auswahl neu).

Das stimmt natürlich Aber nur, weil es unüblich ist, heißt das ja nicht, dass man nicht eben genau diesen Verhalten im Projekt benötigt:-D

Persönlich würde es mich gar nicht mal stören, weil ich ja weiß, dass der Wert übernommen wurde. Aber ich denke, einen Benutzer würde es irritieren, wenn er z. B. ein Bauteil in einer Liste umbenennt und dann aber den alten Namen angezeigt bekommt.

Aber gut: dann bin ich beruhigt. Kein falsches Binding, kein Denkfehler - einfach nur nicht vorgesehen.

Ich denke, ich bleibe dann beim SelectionChanged. Mercie beaucoup!

Gruß
Vorph

Thema: ComboBox SelectedItem update - wird aber nicht unmittelbar angezeigt
Am im Forum: GUI: WPF und XAML

IsEditable ist jetzt false - damit kann ich zwar den Bereich nicht mehr beschreiben, aber das eigentliche Problem ist dadurch nicht gelöst: es geht ja darum, dass wenn der Wert in diesem Bereich sich ändert, dies erst angezeigt wird, wenn man vorher ein anderes Item auswählt.

Im DropDown-Bereich wird bereits der neue Wert angezeigt - da das ja ber das aktuelle Item ist, passiert natürlich erst mal nichts, wenn ich diesen sofort auswähle (logischerweise wird in diesem Fall das SelectionChanged-event nicht gefeuert).

Thema: ComboBox SelectedItem update - wird aber nicht unmittelbar angezeigt
Am im Forum: GUI: WPF und XAML

Danke Witte.

Allerdings gibt es da ein Problem: ich habe aus Testzwecken meiner View eine ListView, bzw. ein Label hinzugefügt. Die ListView zeigt alle Elemente der CollectionViewSource an und zwar NUR deren FullName-Property.

Das Label zeigt nur das FullName-Property der SelectedPerson.

Wie oben bereits beschrieben: SelectedPerson ist mein ViewModel-Property für das SelectedItem der ComboBox.

Klicke ich den Button werden die Werte für FullName in der ListView und auch im Label sofort geändert - nur nicht im Textfeld der ComboBox.

Ich denke somit wäre die Implementierung von INotifyPropertyChanged wohl auch nur ein Hack, oder? Funktioniert die ComboBox möglicherweise iwie anders?

Thema: ComboBox SelectedItem update - wird aber nicht unmittelbar angezeigt
Am im Forum: GUI: WPF und XAML

Hallo,

wahrscheinlich ein einfacher Sachverhalt:

Ich habe eine ComboBox, eine TextBox und einen Button.

Die ComboBox enthält Personen-Objecte, die sie aus einer CollectionViewSource erhält. Angezeigt wird jeweils der volle Name einer Person. Die CollectionViewSource wiederum hat als Source eine ObservableCollection<Person>.

Wählt man in der ComboBox eine Person aus, so wird deren Name in der TextBox angezeigt. Der Benutzer kann nun z.B. einen neuen Namen eingeben. Dieser soll jedoch die 'FullName'-Eigenschaft der ausgewählten Person überschreiben, also ein Update vornehmen.

Dazu ist im übrigen der Button, der ein Command (UpdateCommand) feuert.

Nehmen wir an, ich habe "Müller, Peter" ausgewählt.

Ändere ich den Eintrag in der TextBox jetzt ab in "Antoinette, Marie" und betätige den Button, scheint es zunächst, als sei nichts passiert, denn

- das aktuelle Objekt (SelectedItem) wird zwar im ViewModel in der Command-Methode auf den neuen Wert geupdated, aber in der View ist davon nichts zu sehen.

- klicke ich auf die ComboBox, so dass sich das DropDown-Feld öffnet sehe ich den alten Eintrag (Müller, Peter) nicht mehr, sondern den neuen (Antoinette, Marie) - gleichzeitig wird aber immer noch im Display der ComboBox 'Müller, Peter' angezeigt.

Meine "LÖsung" bisher: ich setze den Index der ComboBox im ViewModel auf -1 und dann direkt auf den Index des SelectedItem; so erweckt es den Anschein, als sei das Objekt geupdated worden.

Hier mal mein XAML:


<ComboBox ItemsSource="{Binding AllPersonsCV.View}" DisplayMemberPath="FullName" Width="120" 
                          SelectedItem="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" SelectedIndex="{Binding MyIndex}"/>

Und der relevante Code im ViewModel; zuerst für das SelectedItem-Property:

 private Person _selectedPerson;

        public Person SelectedPerson
        {
            get { return _selectedPerson; }
            set
            {
                OnPropertyChanged(ref _selectedPerson, value);
            }

Und die Command-Methode, die das eigentliche Update/Überschreiben vornimmt:



 private void UpdatePerson_Execute(object parameter)
        {
            UpdatePerson();
        }

private void UpdatePerson()
        {
            //Der in die TextBox eingegebene string wird an das Property fullName übergeben und hierdann
            //der FullName-Eigenschaft des SelectedItems der ComboBox übergeben.
            SelectedPerson.FullName = fullName;

           //CollectionViewSource-Refresh
            AllPersonsCV.View.Refresh();
         }
               

Und hier noch mein PersonName-Property, welches an den Text der TextBox gebunden ist:


private string _personName;

public string PersonName
{
   get {return _personName; }
   set
      {
          OnPropertyChanged(ref _personName, value);
      }
}

Soweit habe ich das Problem dann im Debugger schon aufgedröselt:
- die Werte werden übergeben, d.h. mein SelectedPerson.FullName-Property wird in der UpdatePerson-Methode überschrieben.

- auch in der AllPersons-ObservableCollection wird dadurch die entsprechende Property des entsprechenden Person-Objekts geändert.

- da für die View aber meine CollectionViewSource (in der im Debugger auch schon der neue Wert zu sehen is) zuständig ist, refreshe ich die CollectionViewSource.

Trotzdem zeigt das Display-Feld der ComboBox immer noch den alten Wert an. Erst, wenn ich ein anderes Element aus der ComboBox auswähle, ist der alte Wert endgültig verschwunden.

Was mache ich da falsch?

Grüße
Vorph

Thema: DataGrid Items mit Expander gruppieren (GroupItems mit indiv. Verhalten)
Am im Forum: GUI: WPF und XAML

[LÖSUNG]
Heureka – ich hab’s!

Ich weiß nicht, ob meine Frage seltendämlich, bockschwer oder schlecht gestellt war – daher hier eine kurze Zusammenfassung und eine Lösung step-by-step.

Problemstellung:

Ich möchte in meinem DataGrid auf den Header der Column ‚Klasse‘ klicken und alle Schüler-Objekte nach ihren Klassen sortiert bekommen.

Dabei sollen alle Schüler-Objekte unter einem Expander gruppiert werden.
Beim ersten Klick auf die Column ‚Klasse‘ werden alle Gruppen ‚expanded‘ angezeigt.
Beim zweiten Klick auf die Column ‚Klasse‘ werden alle Gruppen ‚collapsed‘ angezeigt.
Beim dritten Klick auf die Column ‚Klasse‘ werden alle Gruppen ‚collapsed‘, bis auf die Gruppe, die das aktuell ausgewählte Schüler-Objekt enthält.

Oh ja, und die Lösung sollte natürlich MVVM-kompatibel sein…

Im Prinzip war die Lösung – wie so oft – ziemlich einfach, wenngleich nicht unbedingt intuitiv!

Hier also die Lösung:
Wir benötigen:
- Eine Klasse die die IMultiValueConverter-Schnittstelle implementiert
- Einen enum, genannt ExpanderStatesEnum
- Einen Zähler und ein Command im ViewModel
- Eine CollectionViewSource die dem DataGrid als ItemsSource dient
- Und ein VieModel-Property vom Typ Student, das in meinem Fall einfach SelectedStudent heißt

Auf die beiden zuletzt genannten muss ich hier denke ich nicht näher eingehen.

Zuerst das Offensichtliche: wir legen uns einen enum an, der die gewünschten States repräsentiert


public enum EpanderStatesEnum
{
expandAll,
collapseAll,
collapseAllButSelected
}
expandAll wird später im ViewModel gesetzt, wann immer alle Expander ‚expanded‘ dargestellt werden sollen. Analog dazu ‚collapseAll‘.
collapseAllButSelected soll gesetzt werden, wann immer alle Expander ‚collapsed‘ werden sollen, bis auf denjenigen, zu dem das aktuell ausgewählte Schüler-Objekt gehört (sofern eines ausgewählt wurde).

Als nächstes widmen wir uns dem ViewModel. Hier benötigen wir zuerst ein Command:


public ICommand GroupSortCommand { get; set; }
//Und im Constructor
public MyViewModel()
        {
//do Stuff
GroupSortCommand = new CommandDelegateBase(GroupSort_Execute,    GroupSort_CanExecute);
        }
 

Die Methoden GroupSort_Execute und GroupSort_CanExecute können wir eigentlich gleich schon anlegen – zuvor benötigen wir im ViewModel einen einfachen Zähler…


private int _simpleCounter;

…der mitzählt, wie oft der Header bereits angeklickt wurde (zur Erinnerung: wir wollten ja verschiedenes Grouping-Verhalten beim 1. Klick, 2. Klick, etc.).
Nun noch schnell ein bool-Property, das darüber Auskunft gibt, ob überhaupt gruppiert werden soll (true) oder eben nicht (false)
Das sieht dann so aus:


private bool _isGroupSortRequested = true;

_isGroupSourtRequested wird mit true initiiert, damit man die Funktionalität beim Starten des Programms gleich benutzen kann.

Nun aber wirklich zu GroupSort_Execute und GroupSourt_CanExecute, damit unser Command auch eine Logik verabreicht bekommt:


private bool GroupSort_CanExecute(object paramerter)
        {
            return true;
        }
        
        private void GroupSort_Execute(object parameter)
        {
//Zunächst prüfen, ob wir nicht schon Schüler-Objekte gruppiert haben
            if (_isGroupSortRequested)
            {
//falls ja, kümmern wir uns um den Zustand des Counters – dieser kann die Werte 1, 2 und 3 //aufweisen.
                switch (_simpleCounter)
                {
//Beim 1. Klick
                    case 1:
//Wir setzen unseren enum auf den gewünschten Status
                        ExpandedState = ExpandedStatesEnum.expandAll;
//Wir übermitteln unserer CollectionViewSource die gewünschte GroupDescription…
                        AllStudentsCV.GroupDescriptions.Add(new PropertyGroupDescription("StudentEducational.ClassLabel"));
//…und zählen den Zähler eins hoch
                        _simpleCounter++;
                        break;
                        //Beim 2. Klick
                    case 2:
//hier muss nur der gewünschte enum-Status übermittelt werden – die GroupDescription ist noch //aktiv
                        ExpandedState = ExpandedStatesEnum.collapseAll;
//Auch hier den Zähler eins weiter zählen
                        _simpleCounter++;
                        break;
//Beim 3. Klick…
                    case 3:
//Wieder den enum auf den gewünschten Status setzen
                        ExpandedState = ExpandedStatesEnum.collapseAllButSelected;
//Und jetzt ganz wichtig – den Zähler auf den Wert 1 zurücksetzen!
                        _simpleCounter = 1;
//Und _isGroupSortRequested auf false setzen – dies bewirkt, dass wir beim 4. Klick auf den Header //im else-Teil dieser Schleife landen
                        _isGroupSortRequested = false;
                        break;
                }
            }
            else
            {
//wenn _isGroupSortRequested false ist, landen wir hier
//Alle GroupDescriptions zurücksetzen
                AllStudentsCV.GroupDescriptions.Clear();
//und die Variable _isGroupSortRequested wieder initiieren, damit wir beim nächsten Klick auf den //Header wieder im if-Teil der Schleife landen.
                _isGroupSortRequested = true;
            }            
        }

Wo benötigen wir dieses Command? Im ViewModel – nämlich immer dann, wenn der Header der Column ‚Klasse‘ angeklickt wird. Das erledigen wir natürlich im XAML der betreffenden View. Da es sich bei mir um ein DataGrid handelt, in dem ich das Command einbinden möchte, kommt der Code als Style in die DataGrid.Resources:

<DataGrid x:Name="MyGrid" ItemsSource="{Binding AllStudentsCV.View}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" HeadersVisibility="Column"
                      IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding SelectedStudent}">

                <DataGrid.Resources>
                    <Style x:Key="GroupHeaderStyle" TargetType="DataGridColumnHeader">
                        <Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.GroupSortCommand}"/>                        
                    </Style>
                   
                </DataGrid.Resources>

Ich sollte vielleicht hinzufügen, dass die Property HeadersVisibility="Column" unbedingt dazugehört, wenn ihr den Header-Button-Style wollt – sonst gibt es kein visuelles Feedback (blau hinterlegter background).

Als nächstes kümmern wir uns um die visuelle Darstellung unserer Daten – die Schüler, die im DataGrid angezeigt werden, sollen ja später gruppiert und mit Hilfe von Expandern dargestellt werden. Dazu erstellen wir uns erst mal einen Style für den Expander und zwar in den Resources der View (in meinem Fall UserControl):
 
<!--dazu komme ich noch -->
<conv:ExpandConverter x:Key="ExpanderStateConverter"/>
<!—hier hübsche ich nur den Header des Expanders auf… -->
        <DataTemplate x:Key="ExpanderHeader">
            <ContentPresenter 
                Content="{Binding}"
                TextBlock.FontSize="14"
                TextBlock.FontWeight="Bold"/>
        </DataTemplate>
                
        <Style x:Key="GroupItemStyle" TargetType="Expander">
            <Setter Property="Header" Value="{Binding Name}"/>
            <Setter Property="HeaderTemplate" Value="{StaticResource ExpanderHeader}"/>
            <Setter Property="Background" Value="#FF5CB9EE"/>
            <Setter Property="ExpandDirection" Value="Down"/>
<!-- Ab HIER wird es interessant -->
            <Style.Triggers>
                <DataTrigger  Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="expandAll">
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="expandAll">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding
                RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="collapseAll">
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="collapseAll">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding
                RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="collapseAllButSelected">
                    <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="collapseAllButSelected">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding
                RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </DataTrigger>

            </Style.Triggers>
            
        </Style> 

Der „Schlüssel“ zum Erfolg liegt letztlich in den DataTriggern des Expanders. Daher werde ich hier etwas ausführlicher sein:
 <DataTrigger  Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=DataContext.ExpandedState}" Value="expandAll">                </DataTrigger> 

Wir binden hier an das ExpandedState-Property des ViewModels und geben gleich den Wert an, auf den der DataTrigger hier reagiren soll: expandAll. Wenn also der ExpandedState = expandAll ist, dann soll der Trigger…

                   <Setter Property="IsExpanded">
                        <Setter.Value>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource ExpanderStateConverter}" ConverterParameter="expandAll">
                                <Binding  Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                                <Binding
                RelativeSource="{RelativeSource FindAncestor, AncestorType=DataGrid, AncestorLevel=1}"
                Path="DataContext.SelectedStudent"></Binding>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
 

…den Wert für die IsExpanded-Property abhängig von den Werten der Multibindings setzen. Dazu müssen wir das im DataGrid ausgewählte SelectedItem und einen ValueConverter referenzieren.

Keine Sorge – gleich ist es geschafft! :-)

Für den Converter lege ich im Projekt folgende Klasse an:

 public class ExpandConverter : IMultiValueConverter
    {
//Diese Methode benötigen wir, wenn ausgewertet werden soll, ob das SelectedItem im Datagrid in //der Gruppierung enthalten ist.
        private bool ItemInFilterGroup(object[] value)
        {
            CollectionViewGroup oc;
            Student x = new Student();
            //CollectionViewGroup 
            //Es wird ein Array mit 2 objects übergeben; einmal ein weiteres Array mit der //CollectionViewGroup, das ist die Collection, die unsere gruppierten Items enthält und dann noch //ein Object entsprechend des Typs des SelectedItem (hier: Student)
//Wahrscheinlich i. d. Fall nicht nötig, aber ich prüfe in beiden Fällen den Typ und setze die Variablen
            if (value[0] is CollectionViewGroup)
            {
                oc = value[0] as CollectionViewGroup;
            }
            else
            {
                oc = null;
            }

            if (value[1] is Student)
            {
                x = value[1] as Student;
            }

//Sind beide Typen korrekt…
            if (oc != null && x != null)
            {
//Schauen wir nach, ob das SelectedItem (x) in der GroupItem-Collection enthalten ist.
                if (oc.Items.Contains(x))
                {
//Wenn ja geben wir true zurück, was dafür sorgt, dass auch der Expander mit dieser GroupItem bei //IsExpanded auf true gesetzt wird.
                    return true;
                }
                else
                {
                    return false;
                }
            }

            return false;
            
        }

        public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
        {
//Im XAML übergeben wir per ConverterParameter den Status des ExpandedState enums
            var x = parameter as string;
//Im nächsten Schritt prüfen wir, welcher Status vorliegt…
            if (x == "expandAll")
            {                
                return true;
            }
            else if (x== "collapseAll")
            {
                return false;
            }
            else if (x == "collapseAllButSelected")
            {
//Und nur bei diesem Status (wenn alle Expander collapsed werden sollen, außer derjenige mit dem //gerade gewählten SelectedItem des DataGrids, rufen wir die private Methode auf
                return ItemInFilterGroup(value);
            }

            return false;
        }       
//Hier nicht nötig
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }        

Wie man weiter oben, in den Resources der View sieht, wird der Converter dort bereits referenziert (ich erwähne es nur der Vollständigkeit halber: der Converter muss VOR dem Expander-Style deklariert werden…)

Und damit läuft unser Programm:

Klickt man auf den Header ‚Klasse‘ des DataGrids wird das GroupSortCommand gefeuert. Diese ruft letztlich die Methode GroupSort_Execute auf, in der geprüft wird, ob bereits gruppiert wurde, und die dann gemäß unserem Zähler auswertet, ob es sich um den 1., 2. oder 3. Klick auf den Header handelt.

Dadurch wird der Status unseres ExpandedStateEnums verändert, und zwar je nach Klick auf expandAll, collapseAll oder collapseAllButSelected.

Dies wiederum bewirkt, dass unser DataTrigger, äh, getriggert wird und mit Hilfe des Converters auswertet, welcher Fall denn nun vorliegt. Der Converter wertet aus und meldet der View zurück welchen Wert IsExpanded annehmen soll – true oder false.