Laden...

MVVM Starthilfe

Erstellt von Net_Hans vor 12 Jahren Letzter Beitrag vor 11 Jahren 7.411 Views
N
Net_Hans Themenstarter:in
70 Beiträge seit 2010
vor 12 Jahren
MVVM Starthilfe

Hallo,

ich habe mich nach langen dazu hinreißen lassen, mir das MVVM anzusehen. Leider Blick ich noch nicht druch. Im Netz hab ich viele Tutorials gefunden, unter anderem dies hier:
MVVM Pattern in WPF: A Simple Tutorial for Absolute Beginners

Das mit dem Button ist auch schön und gut. Wenn man sich an das Tutorial hält, kommt man auch zu Ziel. Aber ich habe versucht im zweiten Schritt einfach von meiner Model-Klasse ein String zurück zu geben, der die aktuelle Zeit enthält.

Folgenden Code habe ich versucht, aber ich weiß nicht wie ich dem View das ModelView bekannt machen kann. Könnte mir bitte jemand einen Tip geben?

Model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace WpfApplication1
{
    class Model : INotifyPropertyChanged
    {
        private System.Timers.Timer aTimer;
        private DateTime m_now;

        public DateTime Zeit
        {
            get
            {
                return m_now;
            }
            private set
            {
                m_now = value;
                OnPropertyChanged("Zeit");
            }
        }

        public Model()
        {
            aTimer = new System.Timers.Timer();
            aTimer.Elapsed += new System.Timers.ElapsedEventHandler(aTimer_Elapsed);
            aTimer.Interval = 1000;
            aTimer.Enabled = true;
        }

        void aTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Zeit = DateTime.Now; 
        }


        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }
}

ModelView:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.ComponentModel;

namespace WpfApplication1
{

    class ModelView
    {
        private Model mModel;

        public string Zeit
        {
            get { return mModel.Zeit.ToString(); }
        }

        public ModelView()
        {
            mModel = new Model();
        }
    }
}

View:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="100,138,0,0" Name="textBlock1" Text="{Binding Zeit}"  VerticalAlignment="Top" Width="279" />
    </Grid>
</Window>

Danke für eure Hilfe
mfG Hans

T
50 Beiträge seit 2010
vor 12 Jahren

Hallo Hans,

durch dieses Thema habe ich mich auch schon gekämpft. Vorab mal ein Buchtipp von mir:

"Building Enterprise Applications with Windows Presentation Foundation and the MVVM Model View ViewModel Pattern" von Raffaele Garofalo.

Das Buch geht nicht nur auf MVVM ein, sondern zeigt auch sehr schön, wie man eine Anwendung strukturieren kann. Es ist eines der wenigen Bücher, die es zu diesem Thema gibt. Leider.

Zu Deinem Problem zwei Dinge, die mir auffallen.1.Du musst im ViewModel INotifyPropertyChanged implementieren, damit die View mitbekommt, dass es im ViewModel Änderungen gibt.
In Deinem Fall könntest Du die Logik aus Deinem Model übernehmen.

1.Du musst das ViewModel dem DataContext der View zuweisen. Im einfachsten Fall wäre das im Konstruktor der View.

Gruss,
Ronny

N
Net_Hans Themenstarter:in
70 Beiträge seit 2010
vor 12 Jahren

Hallo teebeast,

ich habe mein Programm jetzt um die Teile ergängt, die du angesprochen hast. Jetzt geht das auch erst einmal. Aber ich habe das Gefühl, das diese Umsetztung nicht die 100% richtige ist.
Momentan habe ich das ModelView dem View in der Init Routine bekannt gemacht. Laut Google kann man das aber auch direkt im XAML. Aber das funkt bei mir nicht.

So sieht mein Code aus

Model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace WpfApplication1
{
    class Model : INotifyPropertyChanged
    {
        private System.Timers.Timer aTimer;
        private string m_now;

        public string Zeit
        {
            get
            {
                return m_now;
            }
            private set
            {
                m_now = value;
                OnPropertyChanged("Zeit");
            }
        }

        public Model()
        {
            aTimer = new System.Timers.Timer();
            aTimer.Elapsed += new System.Timers.ElapsedEventHandler(aTimer_Elapsed);
            aTimer.Interval = 1000;
            aTimer.Enabled = true;
        }

        void aTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Zeit = DateTime.Now.ToString(); 
        }


        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }
}

