Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

De_Struktor

unregistriert

11

25.11.2013, 19:51

find ich super von euch :) Davon hat jeder was!

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

12

26.11.2013, 09:04

OK, das wird jetzt etwas länger ;)

Es gibt in diesem Beispiel kein konkretes Model, sondern nur ViewModels und Views. Ein entsprechendes Model dran zu hängen sollte trivial sein, da nur die get- und set-Methoden der Properties entsprechend angepasst werden müssen. Das habe ich mir hier gespart, damit es so übersichtlich und kurz wie möglich bleibt. Ich kann es auf Wunsch aber gern auch noch einfügen, damit es ein vollständiges MVVM-Beispiel ist.
Das Beispiel nutzt keinen Code behind, sondern reine ViewModels und XAML mit DataBinding.

Entwickelt wurde das:

(Link)


Fangen wir mit dem einfachen Fenster an. Es enthält oben einen Button (Zeile 14) und dann eine variable Anzahl von EMail-Einträgen (Zeile 17-23) in einem ScrollViewer (Zeile 16). Das ItemsControl zeigt eine Menge von MailItemViews an. Die Daten bezieht jeder MailItemView dabei aus seinem Binding. Dieses kommt aus einer Collection von MailItemViewModel, welche über die Property MailItems aus dem MainWindowModel bezogen wird. Das MainWindowModel hat also eine Collection von ViewModels für die MailItems und keine MailItemViews selbst.
Der Button zum Hinzufügen für Mails ist an einen Command gebunden, welcher auch über eine Property im MainWindowModel bereitgestellt wird: AddCommand. Wird der Button geklickt, wird der Command aufgerufen und ein neues MailItemViewModel erzeugt und der Liste hinzugefügt.
In Zeile 7 wird eine neue Instanz des MainWindowModels als DataContext an das MainWindow gehängt.

Hier das XAML für das MainWindow:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MultiItems" x:Class="MultiItems.MainWindow"
        Title="MainWindow" Height="350" Width="200">
    <Window.DataContext>
        <local:MainWindowModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Command="{Binding AddCommand}" Content="New Mail"/>

        <ScrollViewer Grid.Row="1">
            <ItemsControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding MailItems}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local:MailItemView DataContext="{Binding}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>
</Window>


Hier das MainWindowModel dazu. Zeile 7 deklariert den Command, Zeile 13 initialisiert ihn und verknüpft ihn mit einer Methode. Zeile 16-18 macht ihn nach außen für das XAML-Databinding bekannt. Zeile 27-30 zeigt die Methode, welche der AddCommand ausführt. Die Methode erzeugt einfach nur ein neues MailItemViewModel und fügt es zur Liste der ViewModels hinzu. Übergeben wird dabei ein Name, eine EMail und ein Event-Handler, der gerufen werden soll, wenn für das MailItem der "Delete"-Button gedrückt wurde. Zeile 32-38 zeigt die Methode, welche das Delete dann letztlich ausführt.
Zeile 21-25 hält die Liste der MailItemViewModels und macht sie nach außen für das XAML-Binding bekannt.

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Collections.ObjectModel;
using System.Windows.Input;

namespace MultiItems
{
    internal class MainWindowModel
    {
        private readonly ICommand _addCommand;

        public MainWindowModel()
        {
            _addCommand = new CommandHandler(ExecuteAdd);
        }

        public ICommand AddCommand
        {
            get { return _addCommand; }
        }

        private readonly ObservableCollection<MailItemViewModel> _mailItems = new ObservableCollection<MailItemViewModel>();
        public ObservableCollection<MailItemViewModel> MailItems
        {
            get { return _mailItems; }
        }

        private void ExecuteAdd()
        {
            MailItems.Add(new MailItemViewModel(OnDeleteMail) {Name = string.Empty, EMail = "foo@bar.de"});
        }

        private void OnDeleteMail(object sender, EventArgs eventArgs)
        {
            var mail = sender as MailItemViewModel;
            if (mail == null)
                return;
            _mailItems.Remove(mail);
        }
    }
}


Das User-Control für Mail-Items ist sehr einfach gestrickt. Ein paar Labels für Name und eMail (Zeile 14+16), jeweils eine Eingabebox (15+17) dazu und ein Button, welcher das Item löschen soll (Zeile 18 ). Die TextBoxen für Name und eMail sind dabei an die Properties des DataContexts gebunden. Die Properties dazu finden sich entsprechend im MailItemViewModel.
Der Button ist auch wieder an einen Command gebunden, der sich im MailItemViewModel findet.

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<UserControl x:Class="MultiItems.MailItemView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid Margin="5">
         <Grid.ColumnDefinitions>
             <ColumnDefinition/>
             <ColumnDefinition/>
         </Grid.ColumnDefinitions>   
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Grid.Column="0" Grid.Row="0" Content="Name: "/>
        <TextBox  Grid.Column="1" Grid.Row="0" Text="{Binding Name}" Margin="2"/>
        <Label Grid.Column="0" Grid.Row="1" Content="Mail: "/>
        <TextBox  Grid.Column="1" Grid.Row="1" Text="{Binding EMail}" Margin="2"/>
        <Button Height="20" Grid.ColumnSpan="2" Grid.Row="2" Margin="2" Content="Delete" Command="{Binding DeleteCommand}"/>
    </Grid>
</UserControl>


Das MailItemViewModel enthält die Properties für die Eingabefelder und den Command. Der Command macht nichts weiter, als den im Konstruktor übergebenen Event-Handler aufzurufen, sofern dieser nicht null ist. Damit wird das Parent-Objekt informiert, dass das Mail-Item entfernt werden soll. Das Kind kennt dabei seinen Parent nicht, sondern nur den zu rufenden EventHandler.

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;
using System.Windows.Input;

