Code Verbesserung für MVVM

  • WPF

Es gibt 37 Antworten in diesem Thema. Der letzte Beitrag () ist von MichaHo.

    Code Verbesserung für MVVM

    Hi,
    ich arbeite mich gerade in MVVM mit WPF ein.
    Das Grundprinzip hab ich glaub verstanden. (Die Business Logik muss ins ViewModel).
    Ich hab mir dazu ein älteres VB Projekt von mir vorgenommen und wollte dies nun auf MVVM umbiegen.
    Ich habe im Model 3 Klassen MConnection, MServer und MValue.
    Wollte Euch fragen, ob die 3 Klassen so in Ordnung sind für MVVM oder ob man da etwas verbessern/optimieren kann.

    Ein View, um Vorgaben zu speichern hab ich mir schon mal erstellt um zu testen, wie die Verbindung vom View zum ViewModel aussieht.
    Das sieht etwas komisch aus:

    C#-Quellcode

    1. this.DataContext = new SettingsViewModel(new MConnection(new MServer("MN-Temp", "10.21.89.246", 4000)));

    Bevor ich daher weiter mache, frag ich nach Eurer Meinung/Erfahrung/Tips.

    Danke Euch

    die Klasse MConnection:
    Spoiler anzeigen

    C#-Quellcode

    1. public class MConnection
    2. {
    3. MServer _Server { get; set; }
    4. NetworkStream _NetworkStream { get; set; }
    5. StreamReader _StreamReader { get; set; }
    6. TcpClient _TCPClient { get; set; }
    7. public bool IsConnected { get; set; }
    8. public MConnection(MServer server)
    9. {
    10. this._Server = server;
    11. }
    12. private void ConnectToServer()
    13. {
    14. _TCPClient = new TcpClient(_Server.Host, _Server.Port);
    15. _StreamReader = new StreamReader(_TCPClient.GetStream());
    16. _NetworkStream = _TCPClient.GetStream();
    17. _TCPClient.ReceiveTimeout = 2000;
    18. IsConnected = true;
    19. }
    20. private void DisconnectFromServer()
    21. {
    22. _TCPClient.Close();
    23. IsConnected = false;
    24. }
    25. public MValue GetMeasuredValue(string client)
    26. {
    27. ConnectToServer();
    28. client += Environment.NewLine;
    29. byte[] sendbyte = new ASCIIEncoding().GetBytes(client);
    30. _NetworkStream.Write(sendbyte,0,sendbyte.Length);
    31. _NetworkStream.Flush();
    32. string result = null;
    33. result = _StreamReader.ReadLine();
    34. DisconnectFromServer();
    35. bool isvalid = false;
    36. string tempstring = null;
    37. string[] temparray = result.Split(';');
    38. for (int i = 0; i < temparray.Length -1;)
    39. {
    40. if (temparray[i].StartsWith("valid"))
    41. if (temparray[i].Substring(6).Equals("1"))
    42. isvalid = true;
    43. if (temparray[i].StartsWith("value"))
    44. tempstring = temparray[i].Substring(6);
    45. }
    46. NumberFormatInfo numberformat = new NumberFormatInfo();
    47. numberformat.NumberDecimalSeparator = ".";
    48. double doubleresult = double.Parse(tempstring, numberformat);
    49. return new MValue(doubleresult, isvalid);
    50. }
    51. }


    Die Klasse MServer:
    Spoiler anzeigen

    C#-Quellcode

    1. public class MServer
    2. {
    3. string _ServerName;
    4. public string Host { get; }
    5. public int Port { get; }
    6. public MServer(string servername, string host, int port)
    7. {
    8. this._ServerName = servername;
    9. this.Host = host;
    10. this.Port = port;
    11. }
    12. }


    Die Klasse MValue:
    Spoiler anzeigen

    C#-Quellcode

    1. public class MValue
    2. {
    3. public double MeasuredValue { get; }
    4. public bool IsValid { get; }
    5. public MValue(double measurevalue, bool isvalid)
    6. {
    7. this.MeasuredValue = measurevalue;
    8. this.IsValid = isvalid;
    9. }
    10. }


    Verschoben. ~Trade
    "Hier könnte Ihre Werbung stehen..."

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

    Naja, das Model allein hat in der Hinsicht nicht all zu viel mit MVVM zu tun. Das ist ja nur das, was als Datenquelle für's ViewModel dahintersteht.
    Daher musst Du die Architektur da so aufbauen, wie sie Dir gefällt. Das ist ja erstmal unabhängig vom Pattern.

    Was das ViewModel angeht: Ist das immer diese eine Verbindung oder kann die sich auch mal ändern, sodass Du sie variabel über den Konstruktor mitgeben musst?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Hi @Trade
    Jepp, so hab ich es auch verstanden, das das Model quasi alles sein kann. In meinem Fall wird es vermutlich nur beim Entity Framwework oder Dataset only bleiben. Dies hier hatte ich zum üben mal angepackt.
    Also die Verbindung selbst ist immer die gleiche (ist eine Ethernet Box die als Temperaturserver konfiguriert ist).
    Also IP, Port ist immer die gleiche. der Servername ist ja rein für interne Zwecke.
    Was sich allerdings ändert ist der abgefragte Com Port. der geht von 1.1 bis 3.4 und representiert die 12 Lan Ports am Server wo die Temperaturfühler angeschloßen werden.
    Da dachte ich, das könnte man ja mit nem Enum machen, aber der Port muss quasi als String an den Server geschickt werden pcmeasure.com1.1 zum Beispiel, da wüsste ich nicht, wie ich das als Enum darstellen soll, da Enums ja integer werte haben.
    "Hier könnte Ihre Werbung stehen..."
    Das ViewModel kann dann eigentlich die Serververbindung selbst instanziieren. Woher wird der Port bezogen? Aus einer Benutzereingabe oder einer Datei?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    der Port ist ja auch immer der gleiche (Bei diesem Box Model), den könnte ich hart codet machen, aber um alles etwas flexibler zu gestalten hatte ich jetzt im SettingsView ne Textbox dafür vorgesehen.

    EDIT:
    ich glaub ich hau das alles in eine Klasse, dann wirds glaub einfacher.
    Ich muss ja sonst auch die Konstruktoren der einzelnen Klassen ändern, denn MConnection ruft ja MServer auf)
    "Hier könnte Ihre Werbung stehen..."
    Wenn Du eine TextBox (besser NumericUpDown (musste selber basteln oder ein Toolkit nutzen)) dafür vorsiehst, dann kannst Du eine Property im ViewModel dafür anlegen und an diese binden (über INotifyPropertyChanged und die Binding-MarkupExtension). Dann kann das ja intern im ViewModel an die Instanz Deiner Verbindung weitergegeben werden. Wie das ganze dann im Model verarbeitet werden soll, musst Du wissen.

    Edit: Wobei es mit einer TextBox sicher auch geht, wenn Du schaust, dass die Converter das vernünftig hinbekommen und nicht "Roulade mit Klößen" drinstehen kann. :>

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Ich hab da glaub auch nen Denkfehler grad :)
    Ich bin ja grad bei den Settings, die haben ja mit der eigentlichen Connection noch garnix zu tun.
    Die MainView muss ja die Connection aufrufen und erhält dann die Values der abgefragten ComPorts.
    Also ruf ich im SettingsViewModel die Server Klasse direkt auf und setze die Inhalte in Propertys (und speicher sie in ein XML).
    Und im MainView kann ich ja dann mit den gespeicherten Werten die Connection aufbauen.
    Dann brauch ich im ValueModel ja nur die eine Methode GetMeasuredValue aufrufen und feddsich...
    Oder?
    "Hier könnte Ihre Werbung stehen..."
    Achso, die Settings sind quasi die Daten für die Connection selber? Jetzt verstehe ich das glaub ich. Du möchtest die Settings laden und dann im View anzeigen, oder?

    MichaHo schrieb:

    Also ruf ich im SettingsViewModel die Server Klasse direkt auf und setze die Inhalte in Propertys
    Du meinst somit mit "Inhalt" die Verbindungsdaten etc.? Dann ja. Und diese Properties bindest Du an die View, sodass diese dort angezeigt werden.
    Ebenso gehst Du dann mit den ComPorts vor, die auch im ViewModel dann als Properties vorhanden sind.

    Und was genau meinst mit "ValueModel"? Ist das das Model, wo die ComPorts etc. drinstehen? Ja, dann rufst Du das halt im ViewModel auf und setzt die entsprechenden Eigenschaften.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Du hast es verstanden, obwohl ich mich umständlich ausgedrückt habe :)
    Also die Settings beinhalten die Daten für die Connection.
    Das hab ich soweit jetzt auch hin gefummelt.
    Ich stell mir das so vor.
    Das Programm wird gestartet, dann wird geprüft ob schon eine XML da ist und ob dort vernünftige Daten drinn stehn.
    Ist das der Fall, wird eine Verbindung aufgebaut und die die Daten des ComPorts abgefragt und in Labels angezeigt.

    Die Comports bau ich mit in die Settings, die ändern sich ja auch nicht so häufig. da mach ich mir ne List<String> und binde die an eine Listbox.
    Dann kann ich mit ForEach die List durchgehen und alle ComPorts abfragen und zurück geben.
    Wobei ich da wohl eher eine Genrische Liste nehme? um den Namen des Ports und den Value zurück zu geben?
    "Hier könnte Ihre Werbung stehen..."
    Das Prüfen könnte man über Commands lösen oder direkt im Konstruktor des ViewModels. Und wenn dort Daten drinstehen, dann kann ja direkt eine Verbindung aufgebaut werden. Sollen die Verbindungsdaten dann im View angezeigt werden und dann explizit über einen Button-Klick eine Verbindung aufgebaut werden? Jedenfalls dann kannst Du im Hintergrund im ViewModel die ComPort-Properties setzen (in der Verbindungsprozedur), die dann ans View gebunden sind und angezeigt werden. Oder in Deinem Fall halt eine Liste (am besten wäre wohl eine ​ObservableCollection<ComPort>, wenn Du dafür eine eigene Model-Klasse erstellen willst).

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    also die Verbindung soll am Besten direkt beim Starten aufgebaut werden.
    Die Connection Daten brauch ich nicht, die Verbindung wird ja auch direkt wieder geschloßen, wenn der Wert zurück gegeben wird.
    Über einen Timer würd ich dann alle 5 Minuten den Wert erneut abfragen, also müsste ich die Connection im Konstruktor des ViewModels aufrufen, welches für das Anzeigen der Werte bestimmt ist.
    Es wird später ja nur 2 Views geben, Settings und die Anzeige der Werte.
    (Wenn mans genau nimmt, könnte ich auch nur 1 View bauen zur Anzeige, die IP des Servers und den Port alst Const Variable hinterlegen und die beiden Ports die Abgefragt werden, kenn ich ja auch. dann wäre es wahrscheinlich für den Anfang etwas einfacher)
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    Über einen Timer würd ich dann alle 5 Minuten den Wert erneut abfragen, also müsste ich die Connection im Konstruktor des ViewModels aufrufen, welches für das Anzeigen der Werte bestimmt ist.
    Vorsicht: Dann würde ich das in eine Prozedur auslagern, die Du dann in diesem Intervall immer wieder ausführen kannst. Das ViewModel sollte ja so bleiben und nicht immer neu instanziiert werden. Am Besten wäre daher wohl sogar ein Singleton.

    Kommst Du mit den bisherigen Tipps soweit zurecht? Kannst ja mal versuchen, das so umzusetzen. Ansonsten, wenn was ist, fragst halt einfach nochmal. ;)

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Ja, bisher komm ich noch mit, bis auf 2 Punkte :)
    Singleton und ObservableCollection<ComPort>

    Das ViewModel für die Anzeige ruft ja die GetMeasuredValue Methode der Connection Klasse auf, da kann ich ja nen Command für her nehmen...
    Oder gar ein Delegate?
    "Hier könnte Ihre Werbung stehen..."
    Singleton ist einfach ein Pattern, das dafür sorgt, das von einer Klasse nur eine Instanz erstellt werden kann.
    Und die ObservableCollection ist wie eine Art Liste, die automatisiert Events feuert, wenn sich die Inhalte ändern. ​ComPort war einfach eine Klasse, die diesen repräsentiert, weil Du ja "Name" und "Value" genannt hast.

    Warum genau einen Command? Also wie genau willst Du den einsetzen?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    naja, ich möchte ja erreichen, das die Oberfläche alle 5 Minuten die aktuellen Werte anzeigt.
    Dachte ich könnte das Command zeitgesteuert aufrufen.
    Weil irgendwie muss da ja etwas gestartet werden, was irgendeine Methode abruft um die Werte zu holen.
    Den Namen brauch ich für die Anzeige, damit der/die User wissen, welche Temperatur von welchem Raum grad angezeigt wird.
    Also zum Beispiel Serverraum ist pcmeasure.com1.1 und Elektroraum ist pcmeasure.com3.3

    Das mit der observablecollection muss ich mir mal genauer anschauen
    "Hier könnte Ihre Werbung stehen..."
    Aber das ist ja nicht direkt vom View abhängig. Da reicht eine einfache Methode, die halt mit einem Timer immer wieder aufgerufen wird. Da brauchst Du kein Command.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Stimmt, da hast recht. Das View kann ja auch was anderes sein, zbsp. Ne android app, da könnte man ja mit nem refresh button arbeiten.
    Ich schmeiss jetzt erstmal den Grill an, family hat hunger :)
    morgen in der firma teste ich mal das bisherige und meld mich dann wieder.
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    C#-Quellcode

    1. this.DataContext = new SettingsViewModel(new MConnection(new MServer("MN-Temp", "10.21.89.246", 4000)));
    this.DataContext ist immer schlecht - den Datacontext sollteste im Xaml setzen - nicht im CodeBehind.
    Grundlagen - MVVM-Anwendungs-Struktur kennste schon?
    Da drin wird auch weiterverlinkt zu wie sichs im Xaml arbeitet, wenn der DataContext Xaml-gesetzt ist: Grundlagen - MVVM: "Binding-Picking" im Xaml-Editor
    Kannst im XAML nur setzten, wenn du einen Parameterlosen Konstruktor hast.
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell

    ErfinderDesRades schrieb:



    this.DataContext ist immer schlecht - den Datacontext sollteste im Xaml setzen - nicht im CodeBehind.
    Grundlagen - MVVM-Anwendungs-Struktur kennste schon?
    Da drin wird auch weiterverlinkt zu wie sichs im Xaml arbeitet, wenn der DataContext Xaml-gesetzt ist: Grundlagen - MVVM: "Binding-Picking" im Xaml-Editor


    Danke @ErfinderDesRades die Links kenn ich noch nicht, schaue ich mir gleich durch.
    Das man den DataContext auch im Xaml setzen kann, hab ich gestern gesehen :)

    Radinator schrieb:

    Kannst im XAML nur setzten, wenn du einen Parameterlosen Konstruktor hast.


    Danke @Radinator .....Den hab ich :)
    "Hier könnte Ihre Werbung stehen..."