ModelView

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.ComponentModel;

namespace WpfApplication1
{

    class ModelView : INotifyPropertyChanged
    {
        private Model mModel;

        public string Zeit
        {
            get { return mModel.Zeit; }
        }

        public ModelView()
        {
            mModel = new Model();
            mModel.PropertyChanged += new PropertyChangedEventHandler(mModel_PropertyChanged);
        }

        void mModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChanged(e.PropertyName);
        }

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }
}

Code-Behinde der XAML

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ModelView();
        }
    }
}

XAML

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="100,138,0,0" Name="textBlock1" Text="{Binding Zeit}"  VerticalAlignment="Top" Width="279" />
    </Grid>
</Window>

Könnt ihr mit sagen, was ich daran noch verbessern kann/muss, bevor ich mir schon zu Begin mit MVVM was falsches angewöhne?

Danke

5.742 Beiträge seit 2007
vor 12 Jahren

Hallo Net_Hans,

dein Beispiel ist so simpel, dass die Aufteilung in Model / ViewModel schlichtweg nicht sinnvoll ist.
Was bei dir "Model" ist, kannst du quasi zum ViewModel machen und hast dadurch schon einmal ein einfaches MVVM-Beispiel 😉

Das ViewModel (und evtl. das ModelViewModel) kommen erst bei komplexeren Anwendungen richtig zum Tragen.
In Kürze: Das ViewModel übernimmt sämtliche Transformationen, Aggregationen und Formatierungen der Model-Daten. Somit könnte es z.B. Model-Daten hierarchisch gliedern, Hierarchien auflösen, neu gruppieren etc.

BTW: Deine INotifyPropertyChanged Implementierung ist suboptimal; siehe [Artikel] INotifyPropertyChanged implementieren (zumindest die Prüfung, ob sich der Wert wirklich geändert hat, solltest du einbauen).

BTW2: Verwende besser gleich einen DispatcherTimer - System.Timers.Timer ist für UI Geschichten eher ungeeignet.

S
357 Beiträge seit 2007
vor 11 Jahren

Ich checke auch zur Zeit das MVVM-Muster anhand eines einfachen Beispiels - eben genau auch mit einer Uhr:

dafür habe ich das DateTimeModel:


    class DateTimeModel : ViewModelBase {
        DateTime _now;

        public DateTimeModel() {
            DispatcherTimer timer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate {
                Now = DateTime.Now;
            }, Dispatcher.CurrentDispatcher);
            timer.Start();
        }

        public DateTime Now {
            get {
                return _now;
            }
            set {
                if (_now != value) {
                    _now = value;
                    OnPropertyChanged("Now");
                }
            }
        }
    }

ich update hiermit ein Label in einem Window (MyView). MyView besitzt ein ViewModel 'MyViewModel'.

Zuvor muss 'Now' aus dem DateTimeModel noch formatiert werden (ToString()). Soll hier noch ein ViewModel (laut MVVM) für das DateTimeModel werden, oder übernimmt das 'MyViewModel' ?

Was mich noch irritiert ist die Tatsache, dass das DateTimeModel 'INotifyPropertyChanged' (über ViewModelBase) implementiert. MyViewModel muss dies für das Feld 'Now' (formattiert) natürlich auch machen, um die View zu informieren. Das MyViewModel muss aber irgendview auf den Wert des Propertys kommen - geht natürlich, aber hier verletze ich meiner Meinung nach das MVVM-Muster ... :


    public class MyViewModel : ViewModelBase {
        DateTimeModel _dateTimeM;
        string _now;

        #region Constructor
        
        public MyViewModel() {
            _dateTimeM = new DateTimeModel();
            _dateTimeM.PropertyChanged += _dateTimeM_PropertyChanged;
        }

        void _dateTimeM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
            var property = sender.GetType().GetProperty(e.PropertyName);
            Now = ((DateTime)property.GetValue(sender, null)).ToString("dd'/'MM'/'yy HH:mm:ss");
        }

        public string Now {
            get {
                return _now;
            }
            set {
                if (_now != value) {
                    _now = value;
                    OnPropertyChanged("Now");
                }
            }
        }
    }

Alternativ könnte ich das DateTimeModel mit einem Event ausstatten, damit ich den Wert von Now direkt erhalte:


