Sorry, Sorry, Sorry ...
Die Methode die den Daten-Thread aufruft, darf nicht als static deklariert werden:
private async System.Threading.Tasks.Task<List<Hausblatt>> RefreshHausBlätterListe(string Pfad, int Umfang, bool ShowVerarbeitung)
dann kann die VM_Aktualisierungs-Methode auch als Instanz-Methode deklariert werden und damit stehen in dieser wieder die VM-Eigenschaften zur Verfügung.
private void VM_Aktualisierung(double verarbeitungsStand)
Hallo!
Ich starte im VM über async/await einen Daten-Thread:
private async void ExtraktExecuted(object obj)
{
...
ListHausBlätter = await System.Threading.Tasks.Task.Run(() => RefreshHausBlätterListe(App.PfadHausblätter, MaxDateien, ShowVerarbeitung));
im Daten-Thread möchte ich eine VM-Eigenschaft aktualisieren:
--- Daten-Thread ----------
private static async System.Threading.Tasks.Task<List<Hausblatt>> RefreshHausBlätterListe(string Pfad, int Umfang, bool ShowVerarbeitung)
{
...
Application.Current.Dispatcher.Invoke(new Action(() => VM_Aktualisierung(VerarbeitungsZähler * 100 / HausBlätterListe.Count)));
}
--- GUI-Thread ------------
private static void VM_Aktualisierung(double verarbeitungsStand)
{
// VerarbeitungStand = 50;
}
In der Methode VM_Aktualisierung befinde ich mich wieder im GUI-Thread. Dadurch das ich die Methode als static deklariert habe, fehlen mir allerdings alle Eigenschaften (es werden nur die Methoden im VS angezeigt), siehe Bild.
Wenn ich die VM_Aktualisierung-Methode als Instanz-Methode deklariere wird wiederum der Fehler, das ein Objekt-Verweis erforderlich ist, geworfen:
error CS0120: Für das nicht statische Feld, die Methode oder die Eigenschaft "VMHausblätter.VM_Aktualisierung(double)" ist ein Objektverweis erforderlich.
Wie kann ich denn nun eine VM-Eigenschaft, aus dem Daten-Thread heraus, aktualisieren?
Hallo!
Weil es unheimlich nervt ...
Seit geraumer Zeit (evt. durch ein VS-Update?) werden Compiler-Fehler nicht mehr sofort in der Fehlerliste angezeigt, sondern erst beim explizitem Projekt-Mappe neu erstellen. Die Fehler werden dann nur noch als Maker (ausgefüllter Pfeil) im linken Bereich markiert und in der Ausgabe beschrieben. Der Effekt tritt erst nach einer gewissen Zeit, nach dem Neustart des PC's auf.
Die elektronische Glaskugel sagt , dass dieses Verhalten durch ein deaktivieren des Schreib-Caches des Datenträgers behoben werden könne, aber mein VS ist scheinbar resistent gegenüber dieser Lösung (Schreib-Cache ist deaktiviert und das Verhalten ändert sich nicht!).
Den Standard: Cache ist aktiv, habe ich geändert (und den PC wieder neu gestartet) siehe Bild.
Habe auch das .vs-Verzeichnis mal gelöscht, weil dies in der Lösung mit angegeben war, keine Besserung.
Habt ihr noch einen Hinweis?
Anmerkung:
An der Hardware des PC wurden keine Änderungen vorgenommen.
Die Energie-Optionen des PC's wurden nicht verändert und stehen auf höchste Leistung.
Hallo dannoe
Das es da einige gibt, ist mir bewußt, habe lieber etwas lokales benutzt. Deine Seite sieht aber wirklich sehr gut aus!
Nochmals vielen Dank!!!
Das ist in RegExLab aber sehr versteckt! Es könnte sich ja wenigstens die Hintergrundfarbe ändern, wenn es mehere Treffer gibt.
Durch "scrollen" der Pattern-Zeile werden die weiteren Ergebnisse angezeigt und diese erhalten dann auch eine andere Hintergrundfarbe (siehe Bild).
Sorry.
Hallo Wilfried!
Danke für deine Links.
Hallo dannoe!
Das ist ja der Hammer! Ich habe das "nur" mit dem hier im Forum entwickelten regexlab getestet und da wird nur ein Treffer angezeigt (Siehe Bild).
Da ich im Moment nicht an ein VS komme kann ich es ad hoc nicht nachvollziehen, aber ich bin mir sicher, dass deine Prüfung richtig ist!
Vielen Dank!!!
Hallo!
Man hätte es viel schneller ausprogrogrammiert, aber ... irgendwann verstehe ich auch noch RegEx.
Ich möchte aus einnem Text alle Jahreszahlen (im Bereich von 18xx ... 20xx) extrahieren.
Wenn der Text z.B. "ab1998ist 2000gg1898" lautet , dann müsste ich 3 Treffer für 1998, 2000 und 1898 erhalten.
Einen Treffer erhalte ich durch:
(?:(?:18|19|20)[0-9]{2})
Wie sage ich aber "RegEx" das er alle Treffer findet?
Erster Treffer:
(?:(?:18|19|20)[0-9]{2})
Letzter Treffer:
(?:(?:18|19|20)[0-9]{2})*$
(Soll mit 18, 19, oder 20 beginnen und dann 2 Ziffern folgen) * funktioniert so nicht.
Die Jahreszahlen können auch unmittelbar innerhalb eines Wortes eingeschlossen sein!
In dem Text stehen auch andere Zahlen mit mehr oder weniger Stellen, die nicht erfasst werden sollen.
Hallo Palladin007!
Einfach ist gut! → DispatcherObject
Kann man den Type Color direkt serialisieren? Ist eine Struktur da geht das doch bestimmt wieder nicht. Dann ist es einfacher den Converter: String <-> SolidColorBrush zu implementieren.
Na dann bis demnächst ... 😃
Hallo Palladin007, Hallo Th69!
Woher soll der geneigte Programmierer wissen, welche Objekte aus einer UI-Klasse stammen? Anhand meiner Programmier-Bibel (Buch: WPF von Thomas Claudius Huber) erkenne ich zwar aus der WPF-Klassenhierarchie das Brush von den Objekten: Object → DispatcherObject → DependencyObject → Freezable → Animatable erbt, aber wie ist der Zusammenhang zur Eingruppierung eines Objektes in eine UI-Klasse???
@Th69
Da ich die PK-Klasse serialisiere und aus diesem Grund die darin enthaltenen Brush'es sowieso schon als String (Color-Wert) speichere und bei der Deserialisierung (in der PK Klasse) wieder in Brush'es zurück konvertiere, spare ich mir dies jetzt (Erkenntnis UI-Klassen-Objekte gehören nicht ins VM) und verwende in der Applikation einen String <-> SolidColorBrush Konverter. Vielen Dank!
[Serializable]
/// <summary>
/// Klasse zur Strukturierung der Programm-Konfiguration.
/// </summary>
public class PK
{
/// <summary>
/// HG-Farbe der Bereiche des Programms
/// </summary>
[System.Xml.Serialization.XmlIgnore]
public Brush FarbeHGBereiche { get; set; }
public string FarbeHGBereiche_Color { get; set; } = "#FFADD8E6"; // HG-Farbe der Bereiche des Programms als String
....
public PK()
{
FarbeHGBereiche = (SolidColorBrush)new BrushConverter().ConvertFromString(FarbeHGBereiche_Color); // HG-Farbe der Bereiche
...
}
}
@Palladin007
Du hast das Problem wieder einmal auf den Punkt zusammengefasst! Bewusst habe ich View-Objekt und ViewModel natürlich nicht "gemischt".
Jetzt weis ich zwar wie ich den Brush auch Freezed'en kann (und das es dann funktioniert), aber ich werde die PK-Klasse auf String-Brushes (Stringwert, der ein Color-Wert darstellt) umstellen.
Was sich doch so alles aus einem Thread-Fehler ergibt!
Ich habe viel grundlegendes über Daten-Threads im ViewModel gelernt! Danke!
Vielen Dank an Alle, die mir geholfen haben!
Hallo Palladin007!
Die Ursache habe ich wieder einmal gefunden, aber ...
Der Fehler tritt so lang auf, solang eine Eigenschaft in der View an die VM-Eigenschaft PK gebunden ist.
Im konkretem besteht meine View nur noch aus einem Border, dessen Background an PK.FarbeHGBereiche gebunden ist.
<Border ... Background="{Binding PK.FarbeHGBereiche}" />
Wenn ich das PK-Binding lösche, wird die Exception nicht mehr geworfen.
... Habe weiter experimentiert ... Es liegt wirklich im Detail!!!
PK.Text kein Problem. PK.Brush = Exception. Die Ursache liegt in der CanFreeze-Eigenschaft des Brush'es.
Habe bloß noch nicht gefunden, wie man das ändern kann.
FarbeHGBereiche.CanFreeze = false;
Eine direkte Zuweisung ist nicht möglich. Der Standard-Wert ist True, deshalb ist die Farbe auch eingefroren und lässt sich nicht ändern.
Diese Abhängigkeit lässt sich jetzt auch ohne Probleme in der Beispiel-Applikation reproduzieren.
Aber nicht mehr Heute!!!! (Lösung finden)
Nochmals vielen Dank!!! Ohne dich wäre ich nie so weit gekommen!
Nein, so etwas brauche ich nicht- 😃 😃
Ich versuche eine Kopie des Projektes jetzt so lang "leer zu räumen", bis der Fehler nicht mehr auftritt.
Kann dauern ... (Converter und Behaviors habe ich schon entfern, hat noch nichts gebracht ...).
Vielen Dank für deine anhaltende Unterstützung!!!
Hallo Palladin007!
Na ich habe da schon noch ein paar Changed-Behavior's im Angebot! Wenn du das meinst.
OnChanged(nameof(PK)); löse ich an sehr vielen Stellen aus. Aber das ist doch kein Abonieren. Oder meinst du das?
Hallo Palladin007!
Jetzt geht's ans Eingemachte 😃 an der Stelle wäre bei mir Schluß! Aber sehen wir mal wie weit wir kommen!
private async void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
MainWindow view = (MainWindow)sender;
VMDVDVerwaltung vm = (VMDVDVerwaltung)((MainWindow)sender).DataContext;
System.Diagnostics.Debug.WriteLine($"{Environment.CurrentManagedThreadId} | { System.Threading.SynchronizationContext.Current?.GetType()?.Name}");
await System.Threading.Tasks.Task.Delay(1000); // 2s warten, damit Splash-Screen abgeschlossen ist
System.Diagnostics.Debug.WriteLine($"{Environment.CurrentManagedThreadId} | { System.Threading.SynchronizationContext.Current?.GetType()?.Name}");
#region Programm-Konfiguration laden
vm.AppData_Pfad = ar_Global.ar_Path.BuildCommonAppDataPath(); // Anwendungs-Unterverzeichnis im zentralem Programm-Daten-Verzeichnis (AllUser) erstellen, wenn es noch nicht vorhanden ist.
ErgAsync Erg_LoadPK = await System.Threading.Tasks.Task.Run(() => ProgrammDaten_LoadPK2());
System.Diagnostics.Debug.WriteLine($"{Environment.CurrentManagedThreadId} | { System.Threading.SynchronizationContext.Current?.GetType()?.Name}");
if (Erg_LoadPK.IsErfolgreich) vm.PK = (PK)Erg_LoadPK.Daten; // Eingelesene Programm-Konfiguration übernehmen ----> Exception
...
Dies erzeugt die Ausgabe:
"DVDVerwaltung.exe" (CoreCLR: clrhost): "C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\5.0.17\de\PresentationCore.resources.dll" geladen. Das Modul wurde ohne Symbole erstellt.
1 | DispatcherSynchronizationContext
1 | DispatcherSynchronizationContext
1 | DispatcherSynchronizationContext
2. "Eigene" PropertyChanged-Eventhandler habe ich:
[Serializable]
/// <summary>
/// Basisklasse, die die OnChanged-Methode auf Basis der INotifyPreopertyChanged-Schnittstelle bereitstellt.
/// </summary>
public class MVVM_Base : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Dispatcher-Objekt welches den aktuellen (current) Dispatcher des ViewModels bereitstellt.
/// (Für den Vergleich, ob sich das an die GUI gebundene Objekt in einem anderen Thread befindet.)
/// Aufrufbeispiel:
/// = VM ====
/// if (DispatcherObject.Thread != System.Threading.Thread.CurrentThread)
/// {
/// DispatcherObject.Invoke(() => ... Zugriff auf GUI-Objekt )));
/// }
/// </summary>
public virtual System.Windows.Threading.Dispatcher DispatcherObject { get; protected set; }
protected MVVM_Base()
{
DispatcherObject = System.Windows.Threading.Dispatcher.CurrentDispatcher;
}
}
... ich denke aber, diese sind Standard!?
Das DispatcherObject ist das CurrentDispatcher-Object des aktuellen Threads.
Anmerkung: Diese Basis-Klasse verwende ich sowohl im aktuellen wie auch im Test - Projekt. (Ist Standard bei mir.) Im Test-Projekt funktioniert das (komischerweise).
Ergebnis im Test-Projekt:
1 | DispatcherSynchronizationContext
1 | DispatcherSynchronizationContext
1 | DispatcherSynchronizationContext
1 | DispatcherSynchronizationContext
1 | DispatcherSynchronizationContext
Hilft das?
Hallo Palladin007!
Ja, die Bezeichnung ist nicht so gut gewählt. PK ist sowohl ein Type wie auch eine Eigenschaft.
Es gibt also die Klasse PK:
#region Programm-Konfiguration
[Serializable]
/// <summary>
/// Klasse zur Strukturierung der Programm-Konfiguration.
/// </summary>
public class PK
{
...
}
als auch die Eigenschaft PK im ViewModel:
public class VMDVDVerwaltung : MVVM_Base
{
/// <summary>
/// Programm-Konfiguration
/// </summary>
private PK _PK;
public PK PK { get => _PK; set { _PK = value; OnChanged(nameof(PK)); } }
....
}
Ja, das hatte auch seinen Sinn (Übergabe der PK-Eigenschaft). Wenn die Programm-Konfigurations-Datei nicht aus der Datei geladen werden kann, wird eine Standard Programm-Konfiguration (= neue Instanz der PK-Klasse) verwendet. Du hast aber recht, dass kann ich jetzt jetzt komplett in die ProgrammDaten_LoadPK2 Methode verschieben und mir den Parameter sparen. Ich bin sogar noch einen Schritt weiter gegangen und habe den gesamten Inhalt (zum Laden der XML-Datei) entfernt:
public static async System.Threading.Tasks.Task<ErgAsync> ProgrammDaten_LoadPK2()
{
PK PK2 = new();
await System.Threading.Tasks.Task.Delay(10); // Placeholder for asynchronous work.
ErgAsync erg = new() { IsErfolgreich = true }; // Ergebnis initalisieren
erg.Daten = PK2; // Programm-Konfiguration übergeben
erg.Meldung += "Die Programm-Konfiguration wurde geladen."; // Meldung aktualisieren
return erg; // Ergebnis übergeben
}
Und trotzdem wird immer noch die Exception geworfen! Siehe Bild.
Es wird sogar noch unerklärlicher ... in der Beispiel-Applikation funktioniert der 1:1 übernommene Code! :0
(Load-Behavior → asynchroner Abruf der Daten mit casten der Daten in der Ergebnis-Struktur)
???
public class WindowLoadedBehavior : Behavior<MainWindow>
{
protected override void OnAttached() => AssociatedObject.Loaded += AssociatedObject_Loaded;
protected override void OnDetaching() => AssociatedObject.Loaded -= AssociatedObject_Loaded;
private async void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
MainWindow view = (MainWindow)sender;
MyVM vm = (MyVM)((MainWindow)sender).DataContext;
vm.MyVMEigenschaft = await System.Threading.Tasks.Task.Run(() => MyDatenLoad()); // Eingelesene Daten (in's VM) übernehmen
await Task.Delay(1000); // 1s warten (und async ermöglichen)
ErgAsync Erg_LoadDaten2 = await System.Threading.Tasks.Task.Run(() => MyDatenLoad2("Hallo"));
if (Erg_LoadDaten2.IsErfolgreich) vm.MyVMEigenschaft = (string)Erg_LoadDaten2.Daten; // Eingelesene Daten übernehmen
await Task.Delay(1000); // 1s warten (und async ermöglichen)
ErgAsync Erg_LoadDaten3 = await System.Threading.Tasks.Task.Run(() => MyDatenLoad3());
if (Erg_LoadDaten3.IsErfolgreich) vm.MyVMEigenschaft = ((PK)Erg_LoadDaten3.Daten).DateiNameAblageTypenListe; // Eingelesene Daten übernehmen
ErgAsync Erg_LoadPK = await System.Threading.Tasks.Task.Run(() => ProgrammDaten_LoadPK2());
if (Erg_LoadPK.IsErfolgreich) vm.PK = (PK)Erg_LoadPK.Daten; // Eingelesene Programm-Konfiguration übernehmen
await Task.Delay(3000); // 3s warten (und async ermöglichen)
view.Close(); // Fenster schließen
}
#region Daten-Abrufe
/// <summary>
/// Daten laden.
/// </summary>
/// <returns>String zurückgeben.</returns>
public static async Task<string> MyDatenLoad()
{
await Task.Delay(1000); // 1s warten (und async ermöglichen)
return "Meine Daten ..."; // Ergebnis übergeben
}
/// <summary>
/// Daten laden.
/// </summary>
/// <returns>Ergebnis-Struktur zurückgeben.</returns>
public static async Task<ErgAsync> MyDatenLoad2(string myText)
{
await Task.Delay(1000); // 1s warten (und async ermöglichen)
return new(myText + " Meine Daten 2 ...", true); // Ergebnis übergeben
}
/// <summary>
/// Daten laden.
/// </summary>
/// <returns>Ergebnis-Struktur und Klasse zurückgeben.</returns>
public static async Task<ErgAsync> MyDatenLoad3()
{
await Task.Delay(2000); // 1s warten (und async ermöglichen)
return new(new PK(), true); // Ergebnis übergeben
}
public static async System.Threading.Tasks.Task<ErgAsync> ProgrammDaten_LoadPK2()
{
PK PK2 = new();
await Task.Delay(10); // Placeholder for asynchronous work.
ErgAsync erg = new() { IsErfolgreich = true }; // Ergebnis initalisieren
erg.Daten = PK2; // Programm-Konfiguration übergeben
erg.Meldung += "Die Programm-Konfiguration wurde geladen."; // Meldung aktualisieren
return erg; // Ergebnis übergeben
}
#endregion
}
Und die Hilfsklasse zur Strukturierung der Abruf-Ergebnisse:
public class ErgAsync
{
/// <summary>
/// Die Daten der asynchronen Abfrage.
/// </summary>
public object Daten { get; set; }
/// <summary>
/// Indikator: War der asynchrone Vorgang erfolgreich.
/// </summary>
public bool IsErfolgreich { get; set; }
/// <summary>
/// Indikator: Wurde die Programm-Konfiguration geändert?.
/// </summary>
public bool HasChangePK { get; set; }
/// <summary>
/// Meldungs-Text.
/// </summary>
public string Meldung { get; set; } = string.Empty;
public SolidColorBrush MeldungsHG { get; set; }
public ErgAsync() { }
public ErgAsync(object daten, bool isErfolgreich, bool hasChangePK = false, string meldung = "", SolidColorBrush meldungsHG = null)
{
Daten = daten; IsErfolgreich = isErfolgreich; HasChangePK = hasChangePK;
Meldung = meldung; MeldungsHG = meldungsHG is null ? Brushes.Transparent : meldungsHG;
}
}
Ich bin gerade dabei eine Beispiel-Applikation zu basteln, damit es leicht(er) nachzuvollziehen ist.
Entgegen meiner Vermutung, wenn ich einen String (allein) als Rückgabewert benutze, kommt keine Exception. Vielleicht hängt es irgendwie mit der Kapslung des Rückgabe-Wertes als Objekt zusammen?
if (PK2 is null) // Programmkonfiguration konnte nicht eingelesen werden
{
_ = MessageBox.Show("Es konnte keine (gültige) Programm-Konfigurations-Datei gefunden werden." + Environment.NewLine + Environment.NewLine +
"Es wird eine Standard-Konfiguration geladen." + Environment.NewLine + Environment.NewLine +
"Sie können die Programm-Konfiguration nach ihren Erfordernissen anpassen und speichern (Reiter Einstellungen).",
"Erstaufruf des Programm's!", MessageBoxButton.OK, MessageBoxImage.Hand);
PK2 = new(); // Standard Programm-Konfiguration übernehmen (wurde evt. beim Deserialisieren Null gesetzt!)
}
if (string.IsNullOrEmpty(PK2.DatenVerzeichnis)) // Es wurde (noch) kein Daten-Verzeichnis definiert oder die Programm-Konfiguration wurde noch nie gespeichert! (1. Einlesen der Programm-Konfiguration)
{
string StandardDatenVerzeichnis = Path.Combine(AppDataVerz, PK2.StandardVerzeichnisNameDatenVerzeichnis); // Standard Daten-Verzeichnis initalisieren
if (!Directory.Exists(StandardDatenVerzeichnis)) // Das Standard-Daten-Verzeichnis exestiert noch nicht!
{
try // Das Standard Daten-Verzeichnis erstellen
{
_ = Directory.CreateDirectory(StandardDatenVerzeichnis); // Daten-Verzeichnis erstellen
PK2.DatenVerzeichnis = StandardDatenVerzeichnis; // In der Programm-Konfiguration das Daten-Verzeichnis auf das Standard-Daten-Verzeichnis setzen
erg.Meldung = "Das Standard Daten-Verzeichnis wurde erstellt." + Environment.NewLine;
erg.HasChangePK = true; // Indikator: Programm-Konfiguration wurde geändert, setzen
erg.MeldungsHG = Brushes.Green; // Meldung Hintergrund aktualisieren
}
catch (Exception ex) // Die Erstellung des Standard Daten-Verzeichnisses konnte nicht korrekt ausgeführt werden!
{
string Mldg = "Das Standard Daten-Verzeichnis: " + Environment.NewLine + Environment.NewLine;
Mldg += StandardDatenVerzeichnis + Environment.NewLine + Environment.NewLine;
Mldg += "konnte nicht erstellt werden!!!" + Environment.NewLine + Environment.NewLine;
Mldg += "Das Programm kann nicht fortgeführt werden!" + Environment.NewLine + Environment.NewLine;
Mldg += "Weitere Hinweise entnehmen sie bitte der Fehlermeldung." + Environment.NewLine + Environment.NewLine;
Mldg += "Fehler:" + Environment.NewLine + Environment.NewLine + ex.Message;
_ = MessageBox.Show(Mldg, "Ohne Daten-Verzeichnis geht's nicht!", MessageBoxButton.OK, MessageBoxImage.Error);
return new ErgAsync(null, false, false, "Das Daten-Verzeichnis konnte nicht erstellt werden!", Brushes.Red);
}
}
}
erg.Daten = PK2; // Programm-Konfiguration übergeben
erg.Meldung += "Die Programm-Konfiguration wurde geladen."; // Meldung aktualisieren
return erg; // Ergebnis übergeben
}
#endregion
}
und Part 3 ...
Hallo Abt!
Zur Zeit versuche ich die Daten im WindowLoadedBehavior zu laden. Die alte LoadDaten()-Methode habe ich erst einmal auskommentiert.
Hier der vollständige Behavior:
public class WindowLoadedBehavior : Behavior<MainWindow>
{
protected override void OnAttached() => AssociatedObject.Loaded += AssociatedObject_Loaded;
protected override void OnDetaching() => AssociatedObject.Loaded -= AssociatedObject_Loaded;
private async void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
MainWindow view = (MainWindow)sender;
VMDVDVerwaltung vm = (VMDVDVerwaltung)((MainWindow)sender).DataContext;
// Nach dem Laden des Fensters die ListView-Spalten-Breite aktualisieren
if (vm.DVDEinträgeView != null) // Sind DVD-Einträge vorhanden?
{
vm.DVDEinträgeView.MoveCurrentToLast(); // Zum letzten Eintrag gehen
view.lv.ScrollIntoView(vm.DVDEinträgeView.CurrentItem); // Eintrag in sichtbaren Bereich verschieben
arWPF.WPF_Tools.WPF_Tools.ListView_Helper.UpdateListViewColumns(view.lv); // List-View Spalten-Breite aktualisieren
}
await System.Threading.Tasks.Task.Delay(1000); // 2s warten, damit Splash-Screen abgeschlossen ist
#region Programm-Konfiguration laden
vm.AppData_Pfad = ar_Global.ar_Path.BuildCommonAppDataPath(); // Anwendungs-Unterverzeichnis im zentralem Programm-Daten-Verzeichnis (AllUser) erstellen, wenn es noch nicht vorhanden ist.
vm.PK = new(); // Standard Programm-Konfiguration initalisieren
ErgAsync Erg_LoadPK = await System.Threading.Tasks.Task.Run(() => ProgrammDaten_LoadPK2(vm.PK, vm.AppData_Pfad));
if (Erg_LoadPK.IsErfolgreich) vm.PK = (PK)Erg_LoadPK.Daten; // Eingelesene Programm-Konfiguration übernehmen
#endregion Abschluß: Programm-Konfiguration
}
#region Daten-Abrufe
/// <summary>
/// Programm-Konfiguration (asynchron) einlesen.
/// </summary>
/// <param name="StdPK">Standard-Konfigurations-Daten.</param>
/// <param name="AppDataVerz">Standard Programm-Daten-Verzeichnis.</param>
/// <returns>Indikator: Daten: Programm-Konfiguration + Indikator: Konnten die Programm-Konfigurations-Daten ordnungsgemäß geladen werden?</returns>
public async System.Threading.Tasks.Task<ErgAsync> ProgrammDaten_LoadPK2(PK PK2, string AppDataVerz)
{
await System.Threading.Tasks.Task.Delay(10); // Placeholder for asynchronous work.
ErgAsync erg = new() { IsErfolgreich = true }; // Ergebnis initalisieren
if (string.IsNullOrEmpty(AppDataVerz)) // Ist das Anwendungs-Unterverzeichnis im Programm-Daten-Verzeichnis vorhanden?
{
MessageBox.Show("Auf das Programmkonfigurationsverzeichnis (" + ar_Global.ar_Path.GetProgDataPath() + ") kann nicht zugegriffen werden." + Environment.NewLine + Environment.NewLine +
"Wenden sie sich an ihren Programm-Administrator!", "Programm-Administrator benachrichtigen!",
MessageBoxButton.OKCancel, MessageBoxImage.Hand);
return new ErgAsync(PK2, false, false, "Programmkonfigurations-Verzeichnis ist nicht vorhanden!!!", Brushes.Red);
}
if (File.Exists(Path.Combine(AppDataVerz, PK2.KonfigDateiName))) // Programm-Konfigurations-Datei vorhanden? -> PK aus XML-Datei "laden"
{
PK2 = ar_Global.Serialisieren.DeSerializeObject<PK>(Path.Combine(AppDataVerz, PK2.KonfigDateiName)); // Programm-Konfigurations-Datei versuchen zu "laden"
#region Gespeicherte Farben übernehmen
PK2.FarbeHGBereiche = (SolidColorBrush)new BrushConverter().ConvertFromString(PK2.FarbeHGBereiche_Color); // Farbe der Hintergrund-Bereiche übernehmen
PK2.FarbeHGDVDListe = (SolidColorBrush)new BrushConverter().ConvertFromString(PK2.FarbeHGDVDListe_Color); // Farbe des Hintergrunds der DVD-Liste übernehmen
#endregion
}
Nur 8000 Zeichen ... Part 2 folgt
Hallo Palladin007!
Die Exception tritt genau bei der Zuordnung des Abgerufenen Wertes zur ViewModel-Eigenschaft (vm.PK) und da genau (im Setter) bei der Ausführung der OnChange-Methode, auf.
if (Erg_LoadPK.IsErfolgreich) vm.PK = (PK)Erg_LoadPK.Daten; // Eingelesene Programm-Konfiguration übernehmen
Wie im Bild ersichtlich, wurde die ProgrammDaten_LoadPK2-Methode korrekt (und abschließend) ausgeführt. Alle eingelesenen Daten sind korrekt.
Ich bin mir sicher, wenn ich einen einfachen String zurückgeben würde, würde die Exception auch ausgerufen.
So geht es auch nicht! Die Datei lese ich ja immer in einem Arbeits-Thread ein. Wenn await das eingelesene Objekt nicht dem GUI-Thread zuordnet wird es immer krachen.
WindowLoadedBehavior
{
async AssociatedObject_Loaded
{
----- GUI-Thread
Erg = await System.Threading.Tasks.Task.Run(() => ProgrammDaten_LoadPK2());
vm.PK = (PK)Erg.Daten; // Eingelesene Programm-Konfiguration übernehmen ----> Exception <-------
}
public async System.Threading.Tasks.Task<ErgAsync> ProgrammDaten_LoadPK2(PK PK2, string AppDataVerz)
{
---- Daten-Thread
...
return erg; // Ergebnis übergeben
}
}
Etwas ausführlicher ...
public class WindowLoadedBehavior : Behavior<MainWindow>
{
protected override void OnAttached() => AssociatedObject.Loaded += AssociatedObject_Loaded;
protected override void OnDetaching() => AssociatedObject.Loaded -= AssociatedObject_Loaded;
private async void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
// im GUI-Thread ----------
MainWindow view = (MainWindow)sender;
VMDVDVerwaltung vm = (VMDVDVerwaltung)((MainWindow)sender).DataContext;
#region Programm-Konfiguration laden
vm.AppData_Pfad = ar_Global.ar_Path.BuildCommonAppDataPath();
vm.PK = new(); // Standard Programm-Konfiguration initalisieren
ErgAsync Erg_LoadPK = await System.Threading.Tasks.Task.Run(() => ProgrammDaten_LoadPK2(vm.PK, vm.AppData_Pfad));
if (Erg_LoadPK.IsErfolgreich) vm.PK = (PK)Erg_LoadPK.Daten; // Eingelesene Programm-Konfiguration übernehmen
#endregion Abschluß: Programm-Konfiguration
public async System.Threading.Tasks.Task<ErgAsync> ProgrammDaten_LoadPK2(PK PK2, string AppDataVerz)
{
// im Daten-Thread ----------
...
return erg; // Ergebnis übergeben
}
}
}
Hallo Th69!
LoadDaten läuft im Daten-Thread.
Mal einen kleinen Moment (so einen wie in der Werbung 😉) ProgrammDaten_LoadPK asynchron vom Behavior aus aufzurufen und da das Ergebnis an die VM-Eigenschaft zu übergeben ist (vielleicht) auch eine gute Idee!
Hallo Th69!
LoadDataAsync rufe ich vom GUI-Thread WindowLoadedBehavior → VM → LoadDaten() aus auf.
public class WindowLoadedBehavior : Behavior<MainWindow>
{
protected override void OnAttached() => AssociatedObject.Loaded += AssociatedObject_Loaded;
protected override void OnDetaching() => AssociatedObject.Loaded -= AssociatedObject_Loaded;
private async void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
MainWindow view = (MainWindow)sender;
VMDVDVerwaltung vm = (VMDVDVerwaltung)((MainWindow)sender).DataContext;
...
bool HasDatenLoaded = await vm.LoadDaten(); // Programm-Daten laden
}
}
Im Daten-Thread ordne (möchte) ich der VM-Eigenschaft (PK) den eingelesenen Wert zuordnen: ( if (Erg_LoadPK.IsErfolgreich) PK = (PK)Erg_LoadPK.Daten; // Eingelesene Programm-Konfiguration übernehmen )
/// <summary>
/// Alle Programm-Daten laden.
/// </summary>
public async System.Threading.Tasks.Task<bool> LoadDaten()
{
#region Programm-Konfiguration laden
PK = new(); // Standard Programm-Konfiguration initalisieren
ErgAsync Erg_LoadPK = await System.Threading.Tasks.Task.Run(() => ProgrammDaten_LoadPK(PK, AppData_Pfad));
if (Erg_LoadPK.IsErfolgreich) PK = (PK)Erg_LoadPK.Daten; // Eingelesene Programm-Konfiguration übernehmen
Wenn an dieser Stelle im Setter (von PK) OnChange() ausgeführt wird, kracht (Exception) es.
Sorry, ich muss mein Problem noch einmal "wiederbeleben" ...
Im Prinzip ist es noch genau das Gleiche wie am Anfang ... jetzt kann ich die Quelle jedoch genauer lokalisieren.
System.ArgumentException
DependencySource muss in demselben Thread wie DependencyObject erstellt werden.
Dieser Fehler tritt immer genau dann auf, wenn ich ein im Daten-Thread eingelesenes Objekt in der View aktualisieren möchte.
Beispiel (View Aktualisierung explizit):
public class MyVM : MVVM_Base
{
private PK _data1;
public PK data1 { get => _data1; set { _data1 = value; } }
...
public async Task LoadDataAsync()
{
data1 = await LoadDataFromXML1Async(); // Wird ohne Fehler der VM-Eigenschaft zugewiesen
data2 = await LoadDataFromXML2Async(data1);
...
}
...
public void ProgrammDatenViewAktualisieren()
{
OnChanged(nameof(data1)); // ---> Exception !!!
}
}
Beispiel (View Aktualisierung implizit):
public class MyVM : MVVM_Base
{
private PK _data1;
public PK data1 { get => _data1; set { _data1 = value; OnChanged(nameof(data1)); } }
...
public async Task LoadDataAsync()
{
data1 = await LoadDataFromXML1Async(); // ---> Exception !!!
data2 = await LoadDataFromXML2Async(data1);
...
}
Der Sachverhalt ist so ja auch erst einmal verständlich, wenn man auch sonst den umgekehrten Fall hat, dass man aus dem Daten-Thread die View aktualisieren möchte. Ich dachte nur, dass await das eingelesene Objekt dem GUI-Thread zuordnet.
Hallo Palladin007!
Das ist ein Missverständnis. Mit View meinte ich eine DataView (ist am Ende wieder eine Liste)! Wenn ich diese in einen Task auslagere und dann darauf "warte" wird die Reihenfolge auch korrekt eingehalten:
public async Task LoadDataAsync()
{
data1 = await LoadDataFromXML1Async();
data2 = await LoadDataFromXML2Async(data1);
data3 = await LoadDataFromXML3Async(data1);
data4 = await Vorgang1_BuildDataView();
}
Deine "Struktur" hat mir für das Verständnis (was ist wo verfügbar) sehr geholfen!!!
Vielen Dank noch einmal!
Hallo BlonderHans!
Die Initalisierung der Daten im Load-Behavior ist für mich erst einmal eine einfache Lösung.
Ich werde mir deine Lösung aber auch noch einmal in Ruhe ansehen, eine Basisklasse ist ja auch einfach immer wieder zu benutzen.
Vielen Dank!
Hallo Palladin07!
Die Struktur ist schon einmal sehr hilfreich! Danke!!!
Für den Abruf der Daten von einer XML-Datei funktioniert das wunderbar.
Ich kämpfe jedoch noch mit der Synchronisation von meheren Abrufen.
public async Task LoadDataAsync()
{
data1 = await LoadDataFromXML1Async();
data2 = await LoadDataFromXML2Async(data1);
data3 = await LoadDataFromXML2Async(data1);
data4 = Vorgang1_BuildView(data1,data3);
}
Im Prinziep möchte ich, dass Als erstes data1 abgerufen wird, danach data2 oder data3 und am Schluß Vorgang1 ausgeführt wird.
Ich könnte noch im asynchronen data1-Abruf data2 und data3 (synchron) mit abrufen und das Ergebnis im LoadDataAsync Task den ViewModel-Eigenschaften zuordnen, aber wie setze ich Vorgang 1 an den Schluß?
public async Task LoadDataAsync()
{
data13 = await LoadDataFromXML1...3Async();
data1 = data13.1;
data2 = data13.2;
data3 = data13.3;
data4 = Vorgang1_BuildView(data1,data3)
}
Hallo Palladin007!
Danke für deinen Hinweis! Ich habe es auch schon mal über einen Timer versucht ...
Du kannst es aber auch in einem UI-Event das ViewModel über den DataContext suchen und dort die LoadDataAsync-Methode aufrufen, das wäre auch noch MVVM konform.
Ich habe sowieso einen Load-Behavior implementiert. Auch an dieser Stelle könnte ich die LoadData-Async-Methode aufrufen, aber das löst ja nicht mein Problem. Der Aufruf ist aber sauberer. Danke!
public class WindowLoadedBehavior : Behavior<MainWindow>
{
protected override void OnAttached() => AssociatedObject.Loaded += AssociatedObject_Loaded;
protected override void OnDetaching() => AssociatedObject.Loaded -= AssociatedObject_Loaded;
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
MainWindow view = (MainWindow)sender;
VMDVDVerwaltung vm = (VMDVDVerwaltung)((MainWindow)sender).DataContext;
...
vm.LoadDaten(); // Programm-Daten laden
}
}
Hallo!
Ich habe jetzt schon viel ausprobiert aber...
Ich möchte in einer MVVM-Applikation im VM alle Daten der Applikation (XML-Dateien) asynchron laden.
public VMDVDVerwaltung() // Konstruktor
{
...
WndOpacity = 1; // Transparenz der View initalisieren
LoadDaten(); // Alle Programm-Daten laden
}
/// <summary>
/// Alle Programm-Daten laden.
/// </summary>
private async void LoadDaten()
{
WndOpacity = 0.5; // Fenster-Transparenz auf 0.5 setzen
bool erfolgreich = await System.Threading.Tasks.Task.Run(() => ProgrammDatenInitialisieren(DispatcherObject));
...
WndOpacity = 1; // Fenster-Transparenz auf 1 setzen
}
private bool ProgrammDatenInitialisieren(System.Windows.Threading.Dispatcher DP)
{
bool IsErfolgreich = true; // Indikator: Programm-Daten erfolgreich geladen?, initalisieren
#region Programm-Konfiguration
PK PK2 = __PK; // Standard Programm-Konfiguration immer übernehmen
PK2 = ar_Global.Serialisieren.DeSerializeObject<PK>(Path.Combine(AppData_Pfad, PK2.KonfigDateiName)); // PK aus XML laden
...
DP.Invoke(new Action(() => PK = PK2)); // Der PK im VM die eingelesene PK (aus XML) übergeben
return IsErfolgreich;
}
Bei der Übergabe der eingelesenen PK Eigenschaft:
DP.Invoke(new Action(() => PK = PK2)); // Der PK im VM die eingelesene PK (aus XML) übergeben
wird jetzt der Fehler: (ArgumentException)
DependencySource muss in denselben Thread wie DependencyObjekt erstellt werden.
geworfen.
Wie bekomme ich denn die asynchron eingelesenen Objekte des Daten-Threads ins ViewModel???
Oder muss ich das Daten-Einlesen vollkommen anders ausführen?
Hallo Palladin007, Hallo Th69!
Sorry, mit weiteren Antworten hatte ich garnicht mehr gerechnet!
[Flags]
enum MyEnum : long
{
None = 0,
A = 1 << 0, // 1
B = 1 << 1, // 2
C = 1 << 2, // 4
D = 1 << 3, // 8
}
Das ist ja wirklich sehr übersichtlich, besonders bei "höheren" Bits!!! (Da saß ich dann mit dem Taschenrechner da ...)
Vielen Dank für eure weiteren Hinweise!
Einen zweiten Nachtrag muss ich auch noch machen. Th69 hat in seiner Antwort die Lösung (komprimiert) genau so angegeben. Leider kann man hier eine Antwort nicht als Lösung deklarieren.
Also auch vielen Dank an dich!!!
Verzeichnis Auswahl-Dialog (Assembly)
Der Kern der Verzeichnis-Anzeige basiert auf dem API32 SHBrowseForFolder – Methoden-Aufruf.
Ihm wird als Parameter eine (parametrisierte) BROWSEINFO-Struktur übergeben. Diese steuert alle Optionen der Anzeige des Windows Verzeichnis-Dialoges:
API32.BFFCALLBACK _callbackKeepAlive = BrowseCallbackProc; // CallBack-Methode (für die BrowseInfo-Struktur) initalisieren
IntPtr buffer = Marshal.AllocHGlobal(MAX_PATH); // Speicherplatz für das Rückgabe-Verzeichnis reservieren
API32.BROWSEINFO bi = new(); // Browse-Info Struktur initalisieren
bi.pidlRoot = rootVerzeichnisPIDL; // Der PIDCList-Eintrag auf das Root-Verzeichnis übergeben
bi.hwndOwner = hwndDialogFenster; // Zeiger auf das Dialog-Fenster übergeben
bi.pszDisplayName = buffer; // Dem Zeiger auf die DisplayName-Eigenschaft den Zeiger auf den reservierten Speicher übergeben
bi.lpszTitle = Title; // Den Titel des Verzeichnis-Dialoges übergeben
bi.ulFlags = publicOptions | (int)API32.BFFStyles.NewDialogStyle; // Den Flags alle eingestellten Dialog-Optionen und das neue Dialog-Style-Flag übergeben
bi.lpfn = _callbackKeepAlive; // CallBack Methode zum selektieren des Select-Verzeichnis-Eintrages im Dialog-Fenster (TreeView)
pidlRet = API32.SHBrowseForFolder(ref bi); // Verzeichnis-Dialog anzeigen. Das Ergebnis der Auswahl ist ein (Zeiger auf einen) PIDCList-Eintrag auf das ausgewählte Verzeichnis.
An dieser Stelle gibt es eigentlich nur einen Knackpunkt. Das ist das selektieren des Eintrages das dem Select-Verzeichnis zugeordnet werden soll. Ein iterieren durch das TreeView des Auswahl Verzeichnis-Dialog-Fensters scheitert an der Möglichkeit sich im TreeView immer weiter (rekursiv) fortzubewegen. Die Lösung stellt die Definition einer CallBack-Methode (hier BrowseCallbackProc), die man ebenfalls in der BROWSEINFO-Struktur zuordnen kann (bi.lpfn=), dar.
In ihr kann man abfragen, ob die Initalisierung des Dialog-Fensters abgeschlossen wurde und dann eine BFFM_SETSELECTIONW Nachricht an das Dialog-Fenster senden in dessen Parameter das Select-Verzeichnis (sogar als String!) angegeben ist, um den passenden TreeView-Eintrag zu selektieren. Gleichzeitig verschiebt diese Nachricht den selektierten Eintrag noch in den sichtbaren Bereich, aber das funktioniert nicht ganz so zuverlässig.
private int BrowseCallbackProc(IntPtr hwnd, uint msg, IntPtr lParam, IntPtr lpData)
{
if (msg == API32.BFFM_INITIALIZED) // Wenn die Initalisierung des Dialog-Fensters abgeschlossen wurde ...
{
_ = API32.SendMessage(hwnd, API32.BFFM_SETSELECTIONW, 1, _selectedPath) // Das TreeView-Item mit der Bezeichnung des Select-Verzeichnisses selektieren
}
return 0; // Immer das Flag: Erfolgreich zurückgeben
}
Am Ende muss man nur noch den von der SHBrowseForFolder Methode zurückgegebenen PDCList-Eintrag auswerten, dessen Wert auf das ausgewählte Verzeichnis, verweist.
if (pidlRet == IntPtr.Zero) return MessageBoxResult.Cancel; // Wenn der Benutzer den Dialog abgebrochen hat, dann ein Abbruch-Ergebnis zurückgeben
directoryPath = GetVerzeichnisString(pidlRet); // Ausgewähltes Verzeichnis aus dem Rückgabe PIDCList-Eintrag ermitteln
Wie die Parametrisierung der BROWSEINFO-Struktur im Detail erfolgt oder sonstige Details könnt ihr ebenfalls dem veröffentlichten Projekt: Projekt VerzeichnisDialog Assembly.zip, entnehmen.
Beide Projekte sind ausführlichst dokumentiert.
Für die Praktiker: Hier die GUI zum Testen als Programm (GUI.zip) und den Verzeichnis Auswahl-Dialog als Assembly-DLL (Assembly.zip).
Damit hier nicht der falsche Eindruck entsteht! Das habe ich nicht alles selbst "entwickelt" sondern nur zusammengetragen und für mich so lang aufbereitet, bis es (für mich) passt!!!
Besonderen Dank an charlieface von stackoverflow
Hallo!
Viel später als gedacht, aber jetzt ist es doch erledigt. Mein Verzeichnis Auswahl-Dialog (Framework: .NETCore Assembly).
Der Verzeichnis Auswahl-Dialog hat 4 Überladungen für den Aufruf.
Sie unterscheiden sich in der Art wie die Root und Select-Verzeichnis - Eigenschaft übergeben wird (Auswahl-Art). Diese Eigenschaften können als String (spezifiziert einen Verzeichnispfad) oder als Aufzählungswert der Aufzählung: System.Environment.SpecialFolder übergeben werden (Spezial-Verzeichnis). Über die Spezial-Verzeichnisse können Einträge im Verzeichnis-Dialog selektiert werden, die keine direkte Entsprechung im Dateisystem haben. (Desktop als oberste Ebene des Verzeichnis-Dialoges oder Arbeitsplatz als Sammelbehälter für weitere Spezial-Verzeichnisse und Datenträger).
MessageBoxResult ShowDialog(string, string) // Root + Select-Verzeichnis als Verzeichnis
MessageBoxResult ShowDialog(Environment.SpecialFolder, string) // Root als Spezial-Verzeichnis / Select als Verzeichnis
MessageBoxResult ShowDialog(Environment.SpecialFolder, Environment.SpecialFolder) // Root + Special – Verzeichnis als Spezial-Verzeichnis
MessageBoxResult ShowDialog(string, Environment.SpecialFolder) // Root als Verzeichnis und Select-Verzeichnis als Spezial-Verzeichnis
Zur leichteren Handhabung der Spezialverzeichnisse habe ich in der VerzeichnisDialog-Klasse eine Liste der Spezialverzeichnisse implementiert, durch die eine einfache Auswahl (auch mehrerer Spezialverzeichnisse = Gruppe) über Flags und die direkte Zuordnung einer Beschreibung des Spezial-Verzeichnisses zu der Spezialverzeichnis-Aufzählung ermöglicht wird.
Die Liste kann über die Methode:
GetSpezialVerzeichnisse (VerzListenFlag Flags)
Type: ObservableCollection<SpezialVerzeichnisEintrag>
abgerufen werden. Weitere Details zur Handhabung der Spezial-Verzeichnis Liste könnt ihr dem hier veröffentlichten Projekt: Projekt Verzeichnis Dialog GUI.zip, entnehmen.
An dieser Stelle ein paar Hinweise:
- Der Rückgabe-Type der ShowDialog-Methode (der VerzeichnisDialog-Klasse) ist eine, der System.Windows.MessageBoxResult – Aufzählung adäquate Aufzählung, in der VerzeichnisDialog-Klasse. (Dies ist notwendig um konsequent nur im Microsoft.NETCore.App Framework zu bleiben.)
- Das selektierte Verzeichnis wird nur dann (zuverlässig) in den sichtbaren Bereich verschoben, wenn die Auswahl-Art des Root- und Select – Verzeichnisses gleich sind und sich das Select-Verzeichnis unterhalb des Root-Verzeichnisses befindet.
- Wenn die Option OnlyFilesystem (Standard: Ja) auf Nein gestellt wird und ein Eintrag, der keine Entsprechung im Datei-System hat ausgewählt wird, wird bei der Bestätigung [OK-Schaltfläche] automatisch der Status: Abbruch (MessageBoxResult.Cancel) zurückgegeben.
- Wenn das Root-Verzeichnis durch die Auswahl-Art: Dateisystem spezifiziert wird, dann können die übergeordneten Spezial-Verzeichnisse: Desktop und Arbeitsplatz nicht als Select-Verzeichnis angegeben werden. Wenn sie doch ausgewählt werden, wird für diese automatisch das Benutzer-Dokumenten-Verzeichnis selektiert.
Die Bilder zu den einzelnen Varianten der Root- und Select- Verzeichnis-Auswahl stehen ebenfalls noch einmal hier im Verzeichnis Bilder zur Verfügung.
Hallo Palladin007!
Das Enum leite ich doch von long ab. Also Verwaltet das Enum long-Einträge. Oder ist dem nicht so???
public enum StdVerzListenFlag : long
Die MyFlags Eigenschaft muss also vom Typ long sein (vom gleichen Typ wie die Enum-Einträge)), sonst könnte ich sie ja nicht mit alle Einträgen die ich im Enum deklariere "vergleichen " (sie wäre einfach zu klein).
Bitte immer "sagen, wenn ich etwas verkehrt sehe!
Deine Deklaration:
StdVerzListenFlag MyFlags = StdVerzListenFlag.MYDOCUMENTS | StdVerzListenFlag.MYMYMUSIC; // Enum
war für mich am Anfang befremdlich, da es doch die Aussage trifft, dass MyFlags vom Typ ein StdVerzListenFlag-Eintrag sein soll. Den gibt es doch in dieser Kombination im gesammten Enum StdVerzListenFlag garnicht! Die Kombination stellt einen long-Wert dar. Indirekt ist jeder StdVerzListenFlag-Eintrag natürlich wieder ein long.
Deine Lösung sieht so aber deutlich besser aus als meine Lösung mit dem vielen casten und ich glaube auch, dass ich so langsam verstehe was du/ihr mit mischen meint!
Vielen Dank!
public enum StdVerzListenFlag : long
{
DESKTOP = 0, MYDOCUMENTS = 1, FAVORITES = 2, MYPICTURES = 4, MYMYMUSIC = 8, MYVIDEO = 16, STARTMENUFOLDER = 32,
STARTMENU = 64, STARTUP = 128, RECENT = 256, DESKTOPDIRECTORY = 512, NETWORKSHORTCUTS = 1024, FONTS = 2048,
COMMON_STARTMENU = 4096, COMMON_PROGRAMS = 8192, COMMON_STARTUP = 16384, COMMON_DESKTOPDIRECTORY = 32768,
COMMON_APPDATA = 65536, COMMON_DOCUMENTS = 131072, COMMON_MUSIC = 262144, COMMON_PICTURES = 524288,
COMMON_VIDEO = 1048576, APPLICATIONDATA = 4194304, INTERNETCACHE = 8388608, COOKIES = 16777216, HISTORY = 33554432,
USERPROFILE = 67108864, SYSTEM = 134217728, SYSTEMX86 = 268435456, PROGRAMFILES = 536870912,
PROGRAMFILESX86 = 1073741824, All = -1
}
(DESKTOP = 0) Das ist in der Tat sehr ungünstig, da dann alle Vergleiche mit DESKTOP immer True ergeben (ändere ich).
Ich hatte den Enum-Wert "Alle", am Ende, (deshalb war das nicht zu sehen) mit -1 deklariert.
Hallo Abt!
Ich verstehe deine Antwort nicht.
Ein Enum basiert ohne eine weitere Ableitung auf int.32. Wenn man das Enum als Flag benutzen möchte ergeben sich bei einer 2er Potenz (für Bit-Operationen) maximal 32 Flags (0,1,2,4,8 ... 2³²-1). Deshalb kann man dem Enum auch eine Ableitung auf den Datentyp long zuordnen.
[Flags]
public enum StdVerzListenFlag : long
Jede Ausprägung des Enums ist jetzt ein long.
Wenn ich jetzt das long MyFlags mit dem gecasteten long MYPICTURES Bitweise UND verknüpfe (was aus welchem Grund auch immer, jetzt geht?) bekomme ich ein korrektes Ergebnis.
long MyFlags = (long)StdVerzListenFlag.COMMON_APPDATA + (long)StdVerzListenFlag.MYPICTURES;
if ((MyFlags & (long)StdVerzListenFlag.MYPICTURES) == (long)StdVerzListenFlag.MYPICTURES) { }
Vielleicht hast du ja dafür eine Erklärung.
Hallo!
Ich habe eine Flag-Aufzählung als long deklariert, weil ich eventuell mehr als 32 Ausprägungen benötige.
[Flags]
public enum StdVerzListenFlag : long
{
DESKTOP = 0, MYDOCUMENTS = 1, FAVORITES = 2, MYPICTURES = 4, MYMYMUSIC = 8, MYVIDEO = 16, ...
Alle Bit-Operationen können dadurch aber nicht mehr angewandt werden.
Wie ordne ich denn nun mehere Ausprägungen einer (long) Variablen zu???
long MyFlags = StdVerzListenFlag.MYDOCUMENTS & StdVerzListenFlag.MYMYMUSIC;
Oder teste ob in einer Variablen eine Ausprägung enthalten ist?
if((MyFlags & StdVerzListenFlag.DESKTOP) == StdVerzListenFlag.DESKTOP) ...
Es wird sofort der Fehler:
CS0019 Der &-Operator kann nicht auf Operanden vom Typ "long" und "StdVerzListenFlag" angewendet werden.
geworfen.
PS:
Eine Zuordnung würde noch über ein casten der einzelnen Ausprägungen zu long gehen aber spätestens beim Vergleich stehe ich auf dem Schlauch.
long MyFlags = (long)StdVerzListenFlag.MYDOCUMENTS + (long)StdVerzListenFlag.MYPICTURES;
Sommerlich heiße Grüße!
Die erste Aufgabe konnte ich selbst lösen.
Der MSB4018 Fehler
Das ist ein Konflikt zwischen dem Dateinamen der Projekt-Datei und dem Dateinamen der Assembly-Datei.
Sie dürfen nicht gleich sein! (Dies ist vollkommen unabhängig vom NameSpace und Klassennamen.)
Beide betreffen Dateinamen und Verzeichnisnamen im Projekt und im Ausgabeverzeichnis ... dies kann VS wahrscheinlich nicht unterscheiden.
Die Lösung ist also den Dateinamen der Projekt-Datei umzubenennen. An den NameSpace und Klassennamen muss nichts verändert werden.
Hallo!
Wie sollte es anders sein, es gibt noch einige Stolpersteine ... 😃
Ich habe eine .Net 5.0 Klassenbibliothek mit einem NameSpace ASoft und einer (öffentlichen) Klasse VerzeichnisDialog erstellt.
namespace ASoft
{
public class VerzeichnisDialog
{
Zum testen der Klassenbibliothek habe ich die Assembly (VerzeichnisDialog.dll) in einen WPF-Projekt mit dem NameSpace: VerzeichnisDialog eingebunden:
namespace VerzeichnisDialog
{
public class VMVerzeichnisAuswahl : MVVM_Base
{
auf die Eigenschaften der Klassenbibliothek greife ich über den NameSpace der Assembly (ASoft) zu:
ASoft.VerzeichnisDialog dlgVerzeichnis = new();
...
ASoft.VerzeichnisDialog.MessageBoxResult erg = dlgVerzeichnis.ShowDialog(RootVerzeichnis, SelectVerzeichnis); // Verzeichnis-Dialog anzeigen
Wird von IntelliSense auch problemlos unterschieden.
Beim erstellen des WPF-Projektes kommt es jetzt aber zu den Fehler:
Fehler MSB4018 Unerwarteter Fehler bei der GenerateDepsFile-Aufgabe.
System.ArgumentException: Ein Element mit dem gleichen Schlüssel wurde bereits hinzugefügt.
bei System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
bei System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
Ein Element mit dem gleichen Schlüssel wurde bereits hinzugefügt.
VerzeichnisDialog gibt es zwei mal. Einmal als Public Class in der Assembly und einmal als NameSpace im WPF-Projekt. Aber das sind doch vollkommen verschiedene Sachen!
Ich habe das Projekt schon bereinigt und neu erstellen lassen, aber der Fehler kommt wieder.
Wie aus dem Code schon ersichtlich, musste ich die MessageBoxResult Aufzählung in der Klassenbibliothek neu definieren. Ich habe keinen Weg gefunden, wie ich die System.Windows.MessageBoxResult-Aufzählung in die Klassenbibliothek einbinde. In der Bibliothek gibt es im System.Windows - NameSpace nur die weiteren NameSpace Input und Markup.
Das liegt wahrscheinlich daran, dass unter den eingebundenen FrameWorks das Microsoft.Windows.Desktop.App FrameWork fehlt?
Könnt ihr mir dazu Hinweise geben?
Hallo Th69!
Sorry, in den letzten Tagen habe ich das Projekt schon 2x komplett überarbeitet, dass ich gar keine Zeit hatte hier noch einmal vorbei zu sehen!
Ja du hast recht, das iterieren durch das TreeView kann man sich ersparen (ich glaube sogar das ist praktisch nicht umsetzbar) und über die CallBack Funktion das Select-Verzeichnis setzen. Zusätzlich kann man sich auch das setzen des Root-Verzeichnisses über Folder-ID's sparen und Zuweisungen über Environment.SpecialFolder und PIDCList Ermittlung über die API-ILCreateFromPath Bestimmung ausführen. Die Angaben ordnet man dann einer BROWSERINFO-Struktur zu und kann diese an die SHBrowseForFolder Methode übergeben um den Verzeichnis-Dialog anzuzeigen. Ich habe auch noch eine Oberfläche zum Testen gebastelt ... ich denke ich werde das Projekt jetzt noch ein drittes mal überarbeiten (sehr straffen) und hoffe, dass ich am Wochenende dann hier etwas Veröffentlichen kann.
Vielen Dank für deine Ermittlungen!
PS:
Der Forms-NameSpace lässt sich in Core-Projekte einbinden, wenn man nur die Projekt-Datei ändert (<UseWindowsForms>true</UseWindowsForms>) und die Forms-Assembly nicht dem Projekt als COM-Assembly hinzufügt. Forms wird dann beim direkten Referenzieren (System.Windows.Forms...) automatisch geladen. Alles Anstrengungen (fast) umsonst 😦
Hallo Th69!
Die SendMessage Deklaration ist die ganz klassische:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
Meinst du jetzt die Build-Projekt-Einstellung? → Zielplattform: Any CPU .
Das wäre jetzt ja ganz schlecht (dynamisch und nur "manuell")!!! Ich habe zwischendurch auch schon den Eintrag selektieren lassen, bevor ich die Unter-Einträge ermitteln lasse:
IntPtr Item1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), RootItem); // 1. Eintrag = Untergeordneter Eintrag vom Root-Eintrag holen [Ebene 1]
System.Diagnostics.Debug.Print("1. Eintrag: " + GetTVItem(pTV, Item1)); // Bezeichnung des 1. Eintrages ermitteln
SendMessage(pTV, TVM_SELECTITEM, new IntPtr(TVGN_CARET), Item1); // 1. Eintrag selektieren
SendMessage(pTV, TVM_ENSUREVISIBLE, IntPtr.Zero, Item1); // 1. Eintrag in den sichtbaren Bereich bringen
IntPtr Item1_1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), Item1); // 1. Unter-Eintrag vom 1. Eintrag [Ebene 2] holen
System.Diagnostics.Debug.Print("1.1 Eintrag: " + GetTVItem(pTV, Item1_1)); // Bezeichnung des 1.1. Eintrages ermitteln
Aber hilft alles nichts:
1. Eintrag: Anruf_Info
1.1 Eintrag: <leer>
2. Eintrag: Anruf_Info REPORT22
Die SendMessage-Aufrufe funktionieren. Mit ihnen (unverändert) kann ich einen Eintrag selektieren.
Wie im Bild ersichtlich, wird der 1. Eintrag auch aufgeklappt dargestellt.
Genau den FolderBrowserDialog möchte ich ja ersetzen, da er im Forms-NamenSpace integriert ist (siehe Thread-Ausgangspunkt).
Mal sehen, ob ich das (mit meinen Vorstellungen) schaffe!
Danke! und ich hoffe, du hast beim Film abschalten können. 😉
Guten morgen Th69!
Die Konstanten habe ich der commctrl.h - Header-Datei entnommen und passen:
// Konstanten zur Navigation im TreeView
private const int TVGN_ROOT = 0; // Ruft das erste untergeordnete Element des Stammelements ab
private const int TVGN_NEXT = 1; // Ruft das nächste gleichgeordnete Element ab.
//private const int TVGN_PREVIOUS = 2; // Ruft das vorherige gleichgeordnete Element ab.
//private const int TVGN_PARENT = 3; // Ruft das übergeordnete Element des angegebenen Elements ab.
private const int TVGN_CHILD = 4; // Ruft das erste untergeordnete Element ab.
// private const int TVGN_FIRSTVISIBLE = 5; // Ruft das erste sichtbare Element ab.
// private const int TVGN_NEXTVISIBLE = 6; // Ruft das nächste sichtbare Element ab.
//private const int TVGN_PREVIOUSVISIBLE = 7; // Ruft das vorhergehende sichtbare Element ab.
//private const int TVGN_DROPHILITE = 8; // Ruft das Element ab, das das Ziel eines Drag-and-Drop-Vorgangs ist.
private const int TVGN_CARET = 9; // Ruft das aktuell ausgewählte Element ab.
Das Unter-Element vom Eintrag 1 wird auch nicht referenziert.
Damit ich keinen Dominoeffekt erhalte, habe ich jeder Referenz auf einen Eintrag einer separaten Variable zugeordnet.
Folgende Abruf-Reihenfolge:
IntPtr Item1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), RootItem); // 1. Eintrag = Untergeordneter Eintrag vom Root-Eintrag holen [Ebene 1]
System.Diagnostics.Debug.Print("1. Eintrag: " + GetTVItem(pTV, Item1)); // Bezeichnung des 1. Eintrages ermitteln
IntPtr Item1_1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), Item1); // 1. Unter-Eintrag vom 1. Eintrag [Ebene 2] holen
System.Diagnostics.Debug.Print("1.1 Eintrag: " + GetTVItem(pTV, Item1_1)); // Bezeichnung des 1.1. Eintrages ermitteln
IntPtr Item2 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_NEXT), Item1); // 2. Eintrag [Ebene 1] holen
System.Diagnostics.Debug.Print("2. Eintrag: " + GetTVItem(pTV, Item2)); // Bezeichnung des 2. Eintrages ermitteln
IntPtr Item3 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_NEXT), Item2); // 3. Eintrag [Ebene 1] holen
System.Diagnostics.Debug.Print("3. Eintrag: " + GetTVItem(pTV, Item3)); // Bezeichnung des 3. Eintrages ermitteln
IntPtr Item3_1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), Item3); // 1. Unter-Eintrag vom 3. Eintrag [Ebene 2] holen
System.Diagnostics.Debug.Print("3.1 Eintrag: " + GetTVItem(pTV, Item3_1)); // Bezeichnung des 3.1. Eintrages ermitteln
ergibt dann folgendes Ergebnis:
1. Eintrag: Anruf_Info
1.1 Eintrag: <leer>
2. Eintrag: Anruf_Info REPORT22
3. Eintrag: Druck
3.1 Eintrag: <leer>
Das Interessante ist doch, dass es bei dem 1. Eintrag, der ein Untereintrag von Root ist, (mit dem gleichen Aufruf!) funktioniert (siehe Probe Code)! Hier ist lParam intPtr.Zero. Ich würde interpretieren, der Bezug ist damit der Zeiger auf das TreeView selbst. Was ist am TreeView anders als der Bezug auf ein TreeView-Item? Auf der gleichen Ebene (TVGN_NEXT) wirkt ja der Bezug auf das vorhergehende TreeView-Item.
Danke für deine Überlegung(en)!
Hallo Abt und API32 "Fans" (falls es so etwas gibt?) 😉!
Weil es unmittelbar mit diesen Thread zusammenhängt, habe ich noch ein paar Fragen die die API32 Programmierung betreffen.
Es hat mich natürlich nicht in Ruhe gelassen es zu versuchen ... auf den API32 Browser-Dialog von C# aus zuzugreifen.
Nach einer gewissen Einarbeitungszeit stellen sich dann doch ganz brauchbare Ergebnisse ein.
An einer Stelle komme ich jedoch nicht weiter.
Zum einstellen des selektierten Verzeichnisses bei der (Erst) Initialisierung des Dialog-Browsers hole ich (über einen Timer) das Browser-Dialog Fenster und greife auf das TreeView des Dialog-Browsers zu. Funktionier sicher und die Windows-Meldungen werden auch korrekt verarbeitet.
Die Iteration im TreeView erschließt sich mir jedoch nicht.
.
RootItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_ROOT), IntPtr.Zero); // Root-Eintrag holen
TVM_GETNEXTITEM mit TVGN_ROOT holt zuverlässig den Root-Eintrag des TreeView's. [Ebene 0]
.
AktItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), RootItem); // 1. Eintrag = Untergeordneter Eintrag vom Root-Eintrag holen [Ebene 1]
TVM_GETNEXTITEM mit TVGN_CHILD holt den untergeordneten Item vom Root-Eintrag. ⇒ 1. Eintrag [Ebene 1]
.
AktItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_NEXT), AktItem); // 2. Eintrag holen [Ebene 1]
TVM_GETNEXTITEM mit TVGN_NEXT holt den nächsten Item (gleiche Ebene) vom 1. Eintrag. ⇒ 2. Eintrag [Ebene 1]
.
AktItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_NEXT), AktItem); // 3. Eintrag [Ebene 1] holen
TVM_GETNEXTITEM mit TVGN_NEXT holt den nächsten Item (gleiche Ebene) vom 2. Eintrag. ⇒ 3. Eintrag [Ebene 1]
.
Sobald ich aber "tiefer" iterieren will (Ebene 2), geht das nicht!
Anmerkung: Der 1. Eintrag wird ja eigentlich schon vom Root-Eintrag "abgeleitet" (benutzt RootItem vom Root-Eintrag!)
Bei weiteren Ableitungen gehen das jedoch nicht! Zum Beispiel wenn ich den 1. Unter-Eintrag vom 3. Eintrag = Ebene 2 "holen" möchte: (Müsste ich ja den gleichen Aufruf wie beim 1. Eintrag mit dem AktItem vom 3. Eintrag (lParam) benutzen!)
AktItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), AktItem); // 1. Unter-Eintrag vom 3. Eintrag [Ebene 2] holen
Damit wird aber IntPtr.Zero zurückgegeben.
Woran kann das liegen????
.
.
Der Probe-Code sieht folgendermaßen aus:
IntPtr RootItem = IntPtr.Zero; // Handle des Root-Eintrages initalisieren
IntPtr AktItem = IntPtr.Zero; // Handle des akt. TreeView-Items initalisieren
string AktVerzeichnis = RootVerzeichnis; // Aktuelles Verzeichnis initalisieren
RootItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_ROOT), IntPtr.Zero); // Root-Eintrag holen
AktItem = RootItem; // Zeiger auf den akt. Eintrag aktualisieren
if (!SelectVerzeichnis.Equals(AktVerzeichnis)) // Select-Verzeichnis ist nicht das Root-Verzeichnis?
{
if (AktItem != IntPtr.Zero) // Ist ein Root-Eintrag vorhanden?
{
// AktItem = FindItem(pTV, AktItem, SelectVerzeichnis, AktVerzeichnis); // TV rekursiv durchsuchen
AktItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), RootItem); // 1. Eintrag = Untergeordneter Eintrag vom Root-Eintrag holen [Ebene 1]
System.Diagnostics.Debug.Print("1. Eintrag: " + GetTVItem(pTV, AktItem)); // Bezeichnung des 1. Eintrages ermitteln
AktItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_NEXT), AktItem); // 2. Eintrag [Ebene 1] holen
System.Diagnostics.Debug.Print("2. Eintrag: " + GetTVItem(pTV, AktItem)); // Bezeichnung des 2. Eintrages ermitteln
AktItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_NEXT), AktItem); // 3. Eintrag [Ebene 1] holen
System.Diagnostics.Debug.Print("3. Eintrag: " + GetTVItem(pTV, AktItem)); // Bezeichnung des 3. Eintrages ermitteln
AktItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), AktItem); // 1. Unter-Eintrag vom 3. Eintrag [Ebene 2] holen
System.Diagnostics.Debug.Print("3.1 Eintrag: " + GetTVItem(pTV, AktItem)); // Bezeichnung des 3.1. Eintrages ermitteln
}
}
return AktItem;
Das Ergebnis ist im Anhang abgebildet.
Hallo Abt!
Alle Lösungen die ich auf GitHub gefunden habe gehen auf .NET Framework Implementationen zurück und beim Einbinden in das Projekt bekommt man dann die entsprechenden Warnungen. Kann man ausblenden, aber ich suche etwas, dass ich auch in Zukunft ohne Einschränkungen verwenden kann.
Alle sonstigen Lösungen im Netz (die ich gefunden habe) gehen auf den Forms-Namenspace zurück:
Danke für deinen Link zu Palladin007's Beitrag!
Es stellt sich zwar die Frage: Warum muss man das Rad jedes mal neu erfinden? Einen Verzeichnis-Dialog ist doch etwas "alltägliches", aber ich versuche es einmal. Bei deinem 2. Link auf den Win32 Dialog fehlen mir z.B. die Klassen-Namen und die Parameter
Anfangen würde es mit:
/// <summary>
/// Hilfs-Klasse zum Zugriff auf Windows API-Methoden
/// </summary>
public class WindowsAPI
{
[System.Runtime.InteropServices.DllImport("win32")]
public static extern int BrowseDialogBox(HDC hdc, string Title ...);
}
Da ist noch viel Luft nach oben ... 😉
Hallo Abt!
Danke! für die schnelle Antwort.
Es ist schon ein Stückchen her, wo ich mich damit beschäftigt hatte ... jetzt nervt es aber doch ...
Den Beitrag hatte ich auch gefunden. Wenn ich die Projekt-Einstellungen ändere:
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<ApplicationIcon>DVD-Verwaltung-2.ico</ApplicationIcon>
</PropertyGroup>
, erhalte ich jedoch (beim nächsten öffnen des Projektes) die Fehlermeldung:
Warnung MSB3290 Fehler beim Erstellen der Wrapperassembly für die Typbibliothek "{215d64d2-031c-33c7-96e3-61794cd1ee61}". Die Typbibliothek "System_Windows_Forms" kann nicht als eine CLR-Assembly importiert werden, da sie aus einer CLR-Assembly exportiert wurde.
deshalb hatte ich damals aufgegeben. 😦
PS:
Wieso steht der Assembly-Name überhaupt mit Unterstrichen, anstatt mit Punkt???
System.Windows.Forms
PS2:
Wir brauchen uns hier aber auch nicht "festbeißen" ... welchen Verzeichnis-Auswahl Dialog verwendet ihr denn unter .NET Core?
Hallo!
Ich möchte ein Verzeichnis-Auswahl Dialog in meinem WPF, .Net 5 Projekt implementieren.
Bisher habe ich den Path-Dialog von Windows.Form benutzt.
Unter .Net 5 (wahrscheinlich alle .NETCore) kann ich den Forms-Namespace jedoch nicht mehr einbinden / finden! (siehe Bild)
Woran liegt das bzw. welche Verzeichchnis-Auswahl-Dialog(e) benutzt ihr?
Vielen Dank Th69!
Hätte ich zwischendurch noch einmal schauen müssen!!!
Aber so ist es gründlich durch meinen Kopf und jedes Detail ist mir klar.
Mit den richtigen Key-Words findet man dann auch viele Lösungen!
Hallo Abt!
Vielleicht muss ich doch mal etwas weiter ausholen ...
Die serialisierte Klasse (XML-Datei) möchte ich ja auch wieder einlesen.
Dies realisiere ich über die Load-Methode der XmlDocument-Klasse.
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(fileName);
Diese erwartet aber eine Datei, die eine UTF-8 Kodierungs-Angabe hat!
(Ansonsten Exception: There is no Unicode byte order mark. Cannot switch to Unicode.)
Wie du schon gesagt hast, der XML-Standard ist UTF-8.
Das heißt wiederum, ohne eine Codierungs-Angabe in der Datei, wird von UTF-8 ausgegangen, was im
praktischem auch funktioniert. (Wenn ich die Codierungs-Angabe in der Datei entferne, wird das
xmlDocument.Load(fileName) korrekt ausgeführt!)
Deshalb der Ansatz: Beim Serialisieren die Codierungs-Angabe zu unterdrücken oder UTF-8 anzugeben.
Doch jetzt zur Umsetzung.
Der XmlSerializer hat keine Kodierung. Wirksam wird die Kodierung des StringWriter!
Serializer.Serialize(sw, InhaltsListe);
Das Encoding des StringWriter ist (ersteinmal) nicht änderbar. (Nur Getter)
(Ist normalerweise sinnvoll, da, wie du das schon erklärt hast,
der .NET-String intern in UTF-16 Kodiert wird.)
Aus obengenannten Grund musste ich dies aber überschreiben, damit das encoding-Attribut geändert wird. (Ändert sicher nichts daran, dass der String intern trotzdem in UTF-16 gespeichert wird! 😉 )
public sealed class EncodingStringWriter : StringWriter
{
public override Encoding Encoding { get; } // Encoding Eigenschaft überschreiben
public EncodingStringWriter(StringBuilder sb, Encoding encoding) // Konstruktor mit StringBuilder und Kodierung
{
Encoding = encoding; // Kodierung übernehmen
}
}
Wenn ich diesen geänderten StringWriter benutze:
Encoding KodierungXMLDatei = (Encoding)CboKodierung2XML.SelectedValue; // Gewählte Kodierung der XML-Datei
System.Xml.Serialization.XmlSerializer Serializer = new(typeof(List<DateiEintrag>),"MyNS");
StringBuilder sb = new();
EncodingStringWriter sw = new(sb, KodierungXMLDatei);
Serializer.Serialize(sw, InhaltsListe); //
try
{
using StreamWriter writer = new(tblZiel2.Text, false, KodierungXMLDatei);
writer.Write(sw.GetStringBuilder().ToString());
}
wird das encoding-Attribut wie gewünscht (für XML) gesetzt:
<?xml version="1.0" encoding="utf-8"?>
und die so erstellte XML-Datei kann über die Load-Methode der XmlDocument-Klasse problemlos eingelesen und damit deserialisiert werden.
Oder hattest du noch eine andere Lösung?
Wieder vielen Dank für deine Erklärung der grundlegenden Zusammenhänge!
Hallo!
Ich möchte eine Liste serialisieren.
Encoding KodierungXMLDatei = (Encoding)CboKodierung2XML.SelectedValue; // Gewählte Kodierung der XML-Datei
System.Xml.Serialization.XmlSerializer Serializer = new(typeof(List<DateiEintrag>),"MyNS",);
StringBuilder sb = new();
StringWriter sw = new(sb);
Serializer.Serialize(sw, InhaltsListe);
try
{
using StreamWriter writer = new(tblZiel2.Text, false, KodierungXMLDatei);
writer.Write(sw.GetStringBuilder().ToString());
}
Die Liste wird serialisiert, fast alles gut.
Aber ... !!!
Woher nimmt der Serializer das Encoding-Format???
<?xml version="1.0" encoding="utf-16"?>
...
Die XML-Datei selbst, ist nicht in UTF-16 kodiert.
Ich hätte gern eine XML-Datei ohne diese Angabe.
<?xml version="1.0"?>
Wie kann ich das Steuern???
Hallo Paladin007!
Ja, Zitieren wäre wohl besser gewesen! Weil wir gerade bei der Oberfläche sind, ich vermisse noch die alte Error-"Box" (analog der Code oder XAML Box).
Da ich die AutoComplete-TextBox allein in diesem Projekt schon sehr oft benötige wäre es sicher sinnvoll ein eigenes CustomControl zu erstellen. Das es auch ohne geht, habe ich aus deiner Antwort schon herausgelesen. Damit muss ich mich aber erst einmal beschäftigen. Zur Zeit drängelt die Umsetzung der Logik erst noch.
... die TextBox ist dann im Template definiert und Du kannst sie als TemplatePart abrufen
Wird mir sicher bald etwas sagen! 😃
Vielen Dank!
PS: Wie gebe ich hier an, von wem dieses Zitat ist?
Hallo Caveman!
Danke für die Lösung.
Wenn ich das richtig verstanden habe, muss pro Gruppe also noch ein Qualifizierer, wie oft der Bereich vorkommen darf, angegeben werden?
(<BereichsQualifizierer><Bereichs-Name><RegEx>)
Vielen Dank!
Hallo Palladin007!
Nein, ich möchte prüfen ob in einem Text die (festgelegten) Buchstaben in genau dieser Reihenfolge angeordnet sind, unabhängig davon, ob zwischen den Zeichen (im Text) noch weitere Zeichen stehen. Also unabhängig vom Alphabet sondern von einer Reihenfolge die ich vorgebe (welche auch gleiche Zeichen beinhalten kann).
Im Beispiel einfacher zu erklären.
Vorgabe-Reihenfolge: a-e-e
Treffer: Das ist ein Test!
Kein Treffer: Dies ist ein Test!
Danke für deinen Hinweis!
PS: Das Beispiel habe ich bei der Antwort von Caveman als Bild angefügt.