Grundlagen - MVVM: "Binding-Picking" im Xaml-Editor

    • WPF

    Es gibt 8 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      Grundlagen - MVVM: "Binding-Picking" im Xaml-Editor

      Bindings sind riskant. Der Xaml-Editor erzwingt zwar die richtige Syntax einer Binding-Setzung, erzwingt aber nicht, dass die angebundene Property ühaupt existiert

      XML-Quellcode

      1. <TextBlock Text="{Binding Path=IrgendeinGroberUnfug}"/>
      Das compiliert, und führt nichtmal zum Laufzeit-Absturz. Sondern es funktioniert einfach nicht (und verschlechtert die Performance u.U. erheblich).

      Bei richtig angewandtem MVVM–Pattern kennt der Xaml-Editor allerdings den DataContext, und folglich das Viewmodel, das für ein bestimmtes Element gilt. Und dann kann er seinen besonderen Service anbieten, nämlich das Binding-Picking im Property-Fenster:

      Also ich kann direkt auswählen aus den Properties meines Viewmodels, und der Xaml-Editor generiert das Xaml für mich:

      XML-Quellcode

      1. <TextBlock Text="{Binding Path=Caption}" />

      Ich kann auch Bindings überprüfen - bei falschen Bindings wird die falsche Property unterstrichelt angezeigt:



      Jedenfalls, ich hab mal ein klein Folderbrowser-Dialog gebastelt, weil Wpf bis 2010 sowas nicht anbietet


      FolderBrowser-Xaml

      XML-Quellcode

      1. <Window x:Class="FolderBrowser"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. Title="FolderBrowser" Height="400" Width="500"
      5. xmlns:hlp="clr-namespace:System.Windows.Controls;assembly=HelpersSmallEd"
      6. xmlns:my="clr-namespace:FolderBrowser"
      7. FocusManager.FocusedElement="{Binding ElementName=tv}" Background="#FFEAE8E4">
      8. <!--
      9. -->
      10. <Window.DataContext>
      11. <my:DirectoryTree/>
      12. </Window.DataContext>
      13. <hlp:GridEx>
      14. <hlp:GridEx.RowDefinitions>
      15. <RowDefinition />
      16. <RowDefinition Height="auto" MinHeight="10" />
      17. </hlp:GridEx.RowDefinitions>
      18. <hlp:GridEx.ColumnDefinitions>
      19. <ColumnDefinition MinWidth="10" Width="Auto" />).A
      20. <ColumnDefinition />
      21. </hlp:GridEx.ColumnDefinitions>
      22. <ItemsControl ItemsSource="{Binding Path=SpecialFolderCommands}" Focusable="False">
      23. <ItemsControl.ItemTemplate>
      24. <DataTemplate>
      25. <Button Command="{Binding Path=Command}" Focusable="False">
      26. <StackPanel>
      27. <Image Source="{Binding Path=Image}" Height="35" Width="55" />
      28. <TextBlock HorizontalAlignment="Center" Text="{Binding Path=Caption}"/>
      29. </StackPanel>
      30. </Button>
      31. </DataTemplate>
      32. </ItemsControl.ItemTemplate>
      33. </ItemsControl>
      34. <TreeView x:Name="tv"
      35. hlp:GridEx.Range="b1" ItemsSource="{Binding Path=Drives}"
      36. hlp:VisualSubscribe.Subscribe="{Binding Path=SubscribeTreeview}" >
      37. <TreeView.Resources>
      38. <HierarchicalDataTemplate DataType="{x:Type my:DirectoryNode}" ItemsSource="{Binding Path=Childs}">
      39. <StackPanel Orientation="Horizontal">
      40. <Image Width="16" Height="16" Source="{Binding Path=Image}" />
      41. <TextBlock Margin="2 1 0 1" Text="{Binding Path=Name}" />
      42. </StackPanel>
      43. </HierarchicalDataTemplate>
      44. </TreeView.Resources>
      45. <TreeView.ItemContainerStyle>
      46. <Style TargetType="{x:Type TreeViewItem}">
      47. <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
      48. <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWay}" />
      49. </Style>
      50. </TreeView.ItemContainerStyle>
      51. </TreeView>
      52. <DockPanel hlp:GridEx.Range="a2 b">
      53. <Button x:Name="btOk" Content="Ok" IsDefault="True" Margin="5" Padding="10,0,11,1" />
      54. <Button x:Name="btCancel" DockPanel.Dock="Right" Content="Cancel" IsCancel="True" Margin="5" />
      55. <TextBlock Text="{Binding Path=SelectedItem.Fullname}" VerticalAlignment="Center" TextWrapping="Wrap" />
      56. </DockPanel>
      57. </hlp:GridEx>
      58. </Window>


      Und dann habich ein Youtube-Tutorial gebastelt, was v.a. das Binding-Picking zeigt:
      DirektLink
      Es treten dort auch einige Begrenztheiten des Xaml-Editors zutage:
      Etwa beim Setzen eines Bindings innerhalb eines Styles steht kein Binding-Picking zur Verfügung.
      Oder beim Zufügen eines StringFormats funkt der Xaml-Editor syntaxwidrigen Code dazwischen, den man erst wieder löschen muß!

      Aber im großen und ganzen stellt Binding-Picking eine Art "Option Strict On" für Xaml-Bindings dar, also ein "gepflücktes" Binding funktioniert (höchstwahrscheinlich - auch da gibts leider Ausnahmen), während ein ungestützt hingeschriebenes Binding unendlich viele Fehlermöglichkeiten hinnimmt - und dann einfach nicht funktioniert.
      Dateien

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

      Hallo EDR,
      ich wäre ja auch von Binding-Picking begeistert.

      ErfinderDesRades schrieb:

      Bei richtig angewandtem MVVM–Pattern
      Aber offenbar wende ich MVVM falsch an.
      Ich habe mich mit dem Beitrag von Josh Smith auseinandergesetzt und das Projekt (in stark vereinfachter Form) nachgebaut. Im wesentlichen werden ja hier die ViewModels vom MainViewModel in einer Liste gehalten. Es wird also beim Aufruf eines neuen Fensters (hier Tabs) eine Viewmodel-Instanz erzeugt. Über ein DataTemplate im MainWindow wird die View an das ViewModel gebunden.
      Dadurch gibt es aber (nach meinem jetzigen Kenntnisstand) keine Möglichkeit, im Editor im jeweiligen View mit Binding-Picking zu arbeiten. Denn bei deinem Beispiel wird ja die ViewModelInstanz in den Resources erzeugt.

      XML-Quellcode

      1. <Application.Resources>
      2. <my:MainModel x:Key="MainModel" />
      3. </Application.Resources>
      Das ist also wohl ein anderer Ansatz?
      Ich habe mal mein Projekt angehangen.
      MVVM.zip
      Wie würde man da vorgehen, um die ViewModels an die View zu hängen, so dass sie schon zur Entwurfszeit verfügbar sind? Denn würde ich das über Resources tun, wäre die View an eine andere Instanz als die im MainViewModel erzeugte gebunden...
      :thumbsup: Seit 26.Mai 2012 Oppa! :thumbsup:
      Das MainViewModel wird in den Ressourcen erzeugt wobei MainModel wieder falsch ist das es kein Model ist sondern ein ViewModel.
      In dem MainViewModel werden dann die restlichen ViewModels verwaltet. Dort werden dann von mir aus auch die ViewModel für die Tabs verwaltet. Öffnest du einen neuen Tab wird dort eben eins hinzugefügt.


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      @Vatter: Mein Gott - die c#ler liebens echt von hinten durch die Brust ins Auge.
      Bis man da mal hinter ist, was Josh da mit seine Workspaces und PersonenViewModelse fabriziert!

      Also ich hab jetzt einfach das PersonenView ans MainModel angeschlossen und gut ist, und BindingPicking geht auch.

      Seine Übungen da müsste man nochmal genau analysieren, weil ich blicks grad nicht, welche Funktionen die nach meiner Xaml-Vereinfachung noch erfüllen - aber da kann man bestimmt ziemlich ausmisten.
      Dateien
      • MVVM00.zip

        (23,83 kB, 410 mal heruntergeladen, zuletzt: )
      @ thefiloe,
      türlich werden die Viewmodels im MainViewModel verwaltet. Der Trick is nu, dass das Hinzufügen des ViewModels zu der im MainWindow gebundenen Liste den Tab hinzufügt
      Spoiler anzeigen

      XML-Quellcode

      1. <TabControl Grid.Column="1" Grid.Row="1"
      2. ItemsSource="{Binding Path=WorkSpaces}"
      3. IsSynchronizedWithCurrentItem="True" >
      4. <TabControl.ItemTemplate>...
      Wie dieser Tab gerendert werden soll steht dann hier:
      Spoiler anzeigen

      XML-Quellcode

      1. <Window.Resources>
      2. <DataTemplate DataType="{x:Type vm:PersonenViewModel}">
      3. <vw:PersonListView />
      4. </DataTemplate>
      5. <DataTemplate DataType="{x:Type vm:NocheinViewModel}">
      6. <vw:ColorView />
      7. </DataTemplate>
      8. </Window.Resources>
      Ergo wird ein Viewmodel erzeucht, was über das Datatemplate an die View gebunden wird. Das macht aber ein "Binding-Picking" im Xaml-Editor -meiner Meinung nach- unmöglich.

      @ErfinderDesRades,
      Dein Beilspiel bindt nun wieder das MainViewModel direkt an die View. Das würde bei vielen unterschiedlichen Views dazu führen, dass dieses ViewModel immer größer wird, weil das dann ALLE Daten des Programmes verwaltet. Das entspricht nicht meinem Verständnis von MVVM. Es soll doch für jede View ein Viewmodel geben und für jede View-Instanz also eine Viewmodel-Instanz, odr?.
      Stell dir vor du hättest 1 PersonenListView (Liste aller Personen). Die wird nat. nur 1x gebraucht. Nun möchtest du aber PersonenDetailViews von mehreren Personen gleichzeitig öffnen. Du instanzierst also für jede DetailView ein ViewModel, dem du die betreffenden Daten übergibst. Da diese Instanzen erst zur Laufzeit erstellt werden, stehen die beim Designen des View nicht zum Binden zur Verfügung. Da funzt nur das oben dargestellte Vorgehen vom guten Josh Smith aber eben ohne Picken.
      Eine Alternative ist das "provisorische Binden" mit UserControl.Resources, die man dann wieder rausschmeißen muß, damit die View die richtige Instanz wiedergibt.

      Ich werd das Gefühl nicht los, dass ich da was grundlegendes nicht verstehe....
      :thumbsup: Seit 26.Mai 2012 Oppa! :thumbsup:

      Vatter schrieb:

      Dein Beilspiel bindt nun wieder das MainViewModel direkt an die View. Das würde bei vielen unterschiedlichen Views dazu führen, dass dieses ViewModel immer größer wird, weil das dann ALLE Daten des Programmes verwaltet. Das entspricht nicht meinem Verständnis von MVVM. Es soll doch für jede View ein Viewmodel geben und für jede View-Instanz also eine Viewmodel-Instanz, odr?.
      Mich ficht das nicht weiter an, wenn letztlich alle Viewmodels im MainViewmodel zusammenfließen. Damit ist im Grunde nur das View korrekt modelliert, denn auch in der Oberfläche einer Anwendung fließt die gesamte Anwendungs-Funktionalität letztlich im MainWindow zusammen.
      Das MainViewModel muss sich deswegen nicht pervers aufblähen, denn man kann ja Unterbereiche strukturieren, die einen Bereich an Funktionalität (der u.U. sehr groß sein kann) zusammenfasst in einer einzigen Property des MainViewmodels.
      Üblicherweise hängen die Funktions-Bereiche einer Anwendung ja auch inhaltlich durchaus miteinander zusammen (sonst würde man ja 2 Anwendungen schreiben).
      So auch hier: Ist doch ClusterFuck, Methoden zu schreiben, die ein PersonenViewmodel erzeugen, in die Workspace-Auflistung schmeißen, und dann überprüfen, ob genau dieses PersonenViewmodel bereits drinne ist.
      Aber eben das dollste ist, dass die MainModel.PersonenListe ans neu geschaffene PersonenViewmodel zugewiesen wird, damit man im Xaml ja nicht ans Mainmodel bindet, sondern ans PersonenViewmodel (und indirekt ists eben doch ans Mainmodel gebunden, denn es ist ja die Personenlisten-Referenz zugewiesen).
      Ich hätte vmtl. die WorkspaceModels von vornherein instanziert und in die WorkspaceListe gepackt, und dem WorkspaceModel eine Boolean "IsVisible"-Property angedeihen lassen. Und die WorkspaceListe-CollectionView hätte einen Filter auf IsVisible. Damit könnteman die Workspaces dann an- und aus-knipsen nach Belieben.
      Also fände ich logischer, und wäre bestimmt mal wieder nur die Hälfte Code.

      Stell dir vor du hättest 1 PersonenListView (Liste aller Personen). Die wird nat. nur 1x gebraucht. Nun möchtest du aber PersonenDetailViews von mehreren Personen gleichzeitig öffnen. Du instanzierst also für jede DetailView ein ViewModel, dem du die betreffenden Daten übergibst. Da diese Instanzen erst zur Laufzeit erstellt werden, stehen die beim Designen des View nicht zum Binden zur Verfügung. Da funzt nur das oben dargestellte Vorgehen vom guten Josh Smith aber eben ohne Picken.
      Ok - aber das ist wohl extrem selten, dass man sich von mehreren Personen gleichzeitig mehrere Details angucken will - also das verlangt dem User schon einige Konzentration ab, und einen großen Bildschirm ;)
      Aber egal -
      Ich habe auch einen Hack gemacht, mit dem man bei unveränderter Anwendungsarchitektur trotzdem BindingPicking hinkriegt: Zum Designen bekommt das PersonenView eine Dummi-Instanz:

      XML-Quellcode

      1. <UserControl x:Class="View.PersonListView"
      2. ...
      3. xmlns:vm="clr-namespace:MVVM.ViewModel"
      4. >
      5. <FrameworkElement.DataContext>
      6. <vm:PersonenViewModel/>
      7. </FrameworkElement.DataContext>
      8. <Grid DataContext="{Binding Path=PersonenListCollection}">
      9. ...
      Die Dummi-Instanz stellt sogar eine mit einer Dummi-Person initialisierte PersonenListCollection bereit, und dann pickt sichs ganz vorzüglich:

      VB.NET-Quellcode

      1. Imports MVVM.Model
      2. Namespace ViewModel
      3. Public Class PersonenViewModel : Inherits WorkspaceViewModel
      4. Public Property PersonenListCollection As New ListCollectionView({New Person("dummy", "Mock")})
      5. End Class
      6. End Namespace
      Son fabelhaftes Dummi-Viewmodel schirmt leider das Erben des Datacontextes ab, wenn dieses ucl als DataTemplate zB im TabControl angewendet wird.
      Deshalb, zur Laufzeit, im bösen Codebehind, ersetze ich die Dummi-Instanz durch den Datacontext des Eltern-Elements:

      VB.NET-Quellcode

      1. Namespace View
      2. Public Class PersonListView
      3. Public Sub New()
      4. InitializeComponent()
      5. End Sub
      6. Private Sub UserControl_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles MyBase.Loaded
      7. DataContext = DirectCast(VisualTreeHelper.GetParent(Me), FrameworkElement).DataContext
      8. End Sub
      9. End Class
      10. End Namespace


      Ich werd das Gefühl nicht los, dass ich da was grundlegendes nicht verstehe....
      Och, ich hab das Gefühl, du hast ein ausgezeichnetes Verständnis der Sache, oder wir sind halt beide doof - kann ja auch sein.
      Aber ich denke eher, dass Wpf doof ist, dass man solche Verrenkungen veranstalten muß, damit der Designer seinen Job macht.
      Oder mit Josh Smith stimmt was nicht, dasserda son unsicheren Programmier-Stil ganz unkritisch als das neueste und tollste der Welt verkaufen tut.
      Ist doch unerhört, dass man heutzutage wieder Schreibfehler einprogrammieren kann, unds gibt keine Fehlermeldung - da war doch VB6 weiter entwickelt.
      Und fast noch unerhörter findich, dass sich niemand drüber wundert.
      Dateien
      • MVVM01.zip

        (25,3 kB, 367 mal heruntergeladen, zuletzt: )

      Lösung ohne Hack

      Heute habich den d: DataContext kennengelernt :)
      Den d: - Namespace bindet man so ein:

      XML-Quellcode

      1. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      2. mc:Ignorable="d"

      Der d: - Namespace tätigt Setzungen, die nur zur Designzeit aktiv sind. Nun ist damit auch Setzen eines DataContexts möglich, speziell für die Designzeit:

      XML-Quellcode

      1. <UserControl x:Class="View.PersonListView"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:vm="clr-namespace:MVVM.ViewModel"
      5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      6. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      7. mc:Ignorable="d"
      8. d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{StaticResource PersonMock}">

      Die PersonMock-Resource habich ins App.Xaml gepackt:

      XML-Quellcode

      1. xmlns:vm="clr-namespace:MVVM.ViewModel">
      2. <Application.Resources>
      3. <!--<ViewModel:MainViewModel x:Key="MainViewModel"/>-->
      4. <vm:PersonenViewModel x:Key="PersonMock" />
      5. </Application.Resources>

      Jo, nun brauche ich kein Code-Behind mehr, um den DataContext vom Container zu erben, und kann trotzdem fabelhaft im Designer Binding-Picking veranstalten.

      Also soo doof ist wpf nun doch wieder nicht
      :D
      Nur als Info.
      Du musst bei

      Quellcode

      1. d:DataContext
      kein

      Quellcode

      1. StaticResource
      verwenden, da dort 90% überflüssig sind.
      Für

      Quellcode

      1. d:DataContext
      gibt es extra

      Quellcode

      1. d:DesignInstance
      . Du kannst das so verwenden:

      Quellcode

      1. d:DataContext={d:DesignInstance {x:Type PersonTyp}}


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.