class DateTimeModel {
        public delegate void TimeChangedHandler(DateTime dt);
        public event TimeChangedHandler TimeChanged;

        void OnTimeChanged(DateTime dt) {
            if (TimeChanged != null)
                TimeChanged(dt);
        }

        public DateTimeModel() {
            DispatcherTimer timer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate {
                OnTimeChanged(DateTime.Now);
            }, Dispatcher.CurrentDispatcher);
            timer.Start();
        }

        }
    }
5.657 Beiträge seit 2006
vor 11 Jahren

Hi snupi,

MVVM heißt Model - View - ViewModel, und diese drei Teile sind unabhängig voneinander. Du leitest aber dein Model vom ViewModelBase ab, und bringst diese Trennung damit durcheinander.

Christian

Weeks of programming can save you hours of planning

S
357 Beiträge seit 2007
vor 11 Jahren

ok, habe vergessen zu erwähnen, dass ich im ViewModelBase nur das Interface 'INotifyPropertyChanged' implementiere:


    public abstract class ViewModelBase : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName) {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null) {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Wollte mir hier im Model nur Schreibarbeit ersparen...

5.657 Beiträge seit 2006
vor 11 Jahren

Klar, letztendlich muß dein ViewModel irgendwie darüber informiert werden, wenn dein Model "eigenständig" seine Propertys ändert. Das kannst du entweder über das INotifyPropertyChanged-Interface machen oder über ein eigenes Event, das ist letztendich deine Entscheidung.

Christian

Weeks of programming can save you hours of planning

S
357 Beiträge seit 2007
vor 11 Jahren

Ich weiss, das Beispiel ist zu simple für ein MVVM, da ich den Zeitgeber direkt ins ViewModel geben kann - ich will aber nur mal grundsätzlich alle Teile eines MVVM-Musters behandeln.

Was mich hier nur stört, ist die Tatsache, dass ich im ViewModel 'umständlich' auf den geänderten Wert des Property's kommen muss. Wenn mein Model zb. mehrere Propertys hätte, bräuchte ich im ViewModel schon einen Switch im Handler (_dateTimeM_PropertyChanged()).

In einigen Lektüren habe ich auch gelesen, dass auch das Model ein ViewModel besitzen sollte ... ?

5.299 Beiträge seit 2008
vor 11 Jahren

macht euch nicht verrückt mit dem Versuch, Pattern umzusetzen, wenn man sie nicht recht versteht.

Das nicht recht verstehen kommt oft gar nicht daher, dass man zu dumm ist, sondern weil der Pattern einfach nicht auf die Situation passt.

Für dieses Primitiv-Beispiel ists unpassend, Model und ViewModel auseinanderzudröseln - das Model soll sein PropertyChanged senden, wenns die Property Changed, und daran kann Xaml gebunden werden, und gut.

Dann ist der Aufbau logisch, nachvollziehbar und (überaus) plausibel.

Wenn du da jetzt aber mit Gewalt ein MVVM reinzudrücken versuchst, und womöglich noch iwas mit einem "Model, was ein ViewModel besitzt", weil du das mal iwo gehört hast, da wird man hinterher nix mehr von verstehen können, und ausserdem besteht die Gefahr, dass dus einfach falsch machst (es aber aufgrund des primitiven Kontexts trotzdem funktioniert).

Also die Gefahr besteht, dass du da einen sinnlosen Workaround um nichts baust, und wenn wirklich ein Context auftritt, wo die höhere Komplexität tatsächlich erforderlich ist, dann versuchstes mit deim komischen Workaround, und erst dann mag sich erweisen, dasser gar nie richtig implementiert war.

Der frühe Apfel fängt den Wurm.

S
357 Beiträge seit 2007
vor 11 Jahren

Für dieses Primitiv-Beispiel ists unpassend, Model und ViewModel auseinanderzudröseln - das Model soll sein PropertyChanged senden, wenns die Property Changed, und daran kann Xaml gebunden werden, und gut.

Ich hätte einfach den Zeitgeber ins ViewModel gegeben und dieses dann an XAML gebunden ...

Update: Der Zeitgeber ist nun ein eigenes Model, da ich diesen um einige Funktionalität erweiter habe und in anderen Projekten noch verwende.
Der Zeitgeber hat nun ein TimeChanged-Event. Der Handler dafür im ViewModel setzt das entsprechende Property für die View (inkl. Formatierung der Zeit).