Dynamisches Anzeigen von UserControls mit Drag&Drop

  • WPF

Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von florian03.

    Dynamisches Anzeigen von UserControls mit Drag&Drop

    Hallo,

    heute komme ich mal wieder mit einem Interessanten Problem.
    Ich will ein kleines Programm zum Verwalten von Musikstücken schreiben.
    Dabei können Musikstücke (Song) einer Liste (OrganisationList) zugeordnet werden.

    Hier mal die ViewModels von Song und OrganisationList:
    Spoiler anzeigen

    C#-Quellcode

    1. public class SongViewModel : ViewModelBase
    2. {
    3. public Song SongModel { get; set; }
    4. public SongViewModel(Song songModel, (List<GenreViewModel> Genres, List<OrganisationListViewModel> OrganisationLists ) aviableData)
    5. {
    6. SongModel = songModel;
    7. AviableGenres = aviableData.Genres;
    8. AviableOrganisationLists = aviableData.OrganisationLists;
    9. Name = SongModel.Name;
    10. Interpret = SongModel.Interpret;
    11. Album = SongModel.Album;
    12. ID = SongModel.ID;
    13. Comment = SongModel.Comment;
    14. CreatedBy = SongModel.CreatedBy;
    15. CreationTimeStamp = SongModel.CreationTimeStamp;
    16. LastModifiedBy = SongModel.LastModifiedBy;
    17. LastModifiedTimeStamp = SongModel.LastModifiedTimeStamp;
    18. Genre = AviableGenres.Where(x => x.ID == SongModel.GenreID).FirstOrDefault();
    19. OrganisationList = AviableOrganisationLists.Where(x => x.ID == SongModel.OrganisationListID).FirstOrDefault();
    20. }
    21. public string Name
    22. {
    23. get { return SongModel.Name; }
    24. set { SongModel.Name = value; RaisePropertyChanged(); }
    25. }
    26. public string Interpret
    27. {
    28. get { return SongModel.Interpret; }
    29. set { SongModel.Interpret = value; RaisePropertyChanged(); }
    30. }
    31. public string Album
    32. {
    33. get { return SongModel.Album; }
    34. set { SongModel.Album = value; RaisePropertyChanged(); }
    35. }
    36. #region ModelBase
    37. //ModelBase
    38. public Guid ID
    39. {
    40. get { return SongModel.ID; }
    41. set { SongModel.ID = value; RaisePropertyChanged(); }
    42. }
    43. public string Comment
    44. {
    45. get { return SongModel.Comment; }
    46. set { SongModel.Comment = value; RaisePropertyChanged(); }
    47. }
    48. public string CreatedBy
    49. {
    50. get { return SongModel.CreatedBy; }
    51. set { SongModel.CreatedBy = value; RaisePropertyChanged(); }
    52. }
    53. public DateTime CreationTimeStamp
    54. {
    55. get { return SongModel.CreationTimeStamp; }
    56. set { SongModel.CreationTimeStamp = value; RaisePropertyChanged(); }
    57. }
    58. public string LastModifiedBy
    59. {
    60. get { return SongModel.LastModifiedBy; }
    61. set { SongModel.LastModifiedBy = value; RaisePropertyChanged(); }
    62. }
    63. public DateTime LastModifiedTimeStamp
    64. {
    65. get { return SongModel.LastModifiedTimeStamp; }
    66. set { SongModel.LastModifiedTimeStamp = value; RaisePropertyChanged(); }
    67. }
    68. //End ModelBase
    69. #endregion
    70. public List<GenreViewModel> AviableGenres { get; }
    71. public List<OrganisationListViewModel> AviableOrganisationLists { get; }
    72. private GenreViewModel _Genre;
    73. public GenreViewModel Genre
    74. {
    75. get { return _Genre; }
    76. set { _Genre = value; SongModel.GenreID = value.ID; RaisePropertyChanged(); }
    77. }
    78. private OrganisationListViewModel _OrganisationList;
    79. public OrganisationListViewModel OrganisationList
    80. {
    81. get { return _OrganisationList; }
    82. set { _OrganisationList = value; SongModel.OrganisationListID = value.ID; RaisePropertyChanged(); }
    83. }
    84. }



    Spoiler anzeigen

    C#-Quellcode

    1. public class OrganisationListViewModel : ViewModelBase
    2. {
    3. public OrganisationList OrganisationListModel { get; set; }
    4. public OrganisationListViewModel(OrganisationList organisationListModel,List<StatusViewModel> aviableData)
    5. {
    6. OrganisationListModel = organisationListModel;
    7. AviableStatuses = aviableData;
    8. Name = OrganisationListModel.Name;
    9. ID = OrganisationListModel.ID;
    10. Comment = OrganisationListModel.Comment;
    11. CreatedBy = OrganisationListModel.CreatedBy;
    12. CreationTimeStamp = OrganisationListModel.CreationTimeStamp;
    13. LastModifiedBy = OrganisationListModel.LastModifiedBy;
    14. LastModifiedTimeStamp = OrganisationListModel.LastModifiedTimeStamp;
    15. Status = AviableStatuses.Where(x => x.ID == OrganisationListModel.StatusID).FirstOrDefault();
    16. }
    17. public string Name
    18. {
    19. get { return OrganisationListModel.Name; }
    20. set { OrganisationListModel.Name = value; RaisePropertyChanged(); }
    21. }
    22. public string NameAndStatus
    23. {
    24. get { return Name + " - " + Status.Name; }
    25. }
    26. #region ModelBase
    27. //ModelBase
    28. public Guid ID
    29. {
    30. get { return OrganisationListModel.ID; }
    31. set { OrganisationListModel.ID = value; RaisePropertyChanged(); }
    32. }
    33. public string Comment
    34. {
    35. get { return OrganisationListModel.Comment; }
    36. set { OrganisationListModel.Comment = value; RaisePropertyChanged(); }
    37. }
    38. public string CreatedBy
    39. {
    40. get { return OrganisationListModel.CreatedBy; }
    41. set { OrganisationListModel.CreatedBy = value; RaisePropertyChanged(); }
    42. }
    43. public DateTime CreationTimeStamp
    44. {
    45. get { return OrganisationListModel.CreationTimeStamp; }
    46. set { OrganisationListModel.CreationTimeStamp = value; RaisePropertyChanged(); }
    47. }
    48. public string LastModifiedBy
    49. {
    50. get { return OrganisationListModel.LastModifiedBy; }
    51. set { OrganisationListModel.LastModifiedBy = value; RaisePropertyChanged(); }
    52. }
    53. public DateTime LastModifiedTimeStamp
    54. {
    55. get { return OrganisationListModel.LastModifiedTimeStamp; }
    56. set { OrganisationListModel.LastModifiedTimeStamp = value; RaisePropertyChanged(); }
    57. }
    58. //End ModelBase
    59. #endregion
    60. public List<StatusViewModel> AviableStatuses { get; }
    61. private StatusViewModel _Status;
    62. public StatusViewModel Status
    63. {
    64. get { return _Status; }
    65. set { _Status = value; OrganisationListModel.StatusID = value.ID; RaisePropertyChanged(); }
    66. }
    67. }



    Jetzt will ich eine "interessante" UI gestalten.
    Erstmal habe ich zwei Skizzen angehangen un es zu verdeutlichen:




    Ich möchte im Entefekt an der linken Seite eine ListView haben, in der alle OrganisationLists zu sehen sind. Auserdem eine Checkbox um auszuwählen, ob sie zu "sehen" sind.
    Je nach dem ob die Combobox aktiviert ist, soll auf der rechten Seite ein neues ListView eingeblendet werden, in dem jeweils alle Songs zu sehen sind, die der OrganisationList zugeordnet sind.
    Als Bonus oben drauf soll man die Songs dann noch zwischen den ListViews "hin und her" ziehen können, um sie so einer neuen OrganisationList zuzuordnen.

    Nun habe ich leider keinen Ansatz wie ich das ViewModel für dieses View gestalten soll (also eine ObservableCollection(Of OrganisationListViewModel) und die Songs müssen halt irgendwie rein).
    Und ich denke man kann das mit den ganzen ListViews, die eingeblendet werden sollen irgendwie mit UserControls (und DataTemplates) lösen...

    Vielen Dank für eure Mühe schon mal bis hier her zu lesen :)

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Hallo Florian

    In diesem Fall sein bitte so gut und mach dich eine kleine Beispielanwendung mit solchen Listen und ein paar Dummyeinträgen. Ich weis, das klingt jetzt vieleicht doof und faul, aber ich habe im Moment nicht die Muse solch eine Beispielanwendung komplett zu machen. Drag&Drop ist schon was eigenes, war es unter WinForms schon, in WPF ist das aber auch nicht viel anders gelöst.
    Sei doch so gut und mach mal ne kleine einfache Demoapp - mir fehlt im Moment die Zeit, oder aber vieleicht hat jemand anderes mehr Zeit.

    So viel vorab: MVVM oder nicht ist da relativ egal. Das dies etwas ist (also Drag & Drop) was rein die View betrifft wir das alles sowieso innerhalb des UserControls gemacht.
    Im ViewModel solltest du nur zwei Commands haben. Eines zum hinzufügen eines Eintrag in die Auflistung und einen zum entfernen.

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

    Hallo Sascha

    Nofear23m schrieb:

    Ich weis, das klingt jetzt vieleicht doof und faul

    Des klingt doch gar nicht doof und faul, das ist ja das mindeste was ich tun kann....

    Also ich habe jetzt mal ein kleines Demoprojekt erstellt, allerdings konnte ich in dem besagten ViewModel nur die Beispieldatenerstellung intigrieren.
    Du musst um Gottes Willen auch nicht alles umsetzen, du musst mir nur an der richtigen Stelle die richtigen Denkanstöße geben :)

    Ach ja, du brauchst dich auch nicht so zu hetzen ich bin erst Dienstag Nachmittag wieder da.

    Schon mal vielen Dank für deine Zeit die du investierst. :thumbsup:

    Viele Grüße und gute Nacht
    Florian
    Dateien
    • DragDropDemo.zip

      (232,57 kB, 159 mal heruntergeladen, zuletzt: )
    ----

    WebApps mit C#: Blazor
    Hi.

    Wie Sie sehen, sehen sie Nix... Hmmm! Ahhh! Ein weisser Adler auf weißem Grund! :D

    Das wichtigste zu erst: ObservableCollection(s)!

    Für jede der ListViews eine ObservableCollection im MainWindowViewModel anlegen und mittels Binding (ItemsSource) verknüpfen.

    In der linken Liste sind die Datentypen der "Organisationen" anzulegen.

    Nur damit ich es einfacher habe, nenne ich mal die Organisationen "PlaylistItem".
    Was ich vermute, ist das Du einen PlaylistEditor mit Drag´n Drop programmieren willst.

    Somit ist es klar zu unterteilen, was wohin gehört...

    Die "Hauptdaten" ist eine Liste von Playlisten, die mittels ToggleButton (Schiebeknopf) in eigenen Listen angezeigt werden, wenn "eingeschaltet".
    Hauptdaten nenne ich einmal "PlaylistItems".
    Wenn keine Playlist "eingeschaltet" ist, werden dann auch keine weitern Listen angezeigt.

    In deinem Beispiel-Projekt, hast Du dafür GUIDs und Name in der Klasse "OrganisationList".

    Das ist, meines erachtens, unnötig Kompliziert.
    Es ist viel einfacher umzusetzen.

    VB.NET-Quellcode

    1. Public Property PlaylistItems as ObservableCollection(Of PlaylistItem) = New ObservableCollection(Of PlaylistItem)


    Somit würde das "Model" eine Klasse mit dem Namen PlaylistItem sein.

    Und wann immer eine neue(r) Playlist(Item) erstellt wird, wird auch gleich ein ViewModel vom Typ "SongsViewModel" instanziiert.

    Das "SongsViewModel" beinhaltet abermals eine ObservableCollection(Of Song).

    VB.NET-Quellcode

    1. Public Property Songs as ObservableCollection(Of Song) = New ObservableCollection(Of Song)


    Erkennst du das Muster?
    Public Property Menschen as ObservableCollection(Of Mensch) = New ObservableCollection(Of Mensch)
    Public Property Völker as ObservableCollection(Of Volk) = New ObservableCollection(Of Volk)
    Public Property Universumse as ObservableCollection(Of Universum) = New ObservableCollection(Of Universum)

    Universumse heist wohl eher Multiversum... :/

    Damit ist die Anzahl der ViewModels mit MainVM, PlaylistItemsVM, und SongsVM (1:n) recht überschaubar.

    Und da die ViewModels vom Typ "SongsViewModel" dynamisch hinzugefüg werden, empfiehlt sich hier mittels DataTemplates dies dann "anzuzeigen".

    Hier ein Beispiel aus einem meiner Projekte, die in einer ResourceDictionary in der "App.xaml" einmalig abgelegt sind.

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type viewModels:SerienViewModel}">
    2. <views:SerienView DataContext="{Binding}" />
    3. </DataTemplate>
    4. <DataTemplate DataType="{x:Type viewModels:SettingsViewModel}">
    5. <views:SettingsView DataContext="{Binding}" />
    6. </DataTemplate>
    7. <DataTemplate DataType="{x:Type viewModels:SerieItemViewModel}">
    8. <views:SerieItemView DataContext="{Binding .}" />
    9. </DataTemplate>
    10. <DataTemplate DataType="{x:Type viewModels:StaffelnViewModel}">
    11. <views:StaffelnView DataContext="{Binding}" />
    12. </DataTemplate>
    13. <DataTemplate DataType="{x:Type viewModels:StaffelItemViewModel}">
    14. <views:StaffelItemView DataContext="{Binding .}" />
    15. </DataTemplate>


    Hier "vermähle" ich die View mit dem ViewModel.
    Wenn ein ViewModel in XAML eingepflegt wird, holt das XAML diese "Vermählung" dann hier ab.

    Um das in XAML dann für das jeweilige "heranholen", benötigt es eins kleinen Tricks.

    XML-Quellcode

    1. <ContentControl Content="{Binding SerienVm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />


    Mithilfe des ContentControl lege ich den Inhalt fest, der als Binding eine ViewModel zurück gibt.
    Wichtig! "Mode=TwoWay" und "UpdateSourceTrigger=PropertyChanged" müssen dabei sein! Hugh habe gesprochen... ;)

    Und damit auch mit Vorschaudaten während des Programmierens gearbeitet werden kann, empfiehlt sich folgendes:

    XML-Quellcode

    1. d:DataContext="{d:DesignInstance {x:Type viewModels:MainWindowViewModel},
    2. IsDesignTimeCreatable=True}"

    Beispiel für das MainWindowViewModel.

    Das ist das nervige bei MVVM, aber wenn das aus dem Weg ist, ist alles soooo einfach, wie Pusteblumenpusten.

    Im Codebehind der MainWindowViewModel lege ich immer das hier an:

    VB.NET-Quellcode

    1. Public Property ViewModel As MainWindowViewModel = New MainWindowViewModel


    Dann setzte ich den Datenkontext hiermit:

    VB.NET-Quellcode

    1. Public Sub New()
    2. ' Dieser Aufruf ist für den Designer erforderlich.
    3. InitializeComponent()
    4. ' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu.
    5. DataContext = ViewModel
    6. End Sub


    Das war jetzt viel zu viel MVVM "geklöppel"...

    Zum Thema Drag´n Drop nochmal.

    VB.NET-Quellcode

    1. Private Sub Window_DragEnter(sender As Object, e As DragEventArgs)
    2. If e.Data.GetDataPresent(DataFormats.FileDrop) Then
    3. e.Effects = DragDropEffects.Copy
    4. End If
    5. End Sub
    6. Private Sub Window_Drop(sender As Object, e As DragEventArgs)
    7. If e.Data.GetDataPresent(DataFormats.FileDrop) Then
    8. 'Folder or Files
    9. Dim files = e.Data.GetData(DataFormats.FileDrop)
    10. For Each file In files
    11. If Directory.Exists(file) Then
    12. For Each filt In ViewModel.Settings.FileExtensions
    13. For Each item In GetAllFiles(file, "*." & filt)
    14. If ViewModel.ListOfKeybindFiles.Any(Function(x) x.FileLocation = item) = False Then
    15. ViewModel.ListOfKeybindFiles.Add(New KeybindFile(item))
    16. End If
    17. Next
    18. Next
    19. Else
    20. For Each filt In ViewModel.Settings.FileExtensions
    21. If file.ToString.EndsWith("." & filt) Then
    22. If ViewModel.ListOfKeybindFiles.Any(Function(x) x.FileLocation = file) = False Then
    23. ViewModel.ListOfKeybindFiles.Add(New KeybindFile(file))
    24. End If
    25. End If
    26. Next
    27. End If
    28. Next
    29. End If
    30. e.Effects = DragDropEffects.None
    31. End Sub
    32. Private Function GetAllFiles(path As String, searchPattern As String) As IEnumerable(Of [String])
    33. Return _
    34. Directory.EnumerateFiles(path, searchPattern).Union(
    35. Directory.EnumerateDirectories(path).SelectMany(Function(d)
    36. Try
    37. Return GetAllFiles(d, searchPattern)
    38. Catch e As UnauthorizedAccessException
    39. Return Enumerable.Empty(Of [String])()
    40. End Try
    41. End Function))
    42. End Function


    Damit habe ich eine Programmweites Drag´n Drop für Ordner und Dateien umgesetz.
    Steht auch im CodeBehind des MainWindow.xaml.

    Wichtig sind das erlauben von "Dropbarkeit"... AllowDrop muss gesetz werden!
    _DragEnter: Ist, was das Empfangen von "geDragtem/gezogenem" anzeigt, wenn Dateien gezogen werden, dann zeigt es den Copy-Cursor.
    _Drop: Ist, was die Magie ausmacht... Also die Verarbeitung der "fallenglasenen" Inhalte... In deinem Fall werden das die "Songs" sein.

    Weiteres zum Thema Drag´n Drop gibt es hier im Forum.

    und noch was... Ich dachte das wäre hier VB-Paradise und kein C+ oder C+- ... was sagst du Schatz? Wie? Das heist WIE? Tzieh Scharp??? Ohhh?!
    Na dann werde ich hier mal nicht gleich die geschweiften Klammern "rumdraggen", und sie Dir nicht gleich vor den Compiler "Droppen"...
    Ja Schatz, ich beruhige mich schon wieder. Jaha, ich habe die Blumen gegossen... grummel motz grumpf

    Ich hoffe ich konnte Dir so richtig den Kopf voll machen mit MVVM gedöns.

    Und als Angebot könnte ich einen LiveStream zum Thema machen falls gewünscht.

    Weis gar nicht wie das geht, aber wird schon...

    Ich mach mal Kaffeepause.

    c.u. Joshi


    EDIT: Ich habe voll vergessen zu erwähnen, das es kein "DragDropViewModel()" braucht.
    Das ist ein Denkfehler, das Drag´n Drop ist nur im UI also im View, und muss zum ViewModel übergeben werden.

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

    Hallo,

    ich kann leider nur von Handy antworten da ich unterwegs bin.
    Deswegen kann ich den Inhalt auch noch nicht ausprobieren.

    Aber ich sage schon Mal vielen, vielen Dank für eine solch super ausführliche Antwort.
    Das Angebot mit einem Livestream fände ich sehr interessant.... Also wenn du das umsetzten könntest, würde es mich echt freuen. Es werden sich bestimmt noch ein paar "WPF-Begeisterte" finden.
    Wann würdest du das dann grob machen @Joshi?

    Viele Grüße und bis morgen, wo ich nochmals ausführlicher antworte
    Florian
    ----

    WebApps mit C#: Blazor