AttachedProperty verstehen

  • WPF

Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von PadreSperanza.

    AttachedProperty verstehen

    Hallo Leute,

    ich habe mich nun etwas mit AttachedProperties herumgeärgert. Und nach Recherchen im Internet habe ich nun auch eine Möglichkeit gefunden, das für einen Fall einzusetzen.

    Ich habe hier nun aber tatsächlich ein paar Verständnisprobleme dabei. Ich möchte aus dem ViewModel heraus das Window (geöffnet mittels ShowDialog()) schließen. Nach vielem Lesen habe ich herausgefunden, dass es zwei Möglichkeiten gibt, das im MVVM zu realisieren.
    1. Man bricht etwas mit dem MVVM und gibt beim Load-Event des Windows einen Ereignishandler an das ViewModel weiter.
    2. Man erstellt eine AttachedProperty.
    Letzteres habe ich nun mit zur Hilfenahme des Internets gebaut.

    Nachdem ich etwas rumgetüftelt habe, funktioniert es nun soweit auch. Hier der Code:


    C#-Quellcode

    1. public static class DialogCloser
    2. {
    3. //Registriert die DialogResult-Property als Attached-Property
    4. public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached(
    5. "DialogResult",
    6. typeof(bool?),
    7. typeof(DialogCloser),
    8. new PropertyMetadata(DialogResultChanged));
    9. //Wenn sich der Wert ändert, wird bei dem angegebenen Objekt die DialogResult-Eigenschaft gesetzt
    10. private static void DialogResultChanged(DependencyObject dObject, DependencyPropertyChangedEventArgs e)
    11. {
    12. var window = dObject as Window;
    13. if (window != null)
    14. {
    15. window.DialogResult = e.NewValue as bool?;
    16. }
    17. }
    18. //Setter
    19. public static void SetDialogResult(Window target, bool? value)
    20. {
    21. target.SetValue(DialogResultProperty, value);
    22. }
    23. //Getter
    24. public static bool? GetDialogResult(DependencyObject dObject)
    25. {
    26. return (bool?)dObject.GetValue(DialogResultProperty);
    27. }
    28. }


    Mein XAML-Code:

    XML-Quellcode

    1. <Window x:Class="WoolMaster3000.Dialog.AddInstructionDialog"
    2. ...
    3. xmlns:local="clr-namespace:WoolMaster3000.Dialog"
    4. xmlns:att="clr-namespace:WoolMaster3000.Attached"
    5. xmlns:viewmodel="clr-namespace:WoolMaster3000.ViewModel.DialogViewModel"
    6. mc:Ignorable="d"
    7. att:DialogCloser.DialogResult="{Binding DialogResult}"
    8. Title="AddInstructionDialog" Height="450" Width="800" WindowStyle="None">
    9. <Window.DataContext>
    10. <viewmodel:AddInstructionViewModel />
    11. </Window.DataContext>
    12. ...


    Wie gesagt, das funktioniert soweit. Wenn ich das nun richtig verstanden habe, dann ist bei att:DialogCloser.DialogResult="{Binding DialogResult}" DialogResult das DialogResult, das ich im ViewModel habe und es wird an an die AttachedProperty vom DialogCloser gebunden. Da dieses im Window-Element gesetzt ist, wird bei DialogResultChanged bei eben jenem Window die DialogResult-Eigenschaft auf den Wert gesetzt, der bei DialogResult im ViewModel gesetzt wurde. Habe ich das soweit richtig verstanden?


    Warum funktioniert dieser Code wunderbar, aber derjenige, den ich vorher hatte nicht (hat mich 2 Stunden gekostet, herauszufinden, dass es an der Art und Weise lag):

    Alter Code

    XML-Quellcode

    1. <Window x:Class="WoolMaster3000.Dialog.AddInstructionDialog"
    2. ...
    3. xmlns:att="clr-namespace:WoolMaster3000.Attached"
    4. xmlns:viewmodel="clr-namespace:WoolMaster3000.ViewModel.DialogViewModel"
    5. mc:Ignorable="d"
    6. att:DialogCloser.DialogResult="{Binding Source={StaticResource ViewModel}, Path=DialogResult}"
    7. Title="AddInstructionDialog" Height="450" Width="800" WindowStyle="None">
    8. <Window.Resources>
    9. <viewmodel:AddInstructionViewModel x:Key="ViewModel"/>
    10. </Window.Resources>
    11. <DockPanel DataContext="ViewModel">


    Was genau ist der Auslöser dafür, dass ich hier den DataContext als DataContext setzen muss und nicht über die Ressource gehen kann?
    Wie müsste ich es schreiben, damit ich über die Ressource gehen könnte? Das wäre ja interessant, wenn ich zum Beispiel mehrere DataContexts hätte und ich von allen Zugriff darauf haben möchte.

    Gruß Pascal

    PadreSperanza schrieb:

    Ich möchte aus dem ViewModel heraus das Window (geöffnet mittels ShowDialog()) schließen.
    Jo, klassisch hätte das Window dann eine bool-Property closed, und macht zu, wenn die per Databinding vom View auf true gesetzt wird.
    Das wäre aber eine "normale" Dependency-Property - keine Attached.
    ja, das ist richtig, aber ich möchte den Dialog ja abfragen und auch den DialogResult. Dieser ist aber keine DependencyProperty. Deshalb habe ich nun den Weg über AttachedProperty gewählt. Da ein einfaches ​Close zwar die Form schließen, aber keine Rückschlüsse auf den Rest liefern würde
    jo - ich versteh nicht recht, was du wolle.
    Ein Window schliessen, und DialogResult abfragen. Wpf-Windows haben aber gar kein DialogResult, wie mans aus Winforms kennt.
    Aber guggemol docs.microsoft.com/en-us/dotne…g?view=windowsdesktop-6.0
    das ist wpf.ShowDialog - returnt ein Bool?
    Ja das ist aber doch der Rückgabewert von ShowDialog().
    Wenn du also dein Window mit ShowDialog() geöffnet hast, bekommst du da, wo du es aufgerufen hast, das Result auch zurück.

    Allerdings weiss ich garnet, wie du den Dialog überhaupt per Code schliessen willst.
    So ein modaler Dialog blockiert den Thread - also Code wird da garnet ausgeführt - allenfalls nebenläufig, was wieder andere Probleme aufwirft.
    Davon hast du aber bislang nix erwähnt.
    Ich versteh immer weniger, was dir eine DepProp oder Attached-DepProp weiterhelfen können sollen würde.
    Ich öffne aus einem Window ein weiteres Window mittels ShowDialog. In diesem zweiten Windo habe ich eine ViewModel hinterlegt, um darauf binden zu können und im Dialog Daten anzuzeigen. Dieses enthält außerdem einen Button für Übernehmen und einen für Abbrechen. Wenn man nun auf einen der beiden Buttons drückt, soll sich der Dialog schließen. Da ich aber alles im ViewModel habe, habe ich ja theoretisch keine Möglichkeit aus diesem DataContext auf mein Dialog-Window zuzugreifen, um es in einem Command zu schließen. Folglich muss ich das über die AttachProperty machen, um im ViewModel auf etwas zu binden, was schlussendlich mein Dialog-Window schließen kann.

    Und deshalb war meine Frage erstens: Habe ich die Funktionsweise der AP richtig verstanden
    2. Wie könnte man es sonst lösen (wie gesagt, mein Weg funktioniert)
    3. Warum muss ich hier den DataContext in ​Window.DataContext setzen und kann hier nicht mit Resourcen arbeiten ?

    PadreSperanza schrieb:

    Ich öffne aus einem Window ein weiteres Window mittels ShowDialog. In diesem zweiten Windo habe ich eine ViewModel hinterlegt...
    Hä?
    In einem Window ein ViewModel hinterlegen?
    In meim Verständnis ist das paradox. Was in einem Window gemacht wird ist View. In einem View kann kein Viewmodel sein - per definitionem nicht.

    Aber tatsächlich ein schwieriges Problem: Wie bindet ein modales UnterForm ans Viewmodel?
    Meine Lösung sieht so aus, dass bei mir ein Window1 niemals ein Window2 öffnet.
    Sondern bei mir öffnet das Viewmodel ein Window2. Das kann ja ausgelöst sein durch ein Command, ausgehend vom Window1.
    Aber da kriege ich Schimpfe von @Nofear23m, wenn ich das sage.

    Eine andere Lösung bestünde ebenfalls in etwas verbotenem, was @Nofear23m schrecklich findet, nämlich: Singleton. (Du hast den Thread doch mitbekommen, wo er mich bat, dieses niemals vorzuschlagen, oder?)
    Dann kann Window1 Window2 auch direkt öffnen, weil Window2 ist gebunden an sein Viewmodel-Singleton - und der ist im Viewmodel ja präsent.

    Beides zugegebenermassen nicht schön.
    Aber ich bin guten Mutes, dass @Nofear23m die "richtige" Lösung wird präsentieren können. Mag sein die wird noch weniger schön, aber eben "richtig".
    Aus hohlem Bauch vermute ich mal, es wird auf einen Dependency-Injection-Container hinauslaufen - gschwind ein Framework installieren, Haufen RegisterDies(), RegisterDas() - Methoden schreiben, und DirectCast(GetDies(), WasicheiglichBrauche), DirectCast(GetDas(), WasicheiglichBrauche), und schon ists "richtig".
    Letztendlich ein verschleierter Universal-Singleton, aber da von Nuget ist's bestimmt was dolles.
    Sicherlich kann ein DI-Container auch noch bisserl mehr als olle outdated Singleton, aber was das ist (vielleicht Testing) kann ich nicht mit Sicherheit sagen, weil ichs noch nie gebraucht habe.
    (Und wenn ichs brauchen würde, würde ich erst noch untersuchen, ob (zB. Testing) mit Singleton nicht eben doch auch ebensogut oder besser geht)

    Ja, zB wenn mehrere Window1 unmodal offen sind, und jedes würde ein modales Unterform öffnen - da käme ich dann mit Singleton ins Schwitzen.
    Wie gesagt: noch nie gebraucht...

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

    Hallo

    Ja, ich hab dafür eine Lösung, und die ist auch zum Teil in meinem ProjektTemplate enthalten. Und nein, kein DependencyInjektion, aber ähnlich, nur ohne dem ganzen Overhead und ihne irgendwelchem Paket von NuGet.

    Aber ich bin Momentag auf Achse und in ganz Österreich unterwegs und kann frühestens morgen abend richtig Antworten, bis dahin nur am Handy.

    Grüße
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hallo,
    im Grunde benötigst Du Interfaces und einen Service.
    Bei mir sieht das ganze so aus:
    Als erstes habe ich ein Interface, welches von einem Dialog Fenster implementiert wird und Properties und Methoden bereit stellt.
    Spoiler anzeigen

    C#-Quellcode

    1. public interface IDialogWindow
    2. {
    3. bool? DialogResult { get; set; }
    4. object DataContext { get; set; }
    5. bool? ShowDialog();
    6. }


    Dann gibt es ein generisches Interface für ein DialogViewModel:
    Spoiler anzeigen

    C#-Quellcode

    1. public interface IDialogViewModel<T>
    2. {
    3. string Title { get; set; }
    4. string Message { get; set; }
    5. T DialogResult { get; set; }
    6. void CloseDialog(IDialogWindow dialog, T result);
    7. }



    Dazu habe ich eine Basis Klasse für DialogViewModels:
    Spoiler anzeigen

    C#-Quellcode

    1. public class DialogViewModelBase<T> : ViewModelBase, IDialogViewModel<T>
    2. {
    3. public string Title { get; set; }
    4. public string Message { get; set; }
    5. public T DialogResult { get; set; }
    6. public DialogViewModelBase() : this(string.Empty, string.Empty)
    7. { }
    8. public DialogViewModelBase(string title) : this(title, string.Empty)
    9. { }
    10. public DialogViewModelBase(string title, string message)
    11. {
    12. Title = title;
    13. Message = message;
    14. }
    15. public void CloseDialog(IDialogWindow dialog, T result)
    16. {
    17. DialogResult = result;
    18. if (dialog != null)
    19. dialog.DialogResult = true;
    20. }
    21. }
    22. public enum DialogResultEnum
    23. {
    24. Undefined,
    25. OK,
    26. Yes,
    27. No,
    28. Cancel
    29. }


    Dann entsprechend ein MessageBoxViewModel:
    Spoiler anzeigen

    C#-Quellcode

    1. public class MessageBoxViewModel : DialogViewModelBase<DialogResultEnum>
    2. {
    3. public MessageBoxViewModel(string title, string message) : base(title, message)
    4. {
    5. OkCommand = new DelegateCommand<IDialogWindow>(y => CloseDialog(y, DialogResultEnum.Yes));
    6. CancelCommand = new DelegateCommand<IDialogWindow>(n => CloseDialog(n, DialogResultEnum.No));
    7. }
    8. public ICommand OkCommand { get; }
    9. public ICommand CancelCommand { get; }
    10. }


    Im View Projekt gibt es dann den DialogService:
    Spoiler anzeigen

    C#-Quellcode

    1. public class DialogService : IDialogService
    2. {
    3. public T OpenDialog<T>(IDialogViewModel<T> viewModel)
    4. {
    5. IDialogWindow dialog = new DialogWindow();
    6. dialog.DataContext = viewModel;
    7. dialog.ShowDialog();
    8. return viewModel.DialogResult;
    9. }
    10. }


    Und das entsprechende Window dazu welches dann IDialogWindow implementiert:
    Spoiler anzeigen

    C#-Quellcode

    1. public partial class DialogWindow : Window, IDialogWindow
    2. {
    3. public DialogWindow()
    4. {
    5. InitializeComponent();
    6. }
    7. }



    Jetzt kann ich im ViewModel einen Dialog aufrufen und bekomme den Result mit dem ich weiter arbeiten kann:
    Spoiler anzeigen

    C#-Quellcode

    1. private void DeletePerson()
    2. {
    3. var dialog = new MessageBoxViewModel("ACHTUNG: Person löschen", "Wirklich löschen ?");
    4. var result = _dialogService.OpenDialog(dialog);
    5. if (result == DialogResultEnum.Yes)
    6. {
    7. People.Remove(SelectedPerson);
    8. SelectedPerson = People.FirstOrDefault();
    9. }
    10. }


    Den IDialogService hole ich mir im Konstruktor und weise in einer private readonly Variablen zu.
    Das ganze mit Dependency Injection oder, was auch geht, in der App.xaml.cs in der OnStartup Methode:
    Spoiler anzeigen

    C#-Quellcode

    1. public partial class App : Application
    2. {
    3. protected override void OnStartup(StartupEventArgs e)
    4. {
    5. base.OnStartup(e);
    6. var win = new MainWindow()
    7. {
    8. DataContext = new MainViewModel(new InMemoryDataProvider(), new DialogService())
    9. };
    10. win.Show();
    11. }
    12. }




    Der Vorteil ist, das ich als DialogResult im Grunde alles zurück geben kann.
    Ich könnte auch eine Person zurück geben, oder ein PersonViewModel oder oder...

    Ich nutze immer DependencyInjection und da immer das Microsoft.Extension.Hosting und Microsoft.Extension.DependencyInjection.
    Macht die Sache etwas einfacher finde ich.
    "Hier könnte Ihre Werbung stehen..."
    Hallo

    So, nun habe ich mal kurz Zeit hier meinen Ansatz zu zeigen. Anders als @MichaHo verwende ich kein DI da ich gerne MVVM rein mit "Boardmitteln" anwende und somit weder Overhead noch irgendwelche Probleme habe hier selbst "einzugreifen".
    DI ist aber dennoch eine Wunderbare sache und ideal für MVVM geeignet.

    So nun zu meinem Ansatz, welcher ähnlich aber dennoch denke ich etwas leichter zu verstehen ist.

    Zum einen erstmal eine kleine Erklärung warum und weshalb.

    Das MVVM Pattern schreibt vor das die Layer getrennt sind und das ViewModel nicht von der View weis. OK, das bedeutet aber das wir etwas wie Controls (dazu zählt auch eine Messagebox) abstrahieren müssen. Wir benötigen also ein Interface. Ich gehe bei den folgenden Beispielen davon aus das eine Projektmappe "korrekt" in mehrere Projekte unterteilt ist. Model - View - ViewModel und die App. In diesem Beispiel behandle ich mal einen Dialog.

    Als erstes benötigen wir ein Interface (einen Bauplan) welches Beschreibt was nun die funktionalität sein soll. Dafür erstelle ich mir einen Ordner "Services" im ViewModel-Projekt und erstelle die Datei "IWindowDialog":

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Namespace Services
    2. Public Interface IDialogWindowService
    3. Function ShowModalDialog(windowname As String, datacontext As Object, owner As Object, Optional topMost As Boolean = False, Optional showInTaskbar As Boolean = True) As Boolean
    4. Sub CloseDialog()
    5. Sub CloseDialog(vm As Object)
    6. End Interface
    7. End Namespace


    Das Interface Beschreibt nun die Funktionalitäten. Ein Dialog soll sich öffnen können und ich möchte diesen auch per Code schliessen können falls notwendig. OK
    In der zweiten Close-Methode kann ich als Parameter ein ViewModel übergeben. Hier ist wichtig zu wissen das wir (Details später) diesen Service nur 1x Registrieren und somit mit Close immer das zuletzt geöffnete Dialogfenster geschlossen werden würde. Damit wir hier aber mehr flexibilität reinbekommen habe ich mir etwas einfallen lassen auf das ich später eingehe, wichtig ist nur zu wissen das wir hier dann ein ViewModel übergeben und eine Routine sucht später nach dem Fenster welches das übergeben ViewModel als DatenKontext hat um dann genau dieses zu schliessen.

    Also erstmal ein Window erstellen welches unseren Dialog später representiert. Ich habe es bei mir SpsWindows getauft und befindet sich im View-Projekt.

    Der XAML ist ganz simple und beinhaltet im Grunde nur einen ContentPresenter:

    Spoiler anzeigen

    XML-Quellcode

    1. <ContentPresenter Content="{Binding}"/>


    Der Code in der CodeBehind bedarf allerdings etwas erklährung:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class SpsWindow
    2. Public MContext As Object
    3. Public Property CanCloseWithEsc As Boolean
    4. Get
    5. Return CBool(GetValue(CanCloseWithEscProperty))
    6. End Get
    7. Set
    8. SetValue(CanCloseWithEscProperty, Value)
    9. End Set
    10. End Property
    11. Public Shared ReadOnly CanCloseWithEscProperty As DependencyProperty =
    12. DependencyProperty.Register("CanCloseWithEsc",
    13. GetType(Boolean), GetType(SpsWindow),
    14. New PropertyMetadata(True))
    15. Public Property AsSpsModalDialog As Boolean
    16. Get
    17. Return CBool(GetValue(AsSpsModalDialogProperty))
    18. End Get
    19. Set
    20. SetValue(AsSpsModalDialogProperty, Value)
    21. End Set
    22. End Property
    23. Public Shared ReadOnly AsSpsModalDialogProperty As DependencyProperty =
    24. DependencyProperty.Register("AsSpsModalDialog",
    25. GetType(Boolean), GetType(SpsWindow),
    26. New PropertyMetadata(True))
    27. Public Sub New(name As String)
    28. Me.Name = name
    29. InitializeComponent()
    30. End Sub
    31. Public Sub New(name As String, datacontext As Object, Optional sizeToContent As SizeToContent = SizeToContent.WidthAndHeight,
    32. Optional startPosition As WindowStartupLocation = WindowStartupLocation.CenterScreen)
    33. Me.Name = name
    34. MContext = datacontext
    35. Me.SizeToContent = sizeToContent
    36. WindowStartupLocation = startPosition
    37. InitializeComponent()
    38. End Sub
    39. Private Sub SPSWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    40. AddHandler PreviewKeyDown, AddressOf Window_KeyDown
    41. If AsSpsModalDialog Then
    42. If Owner IsNot Nothing Then
    43. ShowInTaskbar = False
    44. End If
    45. End If
    46. DataContext = MContext
    47. BringIntoView()
    48. End Sub
    49. Private Sub Window_KeyDown(sender As Object, e As KeyEventArgs)
    50. If e.Key = Key.Escape AndAlso CanCloseWithEsc Then
    51. Close()
    52. End If
    53. End Sub
    54. Public Sub SetwinState(maximize As Boolean)
    55. If maximize Then
    56. WindowState = WindowState.Maximized
    57. Else
    58. WindowState = WindowState.Normal
    59. End If
    60. End Sub
    61. End Class


    Da wir dieses Window sowohl als normales Fenster, als auch als Dialog verwenden können ist hier etwas Logik enthalten.

    Die DependencyProperties wie CanCloseWithESC und AsSpsModalDialog sollten soweit denke ich klar sein.
    Im Konstruktor wird der DatenKontext übergeben und gehalten welcher dann im FormLoaded auch so übergeben wird.


    OK, da wir nun das Interface und ein Fenster haben können wir uns daran machen den Service zu implementieren, also den Bauplan leben einzuhauchen.
    Ich mache dies allerdings nicht im "View" Projekt sondern im "App" Projekt. Warum? Möchte ich eine zweite Anwendung haben welche die selben Views nutzt aber Dialoge, MessageBoxen und andere Teile anders gestallten oder eine andere Logik haben kann ich dies leichter tun. Da ich in diesem Fall ja einfach eine andere "App" erstelle welche dann die DLLs (View, ViewModel) referenziert kann ich hier die Implementierung der Interfaces abermals frei bestimmen, ist aber sicher Geschmacksache, "richtig" wäre beides.



    OK, im App-Projekt einen Ordner mit "Services" erstellt und schnell eine Klasse DialogWindowService erstellt.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Namespace Services
    2. Public Class DialogWindowService
    3. Implements IDialogWindowService
    4. Private _currentSpsWindow As SpsWindow
    5. Public Function ShowModalDialog(windowname As String, datacontext As Object, owner As Object, Optional topMost As Boolean = False, Optional showInTaskbar As Boolean = True) As Boolean Implements IDialogWindowService.ShowModalDialog
    6. Dim spswin As New SpsWindow(windowname, datacontext, SizeToContent.WidthAndHeight, WindowStartupLocation.CenterOwner)
    7. _currentSpsWindow = spswin
    8. spswin.Topmost = topMost
    9. spswin.ShowInTaskbar = showInTaskbar
    10. spswin.Owner = spswin.FindOwnerWindow(owner)
    11. spswin.AsSpsModalDialog = True
    12. Return CType(Application.Current.Dispatcher.Invoke(Function() spswin.ShowDialog), Boolean)
    13. End Function
    14. Public Sub CloseDialog() Implements IDialogWindowService.CloseDialog
    15. If _currentSpsWindow IsNot Nothing Then
    16. _currentSpsWindow.Close()
    17. End If
    18. End Sub
    19. Public Sub CloseDialog(vm As Object) Implements IDialogWindowService.CloseDialog
    20. Dim owner As Window = Application.Current.Windows.Cast(Of Window)().SingleOrDefault(Function(x) x.DataContext IsNot Nothing AndAlso x.DataContext.GetType Is vm.GetType)
    21. If owner Is Nothing Then
    22. For Each window As Window In (From win In Application.Current.Windows()).ToList
    23. If window.DataContext.GetType Is vm.GetType Then
    24. window.Close()
    25. End If
    26. Next
    27. Else
    28. owner.Close()
    29. End If
    30. End Sub
    31. End Class
    32. End Namespace


    Also, diese Klasse implementiert nun das Interface IDialogWindowService wodurch VS uns automatisch die Methoden erstellt, nun müssen wir nur noch die Logik schreiben.
    In diesem Fall drei Methoden.

    ShowModalDialog:

    Wir Instanziieren ein SpsWindow und setzen ein paar Eigenschaften nach belieben.

    CloseDialog:

    Hier ist nur die erste überladung interessant, diese durchsucht alle in der laufenden App instanzierten Fenster und gibt das erste Fenster welche den übergeben DatenKontext als eigenen DatenKontext enthält zurück. Auch keine Hexerei.

    Gut, jetzt kommt das eigendliche Hexenwerk zum Einsatz. Ein ServiceContainer. Ähnlich wie bei DependencyInjection gibt es bei mir einen ServiceContainer welcher als Singleton implementiert wird und die Services hält. Jedes Service muss diesem Container hinzugefügt (Registriert) werden.

    Wir erstellen uns erstmal einen ServieContainer im ViewModel-Projekt:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class ServiceContainer
    2. Public Shared ReadOnly Instance As New ServiceContainer()
    3. Shared Sub New()
    4. ServiceMap = New Dictionary(Of Type, Object)()
    5. ServiceMapLock = New Object()
    6. End Sub
    7. Public Sub AddService(Of TServiceContract As Class)(implementation As TServiceContract)
    8. SyncLock ServiceMapLock
    9. ServiceMap(GetType(TServiceContract)) = implementation
    10. End SyncLock
    11. End Sub
    12. Public Shared Function GetService(Of TServiceContract As Class)() As TServiceContract
    13. Dim service As Object = Nothing
    14. SyncLock ServiceMapLock
    15. ServiceMap.TryGetValue(GetType(TServiceContract), service)
    16. End SyncLock
    17. Return TryCast(service, TServiceContract)
    18. End Function
    19. Shared ReadOnly ServiceMap As Dictionary(Of Type, Object)
    20. Shared ReadOnly ServiceMapLock As Object
    21. End Class


    Dies ist eben eine klassische Singleton-Klasse. Wir können mit AddService ein Service hinzufügen und mit GetService dieses abrufen.
    Ich bin der Meinung das man hier für den Anfang nicht jede Zeile genau verstehen muss, wichtig ist nur das man weis das hier ein Container liegt in welchen ich ein paar Baumpläne inkl. deren implementierung reinpacke um sie später abrufen zu können.

    OK, jetzt wo wir unseren Container und unsere Services haben können wir ja solche ein Service in diesen Container reinpacken.
    Wir öffnen in unserem App-Projekt die Application.vb und schreiben folgende Zeile in die Application_Startup Methode:

    VB.NET-Quellcode

    1. ServiceContainer.Instance.AddService(Of IDialogWindowService)(New DialogWindowService())


    Wir übergeben also den Typ und die Implementierung unseres Services und machen die Implementierung damit bekannt.

    Nun zum lustigen Teil der ganzen Sache, dem Anwenden im ViewModel, den auf das wollten wir ja raus oder?
    Jep, also hier mal ein Beispiel wie ein Dialog geöffnet wird:

    VB.NET-Quellcode

    1. Dim diaVm = new MeinDialogViewModel()
    2. Dim diaServ = ServiceContainer.GetService(Of Services.IDialogWindowService)
    3. diaServ.ShowModalDialog("firstDialog", diaVm, Me)


    Schliessen etwa so:

    VB.NET-Quellcode

    1. Dim diaServ = ServiceContainer.GetService(Of Services.IDialogWindowService)
    2. diaServ.CloseDialog(diaVm)



    Sieht auf den ersten Blick kompliziert aus, ist es aber nicht. Und das selbe vorgehen ist bei Messageboxen, InputBoxen, ErrorFenstern und was weis der Geier was zu tun.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Richtig. Habe ich geschrieben.
    Hat aber wieder nichts damit zu tun ein ganzes ViewModel als Singleton zu machen. Äpfel und Birnen.
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##