Hosten eines Windows Media Player-Controls in einer MVVM Application

  • WPF MVVM
  • .NET (FX) 4.5–4.8

Es gibt 35 Antworten in diesem Thema. Der letzte Beitrag () ist von kafffee.

    Hosten eines Windows Media Player-Controls in einer MVVM Application

    Einen wunderschönen Sonntag euch allen,

    ich möchte meiner MVVM-Applikation die Funktionalität eines CD-Brenners zufügen und habe zusammen mit @-Franky- schon vor einiger Zeit für ein anderes Mitglied ein kleines WinForms-Programn mit einem Windows Media Player-Controls entwickelt, das dies tut:

    Nun möchte ich dieses noch feinschleifen und natürlich in mein MVVM-Projekt integrieren... Zum hosten des Windows Media Player habe ich folgende Ansätze im Internet gefunden:

    docs.microsoft.com/de-de/dotne…w=netframeworkdesktop-4.8

    c-sharpcorner.com/uploadfile/d…-media-player-com-in-wpf/

    codeproject.com/Tips/497004/Us…dia-Player-Control-in-WPF

    Diese scheinen aber nur auf Applikationen ausgerichtet sein, die nicht(!) nach dem MVVM-Pattern erstellt wurden. Folglich stellt sich mir die Frage:

    Wie integriere ich das Windows Media Player-Control so, dass ich von meinem ViewModel aus drauf zugreifen kann? In den Anleitungen wird ja der Projektmappe ein Windows Forms-Steuerelementbibliothek-Projekt zugefügt, auf dieses dann das WMP Control gesetzt und dieses dann gehostet, aber das geht halt nur ohne MVVM... Ich meine, ich kann die DLLs ja dem Viewmodel als Verweis zufügen, aber wie stehts mit dem Control selber? In den Anleitungen füge ich der kompletten Projektmappe ja das Steuerelement-Bibliotheksprojekt zu, aber wenn ich das mache erhalte ich von Intellisense keine Vorschläge in meinem VM...

    Oder hat jemand vielleicht einen andere Alternative, wie man Audio- und/oder MP3-CDs programmatisch brennen kann?

    Freu mich auf eure Antworten, kafffee

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „kafffee“ ()

    Hi

    Wenn Du unabhängig vom WMP-Control CDs brennen möchtest, dann schau Dir mal die IMAPI2 Interfaces an. docs.microsoft.com/en-us/windows/win32/api/imapi2/ Hier im speziellen das Interface IDiscFormat2TrackAtOnce das für das brennen von AudioCDs verwendet wird. Ich kann Dir aber jetzt schon sagen das das nicht mit ein paar Zeilen Code zu erledigen ist. Bis man überhaupt dazu kommt ein Audio zu brennen, müssen noch diverse andere IMAPI2 Interfaces aufgerufen, initialisiert und einiges vorbereitet werden (Brenner ermitteln, CD-Rohling Format ermitteln, ist überhaupt eine CD eingelegt, usw usw). Insbesondere muss zB. die MP3, bevor diese gebrannt werden kann, erst einmal zu WAV (44,1kHz, 16Bit, 2CH ohne Header und muss auf eine bestimmte die Länge in Bytes gebracht werden) konvertiert werden. Dazu kann man die Media Foundation nutzen. Ich hab das mal vor sehr langer Zeit, da hatte ich auch noch CD-Rohlinge, mit einem TestProjekt in VB6 realisiert wo ich zumindest eine MP3 auf eine CD als AudioCD brennen konnte. Wie geschrieben, das ist nicht mal eben so fix programmiert.

    Darüber hinaus bietet Windows noch die IMAPI Interfaces an. docs.microsoft.com/de-de/windows/win32/api/imapi/ Die sind etwas einfacher und hier dürfte das Interface IRedbookDiscMaster das sein was Du suchst. Auch hier müssen vorher andere IMAPI-Interfaces initialisiert und vorbereitet werden bevor man überhaupt zum brennen kommt. Diese Interfaces hab ich bisher noch nicht genutzt bzw. nur ein wenig damit experimentiert. Bis zum brennen von irgendwelchen Daten bin ich aber noch nicht gekommen.

    Ich glaube in diesem Fall bist Du besser beraten auf ein fertiges Paket zurückzugreifen das unabhängig vom WMP CDs brennen kann. Das als One-Man-Show zu programmieren ist eine echte Herausforderung.
    Mfg -Franky-
    Moin moin @kafffee

    Ich habe mal mein Archiv durchgeschaut und ich hatte da schon mal was in Sachen IMAPI für .NET vor Monaten/Jahren was angefangen. Das ganze ist noch komplett am Anfangsstadium. Mehr als ein paar Eigenschaften des CD-Brenners und des eingelegten Mediums auslesen kann das noch nicht. Auch fehlen noch komplett die Event-Interfaces um entsprechende Rückmeldungen / den Status beim Brennen auszuwerten usw usw. Das ganze ist eher als Spielerei und Testcode anzusehen. Man könnte damit aber, evtl. nach ein paar Umbauarbeiten und Tests, schon AudioCDs damit brennen. Hinzu kommt halt noch das konvertieren zu RAW-WAV. Wenn es nur um das Brennen von AudioCDs geht, kann man ja nicht benötigte Interfaces rauswerfen.

    Ganz grob funktioniert das Brennen einer AudioCD folgendermaßen: Man erstellt ein IDiscMaster2- und ein IDiscRecorder2-Interface. Mit IDiscMaster2.get_Item und dem Index des Brenners, holst Du dir die UniqueId des Brenners oder ließt diese bereits schon vorher aus. Per IDiscRecorder2.InitializeDiscRecorder und der UniqueId initialisiertst Du den Brenner (weis der IDiscRecorder2 welcher Brenner verwendet werden soll). Dann erstellt man sich ein IDiscFormat2TrackAtOnce-Interface. Ab hier könnte man das EventInterface DDiscFormat2TrackAtOnceEvents.Connect <- mit IDiscFormat2TrackAtOnce initialisieren um diverse Satus zu ermitteln. Dem Interface IDiscFormat2TrackAtOnce.put_Recorder <- das Interface IDiscRecorder2 zuweisen. Dann per IDiscFormat2TrackAtOnce.put_ClientName ein Namen zuweisen. Dann per IDiscFormat2TrackAtOnce.PrepareMedia aufrufen. Evtl. per IDiscFormat2TrackAtOnce.put_DoNotFinalizeMedia sagen das am Ende das Medium finalisiert werden soll (also False). Evtl IDiscFormat2TrackAtOnce.put_BufferUnderrunFreeDisabled setzen (hab ich in meinem VB6-TestCode auf False). Dann kann es mit dem Brennen losgehen. Per IDiscFormat2TrackAtOnce.AddAudioTrack wird die RAW-WAV, die in einem IStream geladen wurde/befindet, gebrannt. Sind alle WAV gebrannt, per IDiscFormat2TrackAtOnce.ReleaseMedia das ganze abschließen, DDiscFormat2TrackAtOnceEvents.Disconnect falls das EventInterface verwendet wird, evtl. noch das Meduim auswerfen (IDiscRecorder2.EjectMedia) und erstellte Interfaces disposen/löschen.

    Natürlich gehört da noch mehr zu wie den freien Speicherplatz auf dem eingelegten Medium ermitteln, Gesamtgröße aller zu brennenden WAVs (müssen ja auf dem Medium draufpassen) und kann das Medium überhaupt für eine AudioCD genutzt werden. Alles zusammen betrachtet nicht ganz ohne. Das konvertieren von Audio-Dateien zu RAW-WAV ist dabei noch das allerkleinste Problem.
    Dateien
    • IMAPI.zip

      (58,04 kB, 65 mal heruntergeladen, zuletzt: )
    Mfg -Franky-

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „-Franky-“ ()

    Hey @-Franky-, erstmal danke für deine ausführlichen Antworten.

    Das hört sich in der Tat sehr kompliziert an. Ich habe mir mal Gedanken gemacht, wie ich das Problem lösen kann, und bin auf folgendes Ergebnis gekommen:

    (1) Ich erstelle mir eine Windows-Forms-Steuerelement-Bibliothek (DLL) und klatsche mir einen Windows Media Player auf das UI.
    (2) Ich erstelle eine Funktion, die mir die Laufwerke ermittelt, die CDs brennen können
    (3) Ich erstelle eine Funktion, die das Brennen startet.
    (4) Ich erstelle eine Property, die Fehlermeldungen und Statusmeldungen beinhaltet
    (5) Ich kompiliere das Ganze als Release
    (6) Ich nehme mein WPF-Projekt her, und füge dem ViewModel Verweise auf folgende Assemblies zu:
    -WindowsFormsIntegration
    -System.Windows.Forms
    (7) Ich füge dem ViewModel auch einen Verweis auf meine selbst erstellte DLL zu
    (8) Ich füge dem ViewModel jeweils auch einen Verweis auf die AxInterop.WMPLib.dll und die Interop.WMPLib.dll zu
    (9) Ich kontrolliere, ob die AxInterop.WMPLib.dll und die Interop.WMPLib.dll auch wirklich im Ausgabeordner meines ViewModels bzw. auch im App-Projekt sind. (Ich musste das bei mir manuell reinkopieren...)
    (10) In meinem ViewModel kann ich nun eine Instanz des selbst erstellten WinForms-Steuerelements erstellen:

    VB.NET-Quellcode

    1. Dim MeinBrenner As New UserControl1


    Einziges Problem: Im Steuerelementprojekt bekomme ich meine Klasse UserControl1 nicht umbenannt. Wenn ich den Dateinamen und die Klasse umbenenne, kommt in der Designansicht irgendein Fehler wie "möglicher Datenverlust", und dass der Fehler vorher behoben werden muss... Das ist halt blöd, wenn in meinem WPF-Projekt dann steht Dim MeinBrenner As New UserControl1... Edit: achja, natürlich, wahrscheinlich einfach den Name im Eigenschaften fenster des Designers abändern...

    Ich hab das Ganze natürlich schon getestet und habe es tatsächlich auch geschafft, von meinem WPF-MVVM-Projekt aus mit dem Windows Media Player-Control ein Lied abzuspielen, ich denke das mit dem Brennen sollte dann auch klappen...

    Wo ich eventuell noch Hilfe brauche, ist die Ermittlung des freien Speicherplatzes auf den Rohlingen und dem programmatischen Löschen einer Playlist (meine DLL erstellt eine (temporäre) WMP-Playlist, die gebrannt und danach wieder gelöscht werden soll).

    Aber das dann im entsprechenden Thread (ich poste das dann da zeitnah)...:

    Übersicht Windows Media Player

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „kafffee“ ()

    @kafffee

    kafffee schrieb:

    Wo ich eventuell noch Hilfe brauche, ist die Ermittlung des freien Speicherplatzes auf den Rohlingen

    Wie das mit dem WMP funktioniert, kann ich Dir nicht sagen. In meinem IMAPI2-Beispiel ist das auslesen des freien Speicherplatzes schon enthalten und ob der eingelegte Rohling für das Brennen einer AudioCD tauglich ist. Wenn das was Du vorhast mein Projekt wäre, würde ich schauen, ob man nicht komplett auf den WMP verzichten kann. Zum Abspielen und Brennen einer AudioCD gibt es ja alternativen.
    Mfg -Franky-

    -Franky- schrieb:

    In meinem IMAPI2-Beispiel ist das auslesen des freien Speicherplatzes schon enthalten und ob der eingelegte Rohling für das Brennen einer AudioCD tauglich ist


    Ah okay, da hatte ich dich falsch verstanden, hatte gedacht das hättest du noch nicht implementiert... Na dann schau ich mir das mal an...

    -Franky- schrieb:

    Zum Abspielen und Brennen einer AudioCD gibt es ja alternativen.


    Ja ich weiss, für alles andere (und das ist eine Menge) verwende ich die bass.dll von un4seen.com (Das coole ist auch, die haben ein eigenes Forum, ist zwar englischsprachig aber man bekommt schnell qualifizierte Antworten...)

    Nur beim Brennen finde ich den WMP jetzt nicht schlecht, v.a. auch weil ich mal wohlwollend davon ausgehe, dass der WMP die MP3s on-the-fly ins Audio-CD-Format konvertiert und nicht erst eine *.wav auf Festplatte erstellt. Für User ohne SSD von Vorteil... Ausserdem gibt es ja auch noch die Möglichkeit, das Ganze im MP3-Format auf Datendisc zu schreiben, find ich jetzt auch nicht zu verkehrt... Ob das ganze dann auch mit DVDs funktioniert muss ich erst mal noch testen. Auf jeden Fall wirft der WMP leider keinen Fehler, wenn die Daten nicht auf die CD passen und lässt es auch nicht komplett bleiben. Ergebnbis ist dann eine CDs mit ein paar Tracks der Grösse 1kB...

    kafffee schrieb:

    weil ich mal wohlwollend davon ausgehe, dass der WMP die MP3s on-the-fly ins Audio-CD-Format konvertiert

    Das sollte der WMP schon machen. Wobei Du per MediaFoundation ebenfalls eine MP3 in den Speicher (IStream oder ByteArray, setzt aber mindestens 64bit-Windows/Programm voraus damit man genügend Speicher adressieren kann) schreiben und somit ein zwischenspeichern auf die Platte entfallen kann. Natürlich kannst Du auch per IMAPI2 ganz normale Daten-CDs/DVDs erstellen. Da kommen dann aber weitere IMAPI2FS-Interfaces dazu. Siehe hier: docs.microsoft.com/en-us/windows/win32/api/imapi2fs/

    Ja wenn Du noch VB6 hättest, könnte ich Dir meine damaligen VB6-TestProjekte zur Verfügung stellen mit denen man ISOs von CDs und Ordner von der Festplatte erstellen und diese ISOs wieder Brennen / Mounten /Unmounten kann. Wie ein Ordner und Dateien als DatenCD gebrannt werden kann und natürlich auch wie man eine MP3 als AudioCD brennt. Aber wer hat schon noch VB6 (außer meine Wenigkeit). :D
    Mfg -Franky-

    -Franky- schrieb:

    Ja wenn Du noch VB6 hättest


    Ne leider net...

    Ich hab mich mal an dein Beispiel und die Beschreibung aus Post 3 gehalten und versucht, das für mich relevante zu extrahieren. Leider kommt es in meiner DiscFormat2TrackAtOnce.vb zu folgendem Fehler in Zeile 10:

    VB.NET-Quellcode

    1. ​Public Class DiscFormat2TrackAtOnce
    2. Implements IDisposable
    3. Public This As IDiscFormat2TrackAtOnce
    4. Private Const MsftDiscFormat2TrackAtOnce As String = "27354129-7f64-5b0f-8f00-5d77afbe261e"
    5. Private Const S_OK As Integer = 0
    6. Sub New()
    7. This = DirectCast(Activator.CreateInstance(Type.GetTypeFromCLSID(New Guid(MsftDiscFormat2TrackAtOnce))), IDiscFormat2TrackAtOnce)
    8. End Sub


    Das COM-Objekt des Typs "System.__ComObject" kann nicht in den Schnittstellentyp "IDiscFormat2TrackAtOnce" umgewandelt werden. Dieser Vorgang konnte nicht durchgeführt werden, da der QueryInterface-Aufruf an die COM-Komponente für die Schnittstelle mit der IID "{6732C3CC-E898-34CB-91C3-4DB24C95F128}" aufgrund des folgenden Fehlers nicht durchgeführt werden konnte: Schnittstelle nicht unterstützt (Ausnahme von HRESULT: 0x80004002 (E_NOINTERFACE)).

    Kannst du mir sagen was da nicht stimmt? Sag mir bitte wenn du noch mehr Codezeilen brauchst... Ich verstehe diese Codezeile nicht...

    Was ich auch nicht verstehe ist, wie kommst du auf:
    ​Private Const MsftDiscFormat2TrackAtOnce As String = "27354129-7f64-5b0f-8f00-5d77afbe261e"

    Woher stammt diese Nummer?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „kafffee“ ()

    Hi

    Hmm, das muss so funktionieren. Auf meinem Win10 System, und da hab ich 2 von, läuft das. Diese {6732C3CC-E898-34CB-91C3-4DB24C95F128} ist komisch. Die kenn ich nicht, Google nicht und magnumdb.com auch nicht. Funktioniert mein Bsp auch nicht?

    kafffee schrieb:

    Was ich auch nicht verstehe ist, wie kommst du auf:
    Private Const MsftDiscFormat2TrackAtOnce As String = "27354129-7f64-5b0f-8f00-5d77afbe261e"
    Schaust Du hier: docs.microsoft.com/en-us/windo…2-idiscformat2trackatonce und dann auf magnumdb.com/search?q=MsftDiscFormat2TrackAtOnce oder Du schaust direkt in die C++ Headerdatei imapi2.h.
    Mfg -Franky-
    @-Franky-

    Doch dein Beispielprojekt funktioniert tadellos... Ich habe allerdings Windows 10 Professional.... Muss ich da evtl. noch nen Verweis zufügen den ich übersehen hab? Oder verwende ich das falsche Zielframework?
    Oder benutzt eine andere Applikation vllt irgendwelche Ressourcen?

    Das ermitteln der CD-Drives, die dann in der Listbox erscheinen funktioniert auch ohne Fehler in meinem Programm...

    Jetzt bin ich so weit gekommen das wär ärgerlich wenn ich wegen sowas jetzt aufgeben müsste...

    Oder liegts vielleicht an dieser Zeile?:
    If IDiscFormat2TrackAtOnce.PutClientName("MyApplication") Then

    Edit:
    Hab den Fehler gefunden, ich hatte was übersehen beim Einfügen.

    Aber jetzt kommt hier ein neuer Fehler:

    System.AccessViolationException
    HResult=0x80004003
    Nachricht = Es wurde versucht, im geschützten Speicher zu lesen oder zu schreiben. Dies ist häufig ein Hinweis darauf, dass anderer Speicher beschädigt ist.
    Quelle = <Die Ausnahmequelle kann nicht ausgewertet werden.>

    Hier in Zeile 3:

    VB.NET-Quellcode

    1. ​Public Function PutRecorder(objRecorder As DiscRecorder2) As Boolean
    2. Dim bolRet As Boolean
    3. If This.put_Recorder(objRecorder.This) = S_OK Then
    4. bolRet = True
    5. End If
    6. Return bolRet
    7. End Function


    Kann es sein, weil ich vergessen hab was zu disposen?

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „kafffee“ ()

    Ich gebe zu, ich habe mir hier nicht alles durchgelesen. Ich bin gerade auch ein wenig ausgelastet, um mich der Problematik komplett zu widmen. Aber ich habe ein paar Fragen:

    Warum sollte man etwas, das nicht MVVM unterstützend ist, nicht in MVVM konvertieren können?
    Wo genau steht, dass es nur in nicht MVVM-Designs geht?
    Ist MVVM immer die beste Lösung? Oder gibt es gute Gründe es nicht einzusetzen?
    Gibt es Gründe, MVVM - also striktes MVVM - aufzuweichen?

    zur ersten Frage: Als du angefangen hast, MVVM umzusetzen und zu lernen (so ging es mir jedenfalls), hast du da eventuell gemerkt, dass du häufig versucht warst (oder es auch getan hast), die Programmierung wieder nach altem Schema umzusetzen? So a la: Na ja, nehme ich einfach diesen Event-Handler und baue das da dran und reiche das durch. Oder so ähnlich?
    Wenn dem so ist, dann sollte dir hier auffallen, dass genau das, was du geschrieben hast, getan wird. Die Applikation, die du schreibst, portierst du von einer "normalen" Schreibweise in ein entsprechendes MVVM-Pattern. So hast du dein Großprojekt ja auch begonnen, wenn ich mich recht erinnere. Du bist immer wieder auf Hürden gestoßen, hast dich weiter damit beschäftigt und dann auch Ansätze gefunden, die eine Lösung bieten und dennoch MVVM erhalten hatten - oder irre ich mich?

    Zu zweitens: Ja, das Projekt ist in "klassischer Programmierungsmanier" geschrieben. Das heißt nicht, dass es nicht anders geht. Das heißt erstmal nur, dass jemand das gebaut hat und zwar nach dieser Struktur und diesem Muster. Der Aufwand, etwas richtig neu zu strukturieren und dann noch 100% MVVM zu bilden kann unter Umständen auch derart groß ausfallen, dass es vielleicht niemanden gab, der sich die Mühe hätte machen wollen, das nochmal umzuschreiben. Du hast halt das Beispiel so gefunden. Vielleicht schaffst du es ja, das ins MVVM zu übersetzten. Ich bin mir sicher, dass du schon einiges gelernt haben dürftest, um so etwas umzusetzen.
    Hier möchte ich nochmal gerne etwas in Erinnerung rufen: Früher hast du bei jedem Knopf immer einen Handler erstellt, der sich der Sache angenommen hat. Wie genau machst du es denn, wenn du einen Button hast, dessen Funktion nun ausschließlich im ViewModel behandelt werden soll? Du nutzt ja nicht mehr "Click-Event", sondern eher irgendwas mit Commands oder so. Vor allem aber, hast du nun einen anderen Kontext, in dem dein Button funktionieren soll. Denk da auch nochmal drüber nach.

    MVVM hat einen entscheidenen Vorteil: man hat strikte Trennung. MVVM hat aber auch Nachteile. Einer dieser Nachteile ist auch die steigende Komplexität eines Programms. Wenn ich ein Programm schreiben würde, das nur ein Datum einliest und einen Text ausgibt, würde das sicher niemand in MVVM schreiben, weil der Nutzen den Aufwand niemals überwiegen würde. Was aber auch nicht schlimm ist. MVVM ist eine wirklich gute Art, etwas zu programmieren. Aber es ist nicht die aller heiligste Art und schon gar nicht, die einzig richtige Art. Manchmal muss man vielleicht sogar davon abweichen (können). Ich versuche MVVM überall einzusetzen, da ich es nun lieben gelernt habe. Aber manchmal kommt man sich damit - so finde ich - selbst in die Quere.

    Das wiederum bringt mich auch direkt dazu, dass ich denke, dass man manchmal Dinge auch aufweichen können sollte. Du solltest - auch für dich - versuchen, MVVM konsequent durchzusetzen. Aber manchmal gibt es da eben Schwierigkeiten. Eine entscheidende Möglichkeit ist: Du könntest von deinem Steuerelement - wenn dir keine bessere Lösung einfällt - die Event-Handler an dein ViewModel weiterreichen (zB über die MainWindow) und das eben einfach auslagern. Dies verstößt durchaus gegen striktes MVVM, bietet aber einen einfachen, direkten Weg, Kommunikation zwischen Teilen zu ermöglichen - so wie man es in klassischer Manier getan hat. Ob es sinnvoll ist, muss jeder für sich selbst entscheiden.
    Anstatt aber zB alles aufzuweichen, könnte man auch einen weiteren Weg gehen: Ein UserControl bauen, dort das WMP-Plugin einfügen. In dessen Codebehind klassisch coden und das UserControl dann entsprechend einbinden und an dein ViewModel binden oder zumindest bekanntmachen. Auch eine Überlegung wert.
    Ich fürchte nur, du wirst ein Lösung suchen müssen, solange niemand für genau dieses Problem bereits eine gebaut hat. Denn keine Lösung ist die einzig wahre Lösung und alle Wege führen nach Rom. Dass mal ein Weg kürzer oder nicht so steinige ist, das ist dann wohl so. Aber am Ende kommen sie alle an :)
    @kafffee
    Heute und morgen komme ich nicht dazu mir das anzuschauen. Zusätzliche Verweise brauchst Du normalerweise nicht da wir hier direkt entsprechende Interfaces verwenden. PutRecorder verwende ich glaub auch in meinem Bsp um danach einige Eigenschaften vom Brenner oder CD auszulesen. Da ich grad unterwegs bin, kann ich in meinen Code nicht reinschauen.
    Mfg -Franky-

    -Franky- schrieb:

    Heute und morgen komme ich nicht dazu mir das anzuschauen.


    Kein Problem, ich mach glaube ich für heut auch Feierabend und morgen komm ich wahrscheinlich auch nicht zum Programmieren. Anbei mal der Code wie ich ihn hab vorab, von dem ich glaube, dass da der Fehler drin ist. Ich bin Zeile um Zeile durchgegangen und hab alles vermeintlich nicht relevante rausgeschmissen. Kann gut sein dass ich es da an irgendeiner Stelle zu gut gemeint hab... Ich denke dass der Fehler irgendwo da sitzt. Aber lass dir Zeit wie gesagt :)

    VB.NET-Quellcode

    1. ​Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
    2. Dim sbInfo As New StringBuilder
    3. Dim strUniqueID As String = IDiscMaster2.GetItem(ListBox1.SelectedIndex)
    4. If strUniqueID <> String.Empty Then
    5. Using IDiscRecorder2 As New DiscRecorder2
    6. If IDiscRecorder2.InitializeDiscRecorder(strUniqueID) Then
    7. Using IDiscFormat2TrackAtOnce As New DiscFormat2TrackAtOnce
    8. If IDiscFormat2TrackAtOnce.PutRecorder(IDiscRecorder2) Then
    9. If IDiscFormat2TrackAtOnce.PutClientName("MyApplication") Then
    10. If IDiscFormat2TrackAtOnce.PrepareMedia Then
    11. Dim intRet As Integer = IDiscFormat2TrackAtOnce.GetFreeSectorsOnMedia
    12. Label1.Text = "Freie Sektoren: " & intRet.ToString
    13. End If
    14. End If
    15. End If
    16. End Using
    17. End If
    18. End Using
    19. End If
    20. End Sub


    PadreSperanza schrieb:

    Ich gebe zu, ich habe mir hier nicht alles durchgelesen. Ich bin gerade auch ein wenig ausgelastet, um mich der Problematik komplett zu widmen.


    Kein Problem, bin eigentlich fast schon durch.

    PadreSperanza schrieb:

    Ein UserControl bauen, dort das WMP-Plugin einfügen. In dessen Codebehind klassisch coden und das UserControl dann entsprechend einbinden und an dein ViewModel binden oder zumindest bekanntmachen.


    Genau diese Idee hatte ich nämlich auch (s. Post #4). Nur das Durchreichen von Events, das macht mir noch Kopfschmerzen. Ich hätte da ein Event gefeuert, jedes Mal wenn sich der Brennstatus und/oder -fortschritt sich ändert, damit das mein ViewModel das dann in der View über Binding anzeigen lässt... Aber das geht glaube ich nicht, wenn man ein Winforms-Control in einer MVVM-App hosten will... Ich wüsste jedenfalls nicht wie. Also hab ich mir gedacht, ich benutze einfach viewmodelseitig einen Timer, der dann jede Sekunde auf Änderungen überprüft.
    Ich gebe zu, von selbst erstellten Events hab ich momentan noch keine Ahnung...

    Noch eine Frage: Hast du mit diesem Satz...:

    PadreSperanza schrieb:

    wenn dir keine bessere Lösung einfällt - die Event-Handler an dein ViewModel weiterreichen (zB über die MainWindow) und das eben einfach auslagern.


    ...gemeint, dass man entweder Eventhandler weiterreicht oder selbst ein UserControl baut? Oder geht beides in einem?
    es geht beides in einem. Du kannst einem UserControl ja auch eigene Events anfügen und diese dann steuern und feuern. Du kannst mittels TriggerBehaviour und Events sogar weitergeleitete Aktionen definieren. Das habe ich selber noch nie gemacht, wird aber häufig in Foren erwähnt zB hierstackoverflow.com/questions/48…-a-command-binding-in-wpf
    Wichtig ist, dass du erkennst, dass du bei einem UserControl wieder eine eigene Kapselung hast, die du wieder überwinden musst. Entweder durch TriggerBehaviour oder durch Durchreichen deines Controls.


    Je nach Anforderung kann man auch anderes machen:

    Baue eine statische Klasse, die als Property die entsprechenden Felder enthält. Jedes Mal, wenn dein WMP nun etwas fertig hat, änderst du den Status-Wert der statischen Klasse - welche INotifyPropertyChanged enthält und andere Klassen können dann damit auch was anfangen. -> Achtung: Hierbei meine ich, dass deine Statische Klasse eine Klasse enthält (Stichwort Singleton), die wiederum Properties bereithält, die mittels INotifyPropertyChanged angesteuert und gebunden werden können.

    Mittels statischem Binding ...="{Binding Source={x:Static model:statischeKlasse.Klasse.Property}} kannst du so auf diese Felder in deinem MVVM zugreifen. Es bleibt jedoch, dass du in der WMP-Klasse diese Felder dann mittels "klassischer" Programmierung befüllst und befeuerst. Sofern das WMP-Control in Steuer-Host "Command" zulässt, kannst du es sogar noch einfacher, direkt mit dem DataContext händeln.. aber dafür kenne ich diese Klasse nicht, um da weiter was zu sagen

    Zum Durchreichen:

    Dein MainWindow enthält ja zum Beispiel ein UserControl, welches wiederum ein ViewModel hat. Das UserControl ist eine Resource und hat einen x:Key. könnte im MainWindow sowas im XAML stehen:

    XML-Quellcode

    1. ...
    2. <controls:ExtendedTabItem>
    3. <view:ViewCatalog x:Name="CatalogViewer" />
    4. </controls:ExtendedTabItem>
    5. ..


    Dann ist dieses UC im MainWindow.xaml.cs so definiert:

    C#-Quellcode

    1. public MainWindow()
    2. {
    3. InitializeComponent();
    4. InitializeAppData();
    5. Closing += CatalogViewer.OnClosing;


    Dein UserControl enthält zum Beispiel nun ein ViewModel als Resource:

    XML-Quellcode

    1. <UserControl.Resources>
    2. <viewmodel:CatalogViewModel x:Key="CatalogViewModel"/>
    3. </UserControl.Resource>


    im UserControl.xaml.cs hast du so etwas stehen:

    C#-Quellcode

    1. internal void OnClosing(object sender, CancelEventArgs e)
    2. {
    3. CatalogViewModel viewmodel = FindResource("CatalogViewModel") as CatalogViewModel;
    4. if (viewmodel != null)
    5. {
    6. viewmodel.SaveCatalogItems();
    7. }
    8. }



    Nun kannst du vom MainWindow aus zum ViewModel herunter reichen.

    Umgekehrt geht es auch, indem du die Übergeordneten bekannt machst:

    C#-Quellcode

    1. Public ViewCatalog(){
    2. ...CatalogViewModel viewModel = FindResource("CatalogViewModel") as CatalogViewModel;
    3. viewModel.parent = this;}


    Der Konstruktor deines UC sucht das ViewModel, das als eigene Resource ja bereits definiert sein muss und weißt diesem sich selbst als Parent zu

    Und im ViewModel selbst hast du seinen eigenen Parent definiert:

    C#-Quellcode

    1. public ViewCatalog parent;
    Mit entsprechenden Schutzstufen, Gettern und Settern.

    Nun kannst du von unten nach oben "blubbern" mit

    C#-Quellcode

    1. parent.DoActionUpwards


    und im UC wiederum kannst du mit der Methode DoActionUpwards etwas anstellen, oder diese wiederum nach oben blubbern, bis du sie beim MainWindow in andere wieder herunter blubbern kannst. So kannst du Daten von einem ViewModel in ein anderes reichen (ähnliches hatte ich dir mal geschrieben mit einem ViewModelContainer, der alle anderen ViewModel kennt).
    Was hierbei wichtig ist: Damit hast du kein striktes MVVM mehr.

    Ist striktes MVVM immer das beste? Nun da streiten sich die Geister. Viele sind sich einig, dass es manchmal besser ist, das etwas auzuweichen, um so an eine geeignete Lösung zu kommen. Andere sehen das genau anders herum. Fazit: Deine Entscheidung :)

    So jedenfalls könnte man es machen

    Ich hab nur gerade leider keine Zeit, das noch schnell in VB zu wechseln, aber ich hoffe, du kannst das bissl C# lesen
    So, das mit dem Feierabend legen wir mal beiseite, kommt eh nur Müll in der Glotze, also Musik und Computer wieder an :P :P

    PadreSperanza schrieb:

    es geht beides in einem. Du kannst einem UserControl ja auch eigene Events anfügen und diese dann steuern und feuern.


    OK klar Events anfügen lassen sich bei einem WinForms-UCL. Aber du redest von einem WPF-UCL oder? Weil die Herausforderung aus meiner Sicht bestand/besteht darin, dass sich das WMP Control nicht so einfach als Verweis zufügen lässt bzw. in WPF hosten lässt sei es nun ein UCL oder eine MVVM App, denn es hat sowas wie eine Sonderstellung, weil es nicht zu den Standard Winforms-Controls gehört und irgendwie erst über die Com-verweis separat zugefügt werden muss, habs jedenfalls auch schon ausprobiert...
    Also hab ich mir wie in meinem Post #4 ein eigenes WinForms-UCL geschrieben, auf das ich dann den WMP zugefügt hab und das ich dann in meiner WPF-App hoste, also ein kleiner Trick...

    Und das Problem: Klar ich kann ja die Events eines WinForms-UCL dann in einer WinForms-App bedienen. Wäre zumindest logisch. Wenn man es aber wie ich gemacht hat, muss ich ja die Events von meinem WinForms-UCL in einer WPF-App mit MVVM bedienen. Nur dass wir nicht aneinander vorbei reden...

    PadreSperanza schrieb:

    Baue eine statische Klasse, die als Property die entsprechenden Felder enthält.


    Was für Felder meinst du damit? Die Statusinformationen als String in einem Array?

    PadreSperanza schrieb:

    So kannst du Daten von einem ViewModel in ein anderes reichen (ähnliches hatte ich dir mal geschrieben mit einem ViewModelContainer, der alle anderen ViewModel kennt).
    Was hierbei wichtig ist: Damit hast du kein striktes MVVM mehr.


    Jou, das mit dem LayerViewModel. Hab ich auch in meinem Projekt verwendet und hat super funktioniert... Bis ich dann an die Grenzen gestossen bin, weil eine Methode, die im ViewModel eines dynamisch erzeugten modalen Dialog sitzt, eine Variable verwenden möchte, die in einem anderen ("festen") ViewModel ist, das aber im LayerViewModel noch nicht instanziiert worden ist... Langer Satz, ich weiss, aber ich glaube verständlich. Jedenfalls warte ich seitdem gespannt darauf, dass @Nofear23m das nächste Kapitel (6.4) seines WPF-Tutorials raushaut, worauf er ausführlich darauf eingehen will, wie man mit so einer Problematik umgehen kann...

    PadreSperanza schrieb:

    Ich hab nur gerade leider keine Zeit, das noch schnell in VB zu wechseln, aber ich hoffe, du kannst das bissl C# lesen


    Alles gut, das bissel versteh ich in der Tat gerade noch so... :)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „kafffee“ ()

    kafffee schrieb:

    Was für Felder meinst du damit? Die Statusinformationen als String in einem Array?
    Ich meine in der Tat so vieles :D

    Ich habe zum Beispiel bei mir eine statische Klasse, die heißt ​AppSettings. Diese statische Klasse besitzt eine einfache Klasse als statisches Feld - ​WindowSettings (implementiert als Singleton). WindowSettings hat folgendes Aussehen:

    C#-Quellcode

    1. ​public class windowSettings : ModelBase //ModelBase beherrscht INotifyPropertyChanged
    2. {
    3. private float _width;
    4. public floath Width
    5. {
    6. get => _width;
    7. set => SetProperty(ref _width, value);
    8. }
    9. ...
    10. }


    Mein ​MainWindow hat eine Höhe, eine Breite, einen Abstand nach links und von oben. Also ​Width, Height, Top, Left. Diese binde ich nun auf diese statische Klasse:


    XML-Quellcode

    1. <MainWindow.Width>
    2. <Binding Source={x:static AppSettings} Path="WindowSettings.Width" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" />
    3. </MainWindow.Width>
    4. ...


    Das ist alles nichts spektakuläres. Das ist auch relativ verständlich.

    Nun habe ich aber einen Dialog gebaut, der zum Beispiel in einem UserControl erstellt werden soll. Dieser Dialog soll sich aber im Center des Programms ausrichten. Da dessen DataContext aber das ViewModel des UC ist, weiß der Dialog nicht, wer das eigentliche MainWindow ist. Also kann ich das auch nicht als Parent angeben. Was ich nun aber erreicht habe und machen kann: Ich kann mich auf die gebundenen Werte in der statischen Klasse beziehen:

    C#-Quellcode

    1. SaveDialog dialog = new SaveDialog();
    2. dialog.Top = AppSettings.WindowSettings.Top;
    3. dialog.Width = AppSettings.WindowSettings.Width;
    4. ...


    So weiß der Dialog zu keinem Zeitpunkt, was oder wer oder wo das MainWindow ist. Aber an die relevanten Daten kommt es Dank der Bindung an eine statische Klasse. Ergibt ja auch durchaus Sinn. Man verteufelt statische Klassen immer, weil diese generell überall im Programm aufrufbar sind und keine Kapselung haben... Ja stimmt. Aber genau da, wo ich diese Kapselung aufheben möchte oder sogar muss, sind genau diese statischen Klassen sinnvoll und auch dafür gemacht. Also angenommen, du hast etwas, das du über zwei ViewModel teilen möchtest oder musst. Zum Beispiel eine List(of Music), um in VB zu bleiben. Das eine UC verwendet die Liste, um die Einträge zu editieren, die andere, um diese Musikstücke abzuspielen. Viele bauen nun jeweils eine List(Of Music) in jedem ViewModel und versuchen nun sie von einer Seite auf die andere zu schleifen. Wenn sie sich die Daten aber teilen, dann kann man auch eine List(Of Music) in einer statischen Klasse bereitstellen, auf die ich beide ViewModel dann binde. Hat Vorteile: Ich brauche nicht zwei Listen mit ggf. selben Inhalt. Und wenn mein Dialog zum Hinzufügen etwas hinzufügt, bekommt die andere Seite das ebenfalls mit, da dieselbe Liste auch hier eine Notification wirft.

    Den Gedanken nun bei dir weitergedacht:

    Du könntest zB in einer statischen Klasse sowas haben wie:

    C#-Quellcode

    1. public enum BrennerStatus
    2. {
    3. BrennenGestartet,
    4. BrennenLaeuft,
    5. BrennenBeendet
    6. }
    7. private BrennerStatus _status;
    8. public BrennerStatus Status
    9. {
    10. get => _status;
    11. set => SetProperty(ref _status, value);
    12. }


    in deinem MVVM bindest du (notfalls mittels Converter) auf diesen Status, in der WMP-File änderst du einfach den Status über CodeBehind. Das INotifyPropertyChanged-Event wird dann gefeurt und alle gebundenen Dinge bekommen sofort mit, dass hier etwas getan werden muss.

    solltest du nun auch noch Daten vom WMP-File brauchen, kannst du diese (wenn sie abstrakt genug eingebunden worden sind) ebenfalls in dieser statischen Klasse einbinden:

    C#-Quellcode

    1. ​private MusikStueck _stueck;
    2. public MusikStueck Stueck
    3. {
    4. get => _stueck;
    5. set => SetProperty(ref _stueck, value);
    6. }


    Du kannst also, statt nur strings zu speichern auch ganze, abstrakte Logiken und Dateien speichern und diese über deine Anwendung teilen. es ist auch nicht verboten und auch keine "BadPractice" wie alle immer sagen. BadPractice wäre, wenn du generell statische Klassen einbaust, ohne dass sie sinnvoll oder notwendig sind. Aber genau für solch ein Szenario bieten sie sich an
    @kafffee
    Auf die schnelle und weil ich gleich weiter muss. Ich vermute das durch die fehlende Zeile bei Dir ein zweiter Aufruf blockiert da das Medium ja nicht freigegeben wurde (ungetestet) das bei Dir zum besagten Fehler führt.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub CbxCdDrives_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CbxCdDrives.SelectedIndexChanged
    2. Dim sbInfo As New StringBuilder
    3. Dim strUniqueID As String = IDiscMaster2.GetItem(CbxCdDrives.SelectedIndex)
    4. If strUniqueID <> String.Empty Then
    5. Using IDiscRecorder2 As New DiscRecorder2
    6. If IDiscRecorder2.InitializeDiscRecorder(strUniqueID) Then
    7. Using IDiscFormat2TrackAtOnce As New DiscFormat2TrackAtOnce
    8. If IDiscFormat2TrackAtOnce.PutRecorder(IDiscRecorder2) Then
    9. If IDiscFormat2TrackAtOnce.PutClientName("MyApplication") Then
    10. If IDiscFormat2TrackAtOnce.PrepareMedia Then
    11. Dim intRet As Integer = IDiscFormat2TrackAtOnce.GetTotalSectorsOnMedia
    12. IDiscFormat2TrackAtOnce.ReleaseMedia() '<- diese Zeile fehlt bei Dir
    13. End If
    14. End If
    15. End If
    16. End Using
    17. End If
    18. End Using
    19. End If
    20. End Sub


    Wenn mein TestCode fehlerfrei durchläuft und Dir entsprechende Infos zum Brenner und zum eingelegten Medium ausgibt, dann hast Du mit Sicherheit irgendwo was vergessen zu kopieren.

    Wenn es dann bei Dir soweit läuft, könnte man sich überlegen, anstatt jedes Interface komplett in eine eigene Klasse zu packen, nur die benötigten Interface-Funktionen durch Delegates zu ersetzen und nur noch mit den Pointern der Interfaces sowie mit den Funktionsnummern aus der VTable des Interfaces zu arbeiten. Dann reicht eine einzige Klasse aus die dann entsprechendes enthält. Das macht aber nur Sinn wenn man von einem Interface nur ein zwei oder drei Funktionen benötigt.
    Mfg -Franky-
    @PadreSperanza

    Okay hab mir mal die Zeit genommen das alles zu verstehen. Eine statische Klasse ist in VB ja ein Modul. Und auf ein Modul, das in meinem Namespace ViewModel sitzt, kann ich nun ohne Weiteres von jedem meiner ViewModels im Namespace ViewModel zugreifen. Ein Modul wird nicht instanziiert und hat die gleiche Lebensdauer wie meine Applikation. Somit muss ich mir also keine Sorgen machen, ob meine ObservableCollection(Of Music) schon in seinem ViewModel instanziiert worden ist, sondern ich instanziiere sie einfach in meinem Modul und kann dann jederzeit und von überall aus meinem Namespace ViewModel zugreifen.

    Sehe ich das alles richtig? Das klingt ja echt genial, und ich verstehe nicht, warum dann Module so verteufelt werden, ich habe nämlich echt den gleichen Eindruck bekommen, als ich gerade ein bisschen recherchiert hab.

    Für mein Hauptprojekt kann ich das sehr gut verwenden, da ich z.B. eine Playlist hab, die von einem ViewModel erstellt wird, von einem anderen dann abgespielt und bearbeitet wird.

    Bevor ich mich jetzt aber da reinstürze, warte ich wahrscheinlich noch auf das neue Kapitel von @Nofear23m, vielleicht har er ja da noch Einwände... Ich kann mir aber vom jetzigen Stand her keine weiteren Probleme vorstellen, die damit einhergehen könnten...

    Ein Manko bleibt jedoch: Deine Rangehensweise ist für ein WPF UCL ja supergenial und das funktioniert dann auch mit dem Binding an den Brennstatus...

    Ich habe es jedoch bis dato nicht hinbekommen, das Windows Media Player Control in meinem ViewModel im WPF-Projekt zu instanziieren. Die MS Docs sagen ja folgendes:

    (1) Hinzufügen der Assemblies WindowsFormsIntegration und System.Windows.Forms als Verweis
    (2) Instanziieren Dim mtbDate As New MaskedTextBox("00/00/0000")

    Wenn ich dann aber MaskedTextBox durch AxWindowsMediaplayer ersetzen möchte, meckert mir mein IntelliSense und nimmt es nicht an...

    Wie importiere ich also den das Windows Media Player Control in mein ViewModel und von mir aus auch in besagtes Modul so dass es angenommen wird?

    Also war die einzige Lösung die mir bis jetzt eingefallen ist, einfach selbst ein WinForms(!)-UCL zu bauen auf das ich dann das WMP Control geklatscht hab und die benötigten Funktionen dann abstrahiert/gespiegelt, oder wie man es auch nennen mag, es dann zu einer DLL kompiliert und in mein WPF-Projekt als Verweis zugefügt hab, dann klappts auch mit der Instanziierung...

    Aber auf Eigenschaften dieses selbst erstellte WinForms-UCLs kann ich dann wahrscheinlich nicht binden, und müsste somit mit einem Timer dann jede Sekunde oder so den Brennstatus abfragen. So jedenfalls ist meine Denke...

    Naja vielleicht fällt dir oder einem interessierten Mitleser ja was ein, was ich noch nicht weiss...

    So far, kafffee

    @-Franky-
    Ne das wars leider nicht... Der Fehler bleibt.... Wenn du willst kann ich mein Testprojekt ja mal hochladen, dann kannst du es testen... Vielleicht liegts ja an meinem PC... aber das komische ist dass dein Projekt, das ich mir runtergeladen hab, funktioniert...
    Ich könnte mir vorstellen, dass vielleicht irgend eine Abhängigkeit im Code fehlt, die der Designer aber nicht erkennt und rot anstreicht...


    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „kafffee“ ()

    Ich bin mit der Vermischung von WinForms und WPF noch nicht so vertraut. Ich nutze das meist nur im CodeBehind im Viewmodel (zB den ColorPicker aus WinForms). Das zu benutzen ist halt relativ einfach, da im Codebehind gearbeitet wird und ein ColorPickerDialog eben ein eigenständiges Form ist, was angezeigt wird. Es ist losgelöst von der WPF-Ebene (zumindest das GUI).

    Ich habe gerade spaßeshalber einfach mal gegooglet nach "WPF use Winform User Control". Gleich im ersten Treffer steht Code, der vielversprechend aussieht. Ich habe keine Ahnung, ob der dort beschriebene Code wirklich funktioniert, aber wenn ich mir das anschaue, sieht es zumindest logisch aus. Statt also dein Host-Control im CodeBehind zu erzeugen, erstellen sie es einfach direkt im XAML und fügen danach das WinForm-Control ein... also arbeiten sie ganz bequem nach "WPF-Standard". Sieht zumindest gut und erfolgsversprechend aus:

    stackoverflow.com/questions/10…-reference-it-in-the-xaml

    Wenn du eine eigene DLL daraus gebaut hast. Dann kannst du auch deine eigene Schnittstelle bauen, um das mit der Statischen Klasse zu realisieren. Denn du kannst doch in deiner DLL ein Event einbauen, das vom WMP gefeuert wird. Und deinem ViewModel kannst du doch sagen, dass es auf dieses Event lauschen soll - respektive dem Singleton. Dann sollte das doch eigentlich klappen? Im Zweifel kannst du ja sonst auch INotifyPropertyChanged in dieser DLL implementieren und dann eine öffentliche Schnittstelle nach außen freigeben, der du lauschen kannst - auf die du also bindest.

    Bzgl. Statischen Klassen:

    Soweit ich weiß sind statische Klassen entstanden, als man noch Spaghetti-Code gebastelt hat und Funktionen bereits eine Kapslung vorgenommen haben. Der Sinn ist, dass in einer statischen Methode oder einer statischen Property etwas steht, das an mehreren Stellen zur Verfügung steht und auch genutzt werden kann. In ganz früherem Spaghetti-Code brauchte man so etwas nicht, da das Programm nur aus einer einzigen Funktion (der Main Methode) bestand und man sich mittels GoTo und anderer Dinge fortbewegt hatte. Da waren alle Variablen automatisch lokale Variablen und man hatte zu jeder Zeit Zugriff auf alles. Man brauchte sich also gar keine Gedanken machen.

    Dann kam das Kapseln der Bereiche und Ebenen und man brauchte nun also Möglichkeiten, diese Werte irgendwie zu bekommen. Natürlich kann man mittels Funktionen, die wiederum Callbacks ausführten, diese Werte immer wieder kommunizieren. Aber das ist auch nur ein wahlloses Durcheinander. Abhilfe schafften dann die statischen Klassen, Funktionen und Properties. Dadurch, dass sie sofort mit dem Programm entstehen (in den neueren Hochsprachen trifft das nicht ganz zu... da werden statische Dinge erst zum Leben erweckt, wenn das erste Mal darauf zugegriffen wird und nicht mehr sofort bei Programmstart - der Effekt ist aber gleich), halten sie im Grunde seit Beginn die Daten und stellen sie jederzeit zur Verfügung. Wenn man nun in einer Funktion oder als die OOP kam, in Klassen entsprechendes brauchte, konnte man ohne Probleme darauf zugreifen. Praktisch, nützlich und vollkommen legitim.

    Den Ruf als schlechter Programmierstil - so habe ich es zumindest verstanden und immer wieder gelesen - kam eher dadurch auf, dass Programmierer die Einfachheit der Strukturierung und Funktionalität der statischen Felder erkannt haben. Statt also lieber ein Element oder eine Klasse zu kapseln - wie es heute üblich ist - wurde dann begonnen einfach alles in statischen Klassen auszulagern (was im Grunde auch möglich ist -> Wir erhalten wieder Spaghetti-Code vom Feinsten). Die Übersichtlichkeit, der Schutz der Klasse/Funktion durch Kapslung waren dadurch verloren und - was noch viel schlimmer war - man machte sich selbst alles kaputt, denn:
    Wenn doch alle Variablen jederzeit zur Verfügung stehen... warum sie dann nicht auch einfach nutzen? Die Variable A einfach hier ändern, dann dort, dann noch mal in diesem Abschnitt... Wenn aber alles, jederzeit auf eine Variable zugreifen und sie dann auch noch verändern darf, verliert man schnell den Überblick oder vergisst, dass da noch etwas mitmischt... und dann wundert man sich, warum die Variable eigtl nicht das hält, was sie halten sollte. Sie erlangten einfach einen schlechten Ruf. Aber einen sinnvollen Einsatz können sie dennoch genießen - Sofern man sie mit Bedacht einsetzt und nur da, wo es sinnvoll ist.

    Ja, es stimmt: Statische Klassen haben vor allem den Vorteil, dass man sie nicht instanzieren muss. eine MessageBox kann also auch so heraufbeschworen werden, ohne erst eine Variable deklarieren zu müssen und sie zu füttern. Andererseits stehen diese Klassen auch genau aus diesem Grund dort, wo sie stehen. Eine Textbox ist immer exakt gleich aufgebaut. Das einzige, was sich ändert sind vielleicht Text, Caption oder Button-Art... Dann ist es doch naheliegend, die gleichen Ressourcen auch auszulagern und wieder zu verwenden. Deshalb ist die MessageBox statisch, kann aber individuell befüttert werden, um sie an die Bedürfnisse anzupassen.
    Das gleiche machst du hier mit deiner List(Of Music) dann auch. Das Grundgerüst (die Liste) wäre damit für alle darauf zugreifenden Elemente gleich. Was die Elemente damit machen, ist dann deren Sache. Nur eines solltest du bedenken: Sollten Elemente auf dieser List(Of Music) Operationen durchführen (also ändernde), solltest du dir gut überlegen, ob diese Änderungen für alle Elemente gleichermaßen gelten sollen oder nicht. Wenn nicht, solltest du keinesfalls die Liste selbst nutzen, sondern dir eine abgekapselte Kopie derselben aushändigen. Sonst suchst du dich genauso wund, wie all die Programmierer, die statische Klassen noch und nöcher verwendet haben ;)

    zu deiner anfänglichen Frage: Nicht ganz korrekt. Ja, Module müssen nicht instanziert werden. Aber die Klasse, die dein Modul enthalten muss (Singleton), schon - denn auch die List(Of Music) muss hier instaniziert und befüllt werden können.

    C#-Quellcode

    1. //Dies ist die Definition der eigenen Klasse AppGlobal
    2. public class AppGlobal : ModelBase
    3. {
    4. //Hier gibt es eine statische Klasse / ein VB-Mdoul, das genau eine Variable des Typs AppGlobal aufnehmen kann. Diese ist für alles im Programm gleich und sichtbar.
    5. public static AppGlobal Global
    6. {
    7. get; private set;
    8. }
    9. //Konstruktor für die Klasse AppGlobal
    10. public AppGlobal()
    11. {
    12. }
    13. //statische Ausführung, die beim Instanzieren der statischen Klasse ausgeführt werden müssen. Schreibt sich in C# so
    14. static AppGlobal()
    15. {
    16. //Hier wird dem Singleton ein neues AppGlobal zugewiesen, oder eben ein bestehendes geladen
    17. Global = new AppGlobal();
    18. if (!File.Exists(ApplicationRelated.GlobalPath()))
    19. {
    20. string json = Newtonsoft.Json.JsonConvert.SerializeObject(new AppVariables(), Newtonsoft.Json.Formatting.Indented);
    21. File.WriteAllText(ApplicationRelated.GlobalPath(), json);
    22. //Hier bekommt die Property 'MusicList' (siehe weiter unten) einen Wert zugewiesen, den ich aus JSON lade als Beispiel
    23. Global.MusicList = Newtonsoft.Json.JsonConvert.DeserializeObject<ListOfMusic>(json);
    24. }
    25. else
    26. {
    27. //Sollte keine geladen werden können, wird einfach eine neue erstellt und zugewiesen
    28. Global.MusicList = new ListOfMusic();
    29. }
    30. }
    31. }
    32. //eine statische Methode, um dem Singleton ein neues Musikstück hinzuzufügen. Diese Funktion kann von überall aufgerufen und ein Music-Stück übergeben werden
    33. public static void AddMusic(Music music)
    34. {
    35. //Intern greift diese Funktion dann auf die statische Klasse Global zu,
    36. //dort dann auf die Property MusicList (welches eine ListOfMusic) ist,
    37. //und ruft hierbei die Methode Add zum Hinzufügen auf.
    38. Global.MusicList.Add(music);
    39. }
    40. //Die Property. Da das nicht ganz C#-Konform ist, sondern ich an VB für dich bleiben wollte, sei hier noch gesagt, dass entweder ListOfMusic abgeleitet ist von List<Music>, oder man das eins zu eins austauschen müsste für C#
    41. private ListOfMusic _musicList;
    42. public ListOfMusic MusicList
    43. {
    44. get => _musicList;
    45. set => SetProperty(ref __musicList, value);
    46. }
    47. }


    Und dieses Singleton kann nun weitere Methoden und Felder bekommen und stellt sie in der gesamten Applikation zur Verfügung... aber es ist dennoch gekapselt, weil niemand etwas mit den Properties machen kann, was nicht im Singleton definiert ist.
    Singleton bedeutet hierbei: Es gibt ganz genau ein einziges Element von AppGlobal (kommt von Single = einzig), welches wiederum genau nur einmal diese ganzen Methoden/Properties zur Verfügung stellt. Damit ist sichergestellt, dass niemals zwei gleiche, globale Instanzen existieren. Rein von der Richtigkeit her müsste man das Singleton noch etwas anders definieren. Denn beim Singleton (hier AppGlobal) prüft man beim statischen Get-Zweig, ob ​Global == null. Wenn das so ist, wird Global instanziert und dann zurückgegeben. Wenn es ungleich null ist, ist es bereits instanziert, dann wird es sofort zurückgegeben. So kann sichergestellt werden, dass auch bei Global immer nur genau dieses eine Element drin ist, das sich alle teilen.

    Hoffe, das ist etwas verständlich erklärt