namespace MultiItems
{
    class MailItemViewModel
    {
        private readonly EventHandler<EventArgs> _onDeleteRequest;
        private readonly ICommand _deleteCommand;

        public MailItemViewModel(EventHandler<EventArgs> onDeleteRequest)
        {
            _onDeleteRequest = onDeleteRequest;
            _deleteCommand =
                new CommandHandler(() => { if (_onDeleteRequest != null) _onDeleteRequest(this, new EventArgs()); });
        }

        public string Name { get; set; }

        public string EMail { get; set; }

        public ICommand DeleteCommand
        {
            get { return _deleteCommand; }
        }
    }
}


Als Abschluss noch die notwendige Definition für den CommandHandler:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Windows.Input;

namespace MultiItems
{
    public class CommandHandler : ICommand
    {
        private readonly Action _action;
        private readonly bool _canExecute;

        public CommandHandler(Action action, bool canExecute = true)
        {
            _action = action;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            if(_action != null)
                _action();
        }
    }
}
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von »BlueCobold« (26.11.2013, 09:51)


TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

13

26.11.2013, 09:15

Traumhaft! So hätte ich es, mit etwas anderen Namen, implementiert. Trotzdem habe ich beim paar kritikpunkte:

1) Die Action sollte im Konstruktor eine ArgumentNullException für Action ausgeben, sonst hat man eine ungünstige Exception in Wpf im CommandHandler.
2) Standardmäßig notifizieren eigentlich alle Properties in den ViewModels. es gibt dafür eine ziemlich elegante Lösung, die man in BasicViewModel einbauen kann, so dass es trotzdem übersichtlich bleibt:

C#-Quelltext

1
2
3
4
5
6
protected void SetPropertyValue<T>(string propertyName, ref T backingField, T value)
{
    if (Equals(backingField, value) return;
    backingField = value;
    OnPropertyChanged(propertyName);
}


Im Property ist es dann sehr simpel:

C#-Quelltext

1
2
3
4
5
public string Name 
{
    get { return _name; }
    set { SetPropertyValue("Name", ref _name, value); }
}


3) MailItem sollte auch von BasicViewModel erben.
4) Der DataContext sollte in dem Sample trotzdem gesetzt werden, sonst versteht man nicht, wie man ViewModel und View zusammenführt.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

14

26.11.2013, 09:33

1) Erm, ja. Das wird dem geübtem Leser überlassen ;)
2) Wie du sehen wirst gibt es bei mir überhaupt kein OnPropertyChanged. Es muss nämlich nichts notified werden in dem Sample. Das wäre sozusagen der nächste Schritt.
3) Das gibt es gar nicht. ;) Hatte es erst drin, dann nicht gebraucht und wollte den Lesern nicht zu viel auf einmal zumuten.
4) Verstehe nicht, was du meinst. Der Context wird im MainWindow gesetzt per XAML. Der der Children über das DataTemplate im ItemsControl. Dass das so ist hatte ich dachte ich auch ausführlich erklärt.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

15

26.11.2013, 10:14

1-2) ok
3) Wenn es das eigentlich gar nicht gibt, dann ist es ok. Der Einfachheit halber finde ich das dann ok.
4) Achso, jetzt sehe ich das, clever! Auf die Idee bin ich noch nie gekommen. Funktioniert aber nur bei ViewModels mit Parameterlosen Konstruktor. Aber sehr charmante Lösung. Das wird glaube ich nur kein Anfänger verstehen. Dort finde ich es noch akzeptabel den DataContext im Einzeiler zu setzen.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

16

26.11.2013, 10:17

4) Der Vorteil dieses Wegs ist halt, dass man schon direkt im Designer im VisualStudio etwas sieht. Angenommen die Liste hätte von Anfang an 2 Einträge, würde Visual Studio auch diese beiden schon im Designer anzeigen und deren Properties abrufen. Das ist sehr sehr geil. Man sieht damit schon vor dem Programmstart das komplette Programm.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

17

26.11.2013, 10:22

Das ist ja interessant, das wusste ich nicht. Leider ist der Designer für mich prinzipiell gestorben, weil er bis heute für typisierte DataTemplates keine Designervorschau anbietet - mir ist zumindest nicht bekannt wie...

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

18

26.11.2013, 10:28

Hmm? Was meinst du?
Das einzelne Control wird er nicht anzeigen können, wenn du ihn als generische Klasse definierst, das mag wohl sein. Nie probiert, nie gehabt.
Das komplette Programm wird er aber anzeigen (oder ein Parent-Control inklusive dem generischen Child-Control), wenn der DataContext entsprechend gesetzt und arbeitsfähig ist.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

19

26.11.2013, 12:48

DataTemplates aus einem ResourceDictionary:

Quellcode

1
2
3
4
5
<ResourceDictionary>
    <DataTemplate DataType="{x:Type MyViewModelType}">
        <!-- Mein View -->
    </DataTemplate>
</ResourceDictionary>


Dass er das nicht anzeigen kann ist gut begründbar. Aber toll wäre es :) Besonders, wenn du sehr extensiv mit DataTemplates nach Typ arbeitest, was wir sehr gerne machen, ist ein Start der Anwendung unabdingbar. Ausser, du baust dir Dummy-Windows, die hart auf das Template verweisen.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

20

26.11.2013, 13:42

Ach das zeigt er nicht an?
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige