Wie ein untergeordnetes Fenster anzeigen? Oder ist das eine WinForms-Denkweisen-Sackgasse?

  • WPF
  • .NET 5–6

Es gibt 33 Antworten in diesem Thema. Der letzte Beitrag () ist von Akanel.

    Es würde reichen mir zu schreiben, wie ich das umsetze, da die Buttons ein DialogResult natürlich von Haus aus nicht kennen. Aber gerne alternativ auch ins Projekt einbauen.
    Oder Moment: Geht es darum, dass im CodeBehind im Button-Click-EventHandler der Service herbeibeschworen wird und dann …
    Ach, bevor ich hier rumspekulationiere, warte ich auf Dich.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Hallo

    OK, weis jetzt was du meinst. Ich sehe nirgens eine CodeBehind. Wirst schon entfernt haben.
    Die gehts ja darum das du die Zwei Buttons (Übernhemen und Abbrechen) im ucl hast welches ja (korrekterweise) an dein ViewModel gebunden ist. Gut soweit.

    Es führen ja immer mehrere Wege nach Rom und es gibt verschiedene Arten sowas anzugehen.
    Ziemlich cool finde ich folgende Möglichkeit:

    Die WPF Entwickler haben uns beim Window ja ein DependecyProperty DialogResult gegeben. Dieses ist vom Typ Nullabel(Boolean) .
    Setzen wir dieses auf True oder False schließt sich das Fenster.

    OK, aber das können wir weder vom ViewModel aus setzen noch vom UCL aus. Weil das UCL kann ja das zur Laufzeit übergordnete Fenster nicht steuern. Doch - über AttachedProperties.
    Mit diesen hast du ständig zu tun. z.b. innerhalb eines TextBlocks wenn du diesen im Grid positionieren willst mit Grid.Row = 2.
    Damit sagst du das dieser TextBlock im Grid (in welchem es sich ja befindet) positioniert werden soll.

    Wir machen uns also ein AttachedProperty. Ich nenn die Klasse mal WindowHelper:

    VB.NET-Quellcode

    1. Namespace Helper
    2. Public Class WindowHelper
    3. Public Shared ReadOnly DialogResultProperty As DependencyProperty = DependencyProperty.RegisterAttached("DialogResult", GetType(Boolean?), GetType(WindowHelper), New PropertyMetadata(New PropertyChangedCallback(AddressOf DialogResultChanged)))
    4. Private Shared Sub DialogResultChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    5. Dim ctl = TryCast(d, Control)
    6. If ctl IsNot Nothing Then Window.GetWindow(ctl).DialogResult = CType(e.NewValue, Boolean?)
    7. End Sub
    8. Public Shared Sub SetDialogResult(ByVal target As Window, ByVal value As Boolean?)
    9. target.SetValue(DialogResultProperty, value)
    10. End Sub
    11. End Class
    12. End Namespace


    Ändert sich der Wert der Eigenschaft dann wird das Window geholt und der DialogResult gesetzt.

    Nun brauchen wir im ViewModel ein Property DialogResult. Das kannste einfach so einfügen oder wie ich mittels Interface. Du merkst schon, ich stehe auf Interfaces.

    VB.NET-Quellcode

    1. Public Class AccessPointViewModel : Inherits ViewModelBase
    2. Implements IDialogViewModel
    3. Public ReadOnly AccessPoint As AccessPoint = Nothing
    4. Public Event DeleteAccessPoint As EventHandler
    5. Public Property CommandContainer As New CommandContainer
    6. Private Const Letters As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    7. Private Const Digits As String = "0123456789"
    8. Private Const SpecialCharacters As String = " !""#$%&'()*+,-./:;<=>?@[\]^_`{|}~ÄÖÜäöüß"
    9. Private Const AllCharacters As String = Letters & Digits & SpecialCharacters
    10. Public Sub New()
    11. AccessPoint = New AccessPoint With {.Name = "(Name)", .PasswordLength = 16}
    12. End Sub
    13. Public Sub New(AccessPoint As AccessPoint)
    14. Me.AccessPoint = AccessPoint
    15. End Sub
    16. Private _dialogResult As Boolean?
    17. Public Property DialogResult As Boolean? Implements IDialogViewModel.DialogResult
    18. Get
    19. Return _dialogResult
    20. End Get
    21. Set(value As Boolean?)
    22. _dialogResult = value
    23. InformAboutPropertyChange()
    24. End Set
    25. End Property
    26. End Class


    Gut, nun nur noch Binden. Auch recht simpel, wir müssen uns im XAML nur den Namespace reinholen mit:

    XML-Quellcode

    1. xmlns:helper="clr-namespace:PWM.View.Helper"


    und innerhalb der UCL binden:

    XML-Quellcode

    1. helper:WindowHelper.DialogResult="{Binding DialogResult}"


    Sieht Gesamt dann so aus:

    XML-Quellcode

    1. <UserControl x:Class="UclAccessPointConfiguration"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:ViewModels="clr-namespace:ViewModels;assembly=PWM.ViewModel" d:DataContext="{d:DesignInstance IsDesignTimeCreatable=True, Type=ViewModels:AccessPointViewModel}"
    7. xmlns:helper="clr-namespace:PWM.View.Helper"
    8. xmlns:View="clr-namespace:PWM.View"
    9. mc:Ignorable="d"
    10. Height="250" Width="600"
    11. helper:WindowHelper.DialogResult="{Binding DialogResult}">
    12. <Grid>
    13. <Grid.RowDefinitions>
    14. <RowDefinition Height="Auto"/>
    15. ...
    16. ...


    Zur Sicherheit hänge ich das Projekt mal an.

    Grüße
    Sascha
    Dateien
    • PWM.zip

      (214,26 kB, 72 mal heruntergeladen, zuletzt: )
    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. ##

    ?(
    Okay, ich werd mir wohl noch einige Videos, z.B. AttachedProperties von Dir reinziehen, weil …
    Ah, okay. Jetzt blick ich's langsam. Du hast ja auch die Button-Reaktion geändert. Dort wird ja jetzt über die Commands nicht mehr der Service herbeigerufen, sondern nur noch die DialogResult-Property gesetzt. Und das wird automatisch gemeldet und so wird das Window informiert und kann geschlossen werden. So gaaanz langsam klickert's …

    ##########

    @Nofear23m: Hm, hab jetzt noch das Problem, dass mir gezeigt wird, dass bei der Zeile

    XML-Quellcode

    1. helper:WindowHelper.DialogResult="{Binding DialogResult}"
    »die DialogResult-Eigenschaft […] nicht an die Elemente des Type UserControl angehängt werden [kann].« Aber das Problem versuche ich mal selbst zu lösen.

    ##########

    @Nofear23m: Ah, Ok, nach langem Hin und Her (und dem Schauen Deines AttachedProperty-Videos) fand ich nun endlich die Ursache: der 1. Parameter der SetDialogResult-Methode darf kein Window, sondern muss ein DependencyObject sein.

    ##########

    @Nofear23m: Und noch eine Ergänzung: Deine ToggleButton-Zusammenfassung geht, wenn man einen passenden CommandParameter setzt, schriebst Du. Der ist doch aber schon anderweitig vergeben: CommandParameter="{Binding}". Ich glaub ich könnte mich da an den Thread von @kafffee anschließen: Wie mehrere CommandParameter übergeben. Aber Deine Antwort wird wohl sein: »Einfach gaaanz anders machen«, oder?

    Was mir gerade 48 Minuten Kopfzerbrechen bereitet hat: Ich konnte angelegte AccessPoints nicht konfigurieren. Das entsprechende Dialogfenster wurde so schnell geschlossen, dass ich noch nicht mal mitbekam, dass es sich öffnet. Irgendwann dämmerte es mir, dass ich das DialogResult im APVM erst zurücksetzen muss. Tricky …
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „VaporiZed“ ()

    VaporiZed schrieb:

    der 1. Parameter der SetDialogResult-Methode darf kein Window, sondern muss ein DependencyObject sein.

    Oh, ein kleiner Bug von mir. Aber irgendwie auch gut, so hast du dir das genau angesehen und dadurch das man den Fehler selbst findet gibt es sowohl den Ahaaa Moment, als auch das Erfolgserlebnis das man als Entwickler immer wieder braucht. Sehr cool.
    Sollte ich öfters so kleine Bugs einbauen 8|

    VaporiZed schrieb:

    Irgendwann dämmerte es mir, dass ich das DialogResult im APVM erst zurücksetzen muss.

    OK, habe darüber gerade nachgedacht. Ja, dadurch das die Anzeige, als auch das editieren bei dir ja glaube ich das selbe ViewModel ist kann das gut sein. Hat man das extra wirds ja "zerstört" aber in deinem Fall muss man das wieder zurücksetzen. Aber auch hier gut das du es selbst gemerkt hast. Sehr cool.

    VaporiZed schrieb:

    Wie mehrere CommandParameter übergeben.

    Das habe ich total übersehen. Wozu übergibst du überhaupt mit ​CommandParameter="{Binding}" als Parameter den Datenkontext an sich? Den brauchst du doch nur weil sich die Commands in einer anderen Klasse befinden, die sollen sich aber ja in der VM Klasse befinden. Wenn du die ganzen Commands dort nicht haben willst machste eben einer Partial deiner VM Klasse. Dann haste den Parameter wieder frei.

    Falls man doch mal zwei Parameter übergeben müssen kann man das mit einer Struct oder Klasse machen die man übergibt. Also ne Klasse mit zwei Eigenschaften und diese übergibt man.
    Wenns HardCoded Strings sein soll kann man die z.b. wie folgt übergeben "Wert1;Wert2;Wert3" und dann einfach mit Split wieder im Code zerlegen.

    Aber fast immer zeigt sowas ein schlecht gewähltes Design. Deshalb ist in jedem Fall immer ratsam sich darüber Gedanken zu machen warum ich überhaupt zwei Parameter benötige und ob ich die überhaupt benötige.
    Ganz ehrlich, ich habe bei fast keinem Command einen Parameter. Das kommt echt selten vor das man einen braucht. Und wenn dann meisst nur ein String den man übergibt weil man eben einen Command "wiederverwenden" will. Und der String ist dann fast immer Hardcoded.

    So, jetzt ist es eh wieder ein Roman geworden. Wollte ich heute gar nicht mehr.

    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. ##

    Oh, klar. Wenn man natürlich die APVM-spezifischen Commands logischer-/konsequenterweise in der APVM-Klasse beherbergt, kann ich mir die Weiterleitung ans APVM sparen und den Commandparameter anderweitig verwenden.
    Die RelayCommand-Klasse habe ich dementsprechend erweitert, da es ja nun auch die Möglichkeit gibt, parameterlose Commands zu haben. Und da bei den finalen APVM-Methoden Parameter keinen Sinn ergeben, schau ich mal, inwieweit sich der RelayCommand-Klassenumbau diesbezüglich lohnt/auszahlt.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Hallo

    Im Grunde gehts dir bei deinem Workaround ja nur darum das du nicht zig Commands in deinem ich nenne es jetzt mal HauptviewModel hast richtig?

    Ich hätte da eine Idee zur Aufteilung. Wenn interesse dann sag Bescheid, dann würde ich dir das zum testen mal so Umbauen.

    Du hast in deinem Design (ein VM für die Übersicht und für Edit/New einen kleinen Logikfehler.

    Wenn du einen Accesspoint bearbeitest und dann auf Abbrechen gehst dann würden die bearbeiteten Wert stehen bleiben da es ja die selbe Instanz ist, du müsstest also in dem Fall die Daten neu einlesen.

    Nur so als Gedanke.

    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. ##

    Das mit dem Abbrechen habe ich schon auf dem Schirm. Ich wär dann dabei, vor dem DialogWindow-Aufruf die Daten zwischenzuspeichern, entweder plain oder in einer temporären APVM-Instanz, und dann bei Abbruch die Daten zurückzuschreiben. Ich bin aber grad dabei, mir über eine MVVM-kompatible InputBox Gedanken zu machen. Die brauche ich, um ein Passwort manuell festlegen zu können.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Naja, die Inputbox folgt dem selben Schema wie alles andere in MVVM.

    Window, DialogWindow, Messagebix, einfach alles was UI ist erfolgt über Services.

    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. ##

    Habe jetzt für die ConfigDialog-Abbrechen-Funktionalität die Holzhammermethode verwendet. Da MemberwiseClone natürlich nicht für ein APVM einsetzbar ist (weil ja dann doch nur dieselbe AP-Instanz verwendet wird und somit kein Klon entsteht), erzeuge ich also eine neue APVM-Instanz (TempAPVM) und weise ihr die Daten der bisherigen Instanz Property für Property zu. Wird im Config-Dialog auf [abbrechen] geklickt, werden die Daten von TempAPVM zurückgeschrieben. Das funktioniert, sieht aber codetechnisch ziemlich bescheuert aus. Klar, mit Reflection oder später ggf. speichern/laden ginge das bestimmt auch effizienter. Aber gibt es noch bessere Wege?
    Um das Thema InputBox hab ich mich erstmal aus Zeitgründen gedrückt, indem ich das Problem besser gelöst habe: Im Config-Window kann man das bisherige Passwort in einer zusätzlichen Box eingeben. Das wird dann mit den Einstellungen verglichen und ggf. abgelehnt, wenn es nicht den Kriterien entspricht. Ist m.E. sogar zielführender als extra ne InputBox dafür herzunehmen.
    Da kommt mir aber gerade noch eine Idee: Man gibt das alte Passwort ein und die Kriterien passen sich entsprechend an …
    Aber keine Sorge, um die kümmer ich mich demnächst explizit noch.

    ##########

    An der Command-Zusammenfassungs(?)-Idee bin ich interessiert. Meine Commands sind jetzt erstmal in einer Datei, da ein Command nur noch aus einer Private-ICommand-Zeile und einem ReadOnly-Property-Get-Block besteht, also 6 Zeilen pro Command.

    Mein Vorschlag bzgl Erkennung des DesignModes war Mist. Der funktionierte nicht sonderlich gut. habe aber einen besseren gefunden:

    VB.NET-Quellcode

    1. Public Shared ReadOnly Property IsInDesignMode As Boolean
    2. Get
    3. Return DesignerProperties.GetIsInDesignMode(New Windows.DependencyObject())
    4. End Get
    5. End Property

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

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

    Aaaalso, ich habe mich mal kurz ein wenig gespielt um dich hier ein wenig zu Unterstützen.

    Ich bin ja kein Freund von Partial, vorallem in ViewModels, aber das ist ja Geschmacksache.
    Ich habe mal an der AccessPointViewModel die Commands auskommentiert da ich erstmal alle Commands verworfen habe, mir ist das wie gesagt zu unübersichtlich wenn die Commands irgendwo sind und die Methode dann wieder wo ganz so anders. Aber das kannst du ja Handhaben wie du willst, nur auf diese Art und weise brauchst du eben immer einen CommandParameter. Nicht gut.

    VB.NET-Quellcode

    1. 'Public Property CommandContainer As New CommandContainer


    Was habe ich gemacht:
    Ich habe eine neue ViewModel-Klasse erstellt. AddEditAccessPointViewModel
    Diese Klasse soll sich um das editieren kümmern. Von außen (MainViewModel) wird gesteuert ob diesem VM ein neues AccessPoint-Ojekt übergeben wird oder ein vorhandenes. Eben Add oder Edit.
    Dieses Implementiert nun auch das IDialogViewModel um komfortabel und einfach an die Eigenschaft DialogResult zu binden. Jedes ViewModel welches in einem Dialog aufgehen kann sollte ja dieses Interface implementieren, so die Konvention.
    Dieses VM hat eine Eigenschaft CurrentAccessPoint welche vom Constructor gesetzt wird. Je nachdem ob Add oder Edit wird eine vorhandene Instanz übergeben oder eine neue.
    Dann gibt es noch einen Command AcceptNewAccessPointCommand welcher Speichert. Sollte man Unbenennen in SaveCommand da er ja für Add UND für Edit gilt. Aber habe ich mal gelassen.

    Dieser Command setzt ja lediglich den DialogResult wodurch das Fenster zu geht.
    Nun übernimmt im Falle der Neuanlage (New) ja wieder das MainViewModel das Ruder da hier der Code weiterläuft.

    Dieses fügt das neue Objekt der Auflistung hinzu.
    Da der Editieren Button bei dir aber innerhalb des Objekts selbst ist, also innerhalb der AccessPointViewModel gibt es genau dort auch einen EditAccessPointCommand.
    Dieser Erstellt ein neues Model-Objekt mittels generischer "DeepClone" Erweiterungsmethode.

    VB.NET-Quellcode

    1. Public Module CloneObject
    2. Function DeepClone(Of T)(ByRef orig As T) As T
    3. ' Don't serialize a null object, simply return the default for that object
    4. If (ReferenceEquals(orig, Nothing)) Then Return Nothing
    5. Dim formatter As New XmlSerializer(GetType(T))
    6. Dim stream As New MemoryStream()
    7. formatter.Serialize(stream, orig)
    8. stream.Seek(0, SeekOrigin.Begin)
    9. Return CType(formatter.Deserialize(stream), T)
    10. End Function
    11. End Module


    War der DialogResult True, dann wird das Modelobjekt ersetzt und und die View aktualisiert.

    So sieht das dann aus:

    VB.NET-Quellcode

    1. Private Sub EditAccessPointCommand_Execute(obj As Object)
    2. Dim NewModel = [Object].DeepClone(_accessPointModel) 'Objekt Klonen
    3. Dim DialogService = ServiceContainer.GetService(Of IDialogWindowService)
    4. If Not DialogService.ShowModalDialog(New AddEditAccessPointViewModel(NewModel), "AccessPoint editieren", Me, True, True) Then Return
    5. _accessPointModel = NewModel
    6. RefreshView()
    7. End Sub



    VaporiZed schrieb:

    Aber gibt es noch bessere Wege?

    Ja, hab ich dir reingepackt.

    VaporiZed schrieb:

    An der Command-Zusammenfassungs(?)-Idee bin ich interessiert.

    Im Grunde musst du ja nur schaun was du zusammenlegen kannst und willst. Wenn du unbedingt willst brauchst du nur EINEN Command und Arbeitest mit Parametern ala "Edit, NewPasswort, GeneratePassword, ...." und der Command evtl. DoActionCommand

    VaporiZed schrieb:

    Meine Commands sind jetzt erstmal in einer Datei

    OK, aber dann bitte nicht über diesen Container da du dann ja für jeden Command einen Parameter belegst.

    VaporiZed schrieb:

    habe aber einen besseren gefunden

    Habe ich dir reingepackt. Meine Methode:

    VB.NET-Quellcode

    1. Private Shared ReadOnly HostProcesses As New List(Of String)({"XDesProc", "devenv", "WDExpress", "WpfSurface"})
    2. Public ReadOnly Property IsInDesignMode As Boolean
    3. Get
    4. Return HostProcesses.Contains(Process.GetCurrentProcess().ProcessName)
    5. End Get
    6. End Property




    So, nun du ein paar Zuckerl die ich dir noch rein gepackt habe:

    Ich hatte gesehen das du schon mit Validierung probiert hast.
    Ich hatte mich damit auch schon mal fast zur Tode Beschäftigt und die XAML-Möglichkeiten dafür sind ja auch OK. Aber die für jedes Feld zu setzen ist ein Krampf.

    Um sowas komfortabel und vorallem flexibel und vom ViewModel aus auch steuerbar zu bekommen hat sich folgendes bewährt:

    In der Model-Basisklasse (Ja, ich habe dir eine Basisklasse erstellt:

    VB.NET-Quellcode

    1. Public Class ModelBase
    2. Public Property ID As Guid = Guid.NewGuid()
    3. Public Overridable Function Validate() As IEnumerable(Of ValidationResult)
    4. Return ModelValidator.ValidateEntity(Me)
    5. End Function
    6. Public Function GetPropertyValidationError(propertyName As String) As String
    7. If Validate.Where(Function(v) v.MemberNames.Contains(propertyName)).Any Then
    8. Return Validate.Where(Function(v) v.MemberNames.Contains(propertyName)).First.ErrorMessage
    9. Else
    10. Return Nothing
    11. End If
    12. End Function
    13. Public Function IsValid() As Boolean
    14. Return Validate() Is Nothing OrElse Validate.Count = 0
    15. End Function
    16. Public Function IsValid(propertyName As String) As Boolean
    17. If Validate() Is Nothing OrElse Validate.Count = 0 Then Return True
    18. Return Not Validate.Where(Function(v) v.MemberNames.Contains(propertyName)).Any
    19. End Function
    20. End Class


    Dazu gehören folgende Klassen:

    VB.NET-Quellcode

    1. Public Class ModelValidator
    2. Public Shared Function ValidateEntity(Of T As ModelBase)(entity As T) As IEnumerable(Of ValidationResult)
    3. Return New ModelValidation(Of T)().Validate(entity)
    4. End Function
    5. End Class


    VB.NET-Quellcode

    1. Public Class ModelValidation(Of T As ModelBase)
    2. Public Function Validate(entity As T) As IEnumerable(Of ValidationResult)
    3. Dim validationResults As List(Of ValidationResult) = New List(Of ValidationResult)()
    4. Dim validationContext As ValidationContext = New ValidationContext(entity, Nothing, Nothing)
    5. Try
    6. Validator.TryValidateObject(entity, validationContext, validationResults, True)
    7. Catch ex As Exception
    8. Debug.WriteLine(String.Format("FEHLER Validate: {0}", ex.ToString))
    9. Finally
    10. End Try
    11. Return validationResults
    12. End Function
    13. End Class


    So bekommst du eine Automatische Validierung über DataAnnotation.
    Ich hab dies mal nur für die Eigenschaft Name im AccessPoint erstellt.

    VB.NET-Quellcode

    1. <Required(ErrorMessage:="Der Name ist ein Pflichtfeld")>
    2. <MinLength(5, ErrorMessage:="Der Name muss mindestens 5 Zeichen haben")>
    3. Property Name As String


    Und von nun an können wir das ganze im ViewModel fast automatisieren.

    Ein ViewModel implementiert einfach IDataErrorInfo von MS und IViewModelValidation von mir.
    Dieses Interface sieht wie folgt aus:

    VB.NET-Quellcode

    1. Public Interface IViewModelValidation
    2. ReadOnly Property IsValid As Boolean
    3. Function ValidationErrors() As List(Of ValidationResult)
    4. End Interface


    Die Implementierung diese Interface Methoden und Eigenschaften kann man dann ganz machen wie man will. Mit mehr Luxus oder mit weniger.

    Hier mal eine Implementierung mit allem Drum und Dran. Also das Model wird Validiert, zusätzlich zum zeigen als Beispiel Validiere ich dann das ViewModel unabhängig von Model auch nochmal (Website muss mindestens 6 Zeichen haben) und dann Spendiere ich dem ganzen noch zusätzlich eine Auflistung von "Errors" und diese dann wenn ich will auch noch im View anzeigen zu können.

    VB.NET-Quellcode

    1. #Region "IDataErrorInfo Implementation"
    2. Default Public ReadOnly Property Item(columnName As String) As String Implements IDataErrorInfo.Item
    3. Get
    4. Dim valRes As ValidationResult = ValidationErrors.Where(Function(v) v.MemberNames.Contains(columnName) = True).FirstOrDefault
    5. If valRes Is Nothing Then
    6. Return Nothing
    7. End If
    8. CommandManager.InvalidateRequerySuggested()
    9. Return valRes.ErrorMessage
    10. End Get
    11. End Property
    12. Public ReadOnly Property [Error] As String Implements IDataErrorInfo.Error
    13. Get
    14. Return ValidationErrors().FirstOrDefault()?.ErrorMessage
    15. End Get
    16. End Property
    17. Public ReadOnly Property Errors As List(Of String)
    18. Get
    19. Return ValidationErrors.Select(Function(e) e.ErrorMessage).ToList()
    20. End Get
    21. End Property
    22. #End Region
    23. Public Function ValidationErrors() As List(Of ValidationResult) Implements IViewModelValidation.ValidationErrors
    24. Dim valRet As New List(Of ValidationResult)
    25. valRet.AddRange(_accessPointModel.Validate())
    26. If Website Is Nothing OrElse Website.Length < 6 Then
    27. valRet.Add(New ValidationResult("Es muss eine Website angegeben werden!", New List(Of String) From {NameOf(Website)}))
    28. End If
    29. Return valRet
    30. End Function
    31. Public ReadOnly Property IsValid As Boolean Implements IViewModelValidation.IsValid
    32. Get
    33. Dim result = ValidationErrors.Any()
    34. InformAboutPropertyChange(NameOf(Errors))
    35. Return Not result
    36. End Get
    37. End Property


    Das kann dann im View wie folgt aussehen:




    Dafür habe ich einen Style für die Textbox und ein ErrorTemplate in den Resourcen angelegt.

    Schaus dir einfach durch, aber ich denke mit der Basis bist du für das Projekt mal gut aufgehoben.
    Ich weis, das ist alle sehr viel. Services, Interfaces usw.
    Schaut auf den ersten Blick immer viel aus, das kann man sich aber alles Wiederverwendbar gestallten und dann hat man es in jedem Projekt immer zur Verfügung, ist also nur 1x viel.

    Grüße
    Sascha
    Dateien
    • PWM.zip

      (222,8 kB, 60 mal heruntergeladen, zuletzt: )
    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. ##

    Ok, ich bin dann erstmal (mindestens) die nächsten 2 Wochen beschäftigt, um das alles gedanklich zu verarbeiten. Mir ist klar, dass das wie mit einem mathematischen Funktionsvergleich ist:
    Funktion 1: y = x²
    Funktion 2: y = 100x + z
    x ist Codemenge und y ist Verständnisaufwand
    oder auch x ist Zeit und y ist Codemenge
    Funktion 1 ist das klassische 08/15-Programmieren ohne Architektur, Funktion 2 die mit Architektur wie z.B. Deiner. Und ich weiß auch, dass Funktion 2 zwar anfangs einen hohen z-Wert mitbringt, sich aber schneller im weiteren Verlauf amortisiert (kleineres y bei gleichem x). Aber der z-Wert ist für mich momentan noch sehr hoch :S

    @ErfinderDesRades: Das hatte ich zwischendurch mal in Post#5/#15 erwähnt: AP = AccessPoint (meine Modelklasse), APVM = AccessPointViewModel)
    Das mit dem DesignMode kann man auch mit ner ReadOnly Shared Property machen, dann wird's wohl nur einmal erstellt. Aber jeder kann da ja seine eigene Methode verwenden.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

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

    OK, eh gut wenn du das so siehst. Es zahlt sich auf jeden Fall aus.

    Und was ich vor allem gelernt habe, wenn du bei MVVM richtig durchblickst (ohne irgendwelchen Frameworks die dir alles "verschleiern") dann kommst du mit vielen anderen Pattern und Technologien auch super klar weil bei MVVM einfach sehr viel verwendet wird was man bei größeren Projekten unbedingt benötigt wird.

    Bez. deinem DesignTime-Support abrufe habe ich dir ins Beispiel ja meine Methode reinkopiert, mit der hatte ich noch nie Probleme.
    Und Performance.... naja, da scheiden sich die Geister sowieso immer wieder, bei den heutigen Rechnern ist das sowieso alles weniger ein Thema.

    Bevor ich mir an dieser Stelle Gedanken mache sollte man mal sehen das man Asyncron proggt, das ist viel wichtiger, denn meist ist sowieso das Backend der Flaschenhals und nicht die UI.

    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. ##

    Nofear23m schrieb:

    Private Shared ReadOnly HostProcesses As New List(Of String)({"XDesProc", "devenv", "WDExpress", "WpfSurface"})
    Public ReadOnly Property IsInDesignMode As Boolean
    Get
    Return HostProcesses.Contains(Process.GetCurrentProcess().ProcessName)
    End Get
    End Property


    Hierzu habe ich mal eine Frage.
    Woher kommen die Prozesse die du in die Liste packst? Ich verstehe nicht was das für Prozesse sind und wann die auftauchen.
    Ich verstehe den Code so. Du erstellst eine List<T> und packst dort 4 strings rein (was scheinbar Prozesse sind).
    Die Property gibt nun zurück ob der aktuelle Prozessname (ist doch der Prozessname meiner App?) in der Liste vorkommt oder nicht.
    Demnach müsste ich doch den Prozessnamen meiner App auch in die Liste einfügen, sonst kommt doch immer false raus, oder nicht?

    EDIT: Hat sich erübrigt.
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.

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