Laden...

Eine Klasse oder mehrere Klassen für ein WPF-Programm (Flugsimulator)?

Erstellt von stefanpc81 vor 3 Jahren Letzter Beitrag vor 3 Jahren 1.141 Views
S
stefanpc81 Themenstarter:in
24 Beiträge seit 2017
vor 3 Jahren
Eine Klasse oder mehrere Klassen für ein WPF-Programm (Flugsimulator)?

Hallo,

derzeit programmiere ich mit WPF und .NET Framework 4.7.2 meinen Flugsimulator (bzw. einen 2D-Cockpitsimulator). Bisher dachte ich, dass alle Felder/Methoden mit "public" aus anderen Klassen zugegriffen werden kann. Dies funktioniert leider nicht so wie gedacht, wie ich über das Internet erfahren habe. Ob es in meinem Fall besser wäre dieses Programm mit einer einzigen Klasse zu bauen, ist meine Hauptfrage an euch.

Nachfolgend Beispiele bzw. der Logikaufbau des Simulators:

  1. Prinzipiell soll die Methode Berechne0_5() mit dem DispatcherTimer (unter "public MainWindow()", ist das dort richtig aufgehoben?) alle halbe Sekunde von Anfang an fortlaufend vieles berechnen bzw. die Grafik füttern.
            DispatcherTimer timer0 = new DispatcherTimer();
            timer0.Interval = TimeSpan.FromSeconds(0.5);
            timer0.Tick += Berechne0_5;
            timer0.Start();
  1. Das gesamte Programm hat neben auszulesenden Dateien oder Arrays (funktioniert innerhalb der Klasse) sehr viele Variablen bzw. Felder, die besagen, in welchem Zustand sich die Drehzahl der Triebwerke, die Schalter, die 3 Stromquellen, die aktiven Waypoints (zur Flugroute) etc. befinden. Diese Informationen werden wiederum auf Monitoren oder Schaltern dargestellt (für den Piloten).
  2. Geografische Koordinaten (latitude/longitude) haben sowohl unter der Klasse FMC.lat[], FMC.lon[] als auch das Flugzeug selbst Flugzeug.lat, Flugzeug.lon
    Über eine Methode FMC.GetDiffAndGrad() mit 4 Parametern kann der Winkel sowie die Entfernung aus zwei lat/lon-Koordinaten errechnet werden. Leider funktioniert nur die Kombination aus zwei FMC-Koordinaten, nicht aber eine mit FMC-Koordinaten (Klasse) zusammen mit den Flugzeug-Koordinaten (andere Klasse). Es kommt die Fehlermeldung "Der Name "Flugzeug" ist im aktuellen Kontext nicht vorhanden."

Kurz ausgedrückt beeinflussen sich viele Variablen/Felder gegenseitig oder von verschiedenen Seiten aus. Gerade deswegen gehe ich davon aus, dass ich eine einzige große Klasse verwenden müsste.
Ich hoffe, ich konnte den Sachverhalt gut genug darstellen. Den Code zu posten würde wohl den Rahmen sprengen.
Viele Grüße

309 Beiträge seit 2020
vor 3 Jahren

Bisher dachte ich, dass alle Felder/Methoden mit "public" aus anderen Klassen zugegriffen werden kann. Dies funktioniert leider nicht so wie gedacht, wie ich über das Internet erfahren habe. Ob es in meinem Fall besser wäre dieses Programm mit einer einzigen Klasse zu bauen, ist meine Hauptfrage an euch.

Wieso funktioniert das nicht?

Ich weiß nicht was du vorhast, aber vielleicht wäre es besser z.B. eine Master Simulator-Klasse zu haben und darin unterschiedliche Unterklassen (Triebwerk, kp...). Dann kann jede Klasse den Zugriff auf andere Klassen bekommen, den sie brauchen.

Du solltest dir mal [Artikel] MVVM und DataBinding anschauen.

S
stefanpc81 Themenstarter:in
24 Beiträge seit 2017
vor 3 Jahren

Danke soweit. Habe noch Fragen zur Code-Platzierung:
Ist der nachfolgende Code in MainWindow.xaml.cs richtig platziert?
Wenn ich eine Master Klasse (class Airplane) schreiben würde, wo soll ich Airplane Flugzeug = new Airplane(); hinschreiben, auch in public MainWindow() ?

        public MainWindow()
        {
            InitializeComponent();

            EnglishFormat();

            DispatcherTimer timer0 = new DispatcherTimer();
            timer0.Interval = TimeSpan.FromSeconds(0.5);
            timer0.Tick += Berechne0_5;
            timer0.Start();

            DispatcherTimer timer1 = new DispatcherTimer();
            timer1.Interval = TimeSpan.FromSeconds(1.0);
            timer1.Tick += Berechne1;
            timer1.Start();
        }


        void Berechne0_5(object sender, EventArgs e)
        {
//Berechnungen alle 0.5 sec
        }

        void Berechne1(object sender, EventArgs e)
        {
//Berechnungen alle 1 sec
        }

        public void EnglishFormat()
        {
            var enculture = CultureInfo.GetCultureInfo("en-US");

            Dispatcher.Thread.CurrentCulture = enculture;
            Dispatcher.Thread.CurrentUICulture = enculture;
        }
5.657 Beiträge seit 2006
vor 3 Jahren

Normalerweise würde man zuerst die Business-Logik der Anwendung schreiben, und die ist komplett unabhängig von der verwendeten UI. In deinem Fall wären das Klassen, denen du Parameter übergeben kannst, und die dann die Daten über den gesamten Flugverlauf erzeugen.
Siehe dazu: [Artikel] Drei-Schichten-Architektur

Dann kannst du Tests dafür schreiben, um sicherzustellen, daß die Daten auch richtig berechnet werden. Dazu übergibst du die entsprechenden Parameter, und testest die Daten, die dort berechnet werden.
Siehe dazu: [Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio

Erst im letzten Schritt erstellst du dann die Benutzeroberfläche, damit der Benutzer die Parameter einstellen kann, und die berechneten Daten visualisiert werden. Wenn du WPF verwendest, dann halte dich an das MVVM-Pattern, das ist bei WPF der einfachste und komfortabelste Weg. Dazu mußt du dich aber vorher unbedingt damit auseinandersetzen, wie WPF funktioniert.
Siehe dazu der bereits verlinkte Artikel: [Artikel] MVVM und DataBinding

Weeks of programming can save you hours of planning

S
stefanpc81 Themenstarter:in
24 Beiträge seit 2017
vor 3 Jahren

Hallo,

zur Drei-Schichten-Architektur:
"Die Präsentationsschicht übernimmt die Aus- und Eingabe der Daten": Stimmt in meinem Fall nur bedingt. Die Methode "Berechne0_5" könnte bspw. mitten im Flug feststellen, dass der rechte Treibstofftank leer wird und das rechte Triebwerk ausgeht. In diesem Fall würden im Monitor EICAS Warnmeldungen erzeugt (Canvas wird verwendet) und im gleichen Monitor ändert sich die Grafik der Drehzahl (BitmapImage wird verwendet). Der Autopilot (den ich bisher schon mit Javascript programmiert habe) ist ebenso eine Option im Hintergrund das Flugzeug zu steuern (x- und y-Achse), welches wiederum Änderungen im ND-Monitor zur Folge hat.

Zum DataBinding:
Die Änderungen (BitmapImage, Canvas) machen für meinen Zweck wenig Sinn. Es gibt einen MFD-Monitor mit 4 verschiedenen Oberflächen (Canvas) und einen ND-Monitor mit 3 verschiedenen Oberflächen (Canvas). Die anderen 3 bzw. 2 sind dann nicht sichtbar.
Diese Oberflächen haben hier einen bestimmten Modus und wurden von mir bisher immer mit der if/else-Logik gesteuert, sodass immer nur der Teil des Modi gezeichnet und aktualisiert wird, welcher gerade aktiv ist. Das DataBinding würde, wenn ich es richtig verstanden habe, alle Werte auf Updates überprüfen obwohl sie gerade gar nicht sichtbar sind.
Auf dem EICAS-Monitor werden bis zu 11 Warnmeldungen untereinander (Canvas) dargestellt, die jeweils über eine for-Schleife ermittelt werden. Das sieht dann nach folgendem Muster aus:
warnung[1] = string Warnung 1
warnung[2] = string Warnung 2
warnung[3] = string Warnung 3
...
warnung[11] = string Warnung 6

Allerdings kann sich der Inhalt ändern, z.B. so

warnung[1] = string Warnung 2
warnung[2] = string Warnung 5
...
warnung[11] = string Warnung 7

Ich weiß, dass ich dies alles wunderbar über ein Array steuern und auf dem Canvas positionieren kann (wie ich es auch mit Javascript gemacht habe). Worauf ich hinaus will ist, dass es hierfür wohl schwierig bis unmöglich wird, ein DataBinding zu benutzen, da die Array-Werte "dynamisch" sind.

Ich habe versucht, ein paar Klassen in mein Programm einzubinden, erst getrennt, dann vererbt untereinander in einer Linie (also ohne Verzweigungen). Nachdem innerhalb der Klassen soweit alles lief, wollte ich auf Felder einer anderen Klasse die public sind, zugreifen, was aber nicht geht ("in diesem Kontext nicht vorhanden"). Ich glaube immer mehr, dass ich in meinem Fall (fast) alles in eine Klasse schreiben sollte. Die meisten Klassen, die ich bisher verwendet habe, besitzen sowieso nur ein einziges Objekt. Lediglich die beiden Triebwerke wären 2 Objekte einer Klasse.
Was auch nicht funktioniert, ist, wenn ich über die Benutzeroberfläche einen Button anklicke und in dieser Methode (public partial class MainWindow : Window in der Datei MainWindow.Xaml.cs) if-Abfragen stelle bezüglich bspw. FMC.eigenschaft_xy (FMC wäre ein Objekt einer bestimmten Klasse), dann ist "FMC in diesem Kontext nicht vorhanden".

Eure Tipps zur Drei-Schichten-Architektur und dem DataBinding sind gut gemeint und nach meinem Verständnis nach für die meisten Fälle wohl machbar und ein professioneller Weg.

Unabhängig zu diesen Tipps möchte ich nun wissen, ob es einen Weg gibt, direkt die FMC.eigenschaft_xy aus einer anderen Klasse heraus zu ändern oder ob es doch besser die gleiche Klasse sein muss/sollte?
Vielen Dank für eure Hilfe.

Edit: Tippfehler behoben

309 Beiträge seit 2020
vor 3 Jahren

zur Drei-Schichten-Architektur:
"Die Präsentationsschicht übernimmt die Aus- und Eingabe der Daten": Stimmt in meinem Fall nur bedingt. Die Methode "Berechne0_5" könnte bspw. mitten im Flug feststellen, dass der rechte Treibstofftank leer wird und das rechte Triebwerk ausgeht. In diesem Fall würden im Monitor EICAS Warnmeldungen erzeugt (Canvas wird verwendet) und im gleichen Monitor ändert sich die Grafik der Drehzahl (BitmapImage wird verwendet). Der Autopilot (den ich bisher schon mit Javascript programmiert habe) ist ebenso eine Option im Hintergrund das Flugzeug zu steuern (x- und y-Achse), welches wiederum Änderungen im ND-Monitor zur Folge hat.

Das ist dann von dir Falsch entworfen, wenn du in deiner Logik etwas abhängig von der Präsentation machst. Das wäre so wie wenn du in irgendeiner komplexen Methode auf "Form1.txtPreis.Text" zugreifen würdest. Das sollte alles komplett in deiner Klasse stattfinden. Der Präsentationsschicht wird dann das zur Verfügung gestellt was sie braucht und umgekehrt.

Nachdem innerhalb der Klassen soweit alles lief, wollte ich auf Felder einer anderen Klasse die public sind, zugreifen, was aber nicht geht ("in diesem Kontext nicht vorhanden"). Ich glaube immer mehr, dass ich in meinem Fall (fast) alles in eine Klasse schreiben sollte.

Das glaube ich nicht, vorallem wenn es so umfangreich ist.
Du kannst die Referenz einer Klasse einer anderen mitgeben und darauf zugreifen.

Pseudo-Beispiel:


public Simulator()
{
     auto = new Auto()
     werkstatt = new Werkstatt(auto)
}

5.657 Beiträge seit 2006
vor 3 Jahren

Stimmt in meinem Fall nur bedingt...machen für meinen Zweck wenig Sinn...Ich weiß, dass ich dies alles wunderbar über ein Array steuern und auf dem Canvas positionieren kann (wie ich es auch mit Javascript gemacht habe).

Aber in JavaScript hat es ja auch nicht so ganz super geklappt, wie du in deinen anderen Beiträgen geschrieben hast. Deshalb wolltest du ja auch auf C# umsteigen, um es besser zu machen. Wenn du jetzt als erstes damit anfängst, die gleichen Fehler wieder zu machen, dann wirst du in ein paar Monaten wieder dort sein, wo du heute bist. Und dann wirst du wieder denken, das sind Performanceprobleme. Aber das liegt nicht an Performanceproblemen, und auch nicht an JS oder C#, sondern an deiner grundlegend falschen Herangehensweise.

Worauf ich hinaus will ist, dass es hierfür wohl schwierig bis unmöglich wird, ein DataBinding zu benutzen, da die Array-Werte "dynamisch" sind.

Du solltest den Artikel auch mal lesen. Natürlich geht es um dynamische Daten, sonst bräuchte man ja kein DataBinding.

Ob es in meinem Fall besser wäre dieses Programm mit einer einzigen Klasse zu bauen, ist meine Hauptfrage an euch.

Schon die Frage ist natürlich völliger Blödsinn. Niemand würde auf die Idee kommen, eine Anwendung dieser Größenordnung in eine einzige Klasse zu schreiben.
Natürlich wird dich niemand davon abhalten, mit deinem Gefrickel weiterzumachen. Aber du darfst dann halt keine ernstgemeinte Hilfe hier im Forum erwarten, weil niemand so arbeiten würde.

Deine Frage ist das Äquivalent zu "Ich lerne seit letzter Woche Französisch, und jetzt möchte ich einen Roman schreiben. Verwende ich dazu mehrere Sätze, oder kann ich das Buch auch in einem Satz schreiben?". Wenn DAS das Niveau ist, auf dem du hier diskutieren möchtest, dann darfst du dich nicht wundern, wenn die meisten hier im Forum einfach kopfschüttelnd weiterblättern.

Siehe dazu auch der Hinweis von Palladin007 in Wie kann ich Linien bis zu einem Kreisrand auf einem Canvas anzeigen lassen?, der es ziemlich auf den Punkt bringt.

Weeks of programming can save you hours of planning

S
stefanpc81 Themenstarter:in
24 Beiträge seit 2017
vor 3 Jahren

Hallo,
ich verstehe. Ich war anfangs zu ungeduldig um längere Textpassagen zu lesen. Ich habe mich in der Zwischenzeit mit MVVM beschäftigt und MrSparkles Beispiel-Projekt heruntergeladen und es mir angeschaut.
Für meinen Simulator brauche ich später ein paar Canvas-Zeichenflächen, welche ich nicht mit den anderen WPF-Steuerelementen füllen möchte. Abgesehen evtl. von TextBlocks. Text und geometrische Elemente haben zur Laufzeit veränderbare X/Y-Koordinaten (Canvas.Left, Canvas.Top).
Im Internet habe ich nur wenige Lösungsansätze gefunden (welche ich teilweise auch nicht ganz verstanden habe), wie ich diese Elemente unter MVVM an das betreffende Canvas-Objekt binde bzw. bei Änderung eines mindestens einzelnen CanvasChild-Elements per DataBinding aktualisiere.
Mir wäre es persönlich lieber, die Objekte für die Canvas-Oberflächen in Klassen auszulagern und ich im XAML-Code z.B.

 <Canvas x:Name="canvas_eicas" HorizontalAlignment="Left" Height="400" Margin="200,100" VerticalAlignment="Top" Width="400" Background="Black" ClipToBounds="True"/>

schreiben würde. Ich würde gerne die „internal static class CanvasAssistant“, die ich unter
https://stackoverflow.com/questions/889825/is-it-possible-to-bind-a-canvass-children-property-in-xaml gefunden habe, als Teil der Lösung verwenden. Leider verstehe ich nicht, wie ich eine der definierten Klassen für die CanvasChild-Elemente diese Klasse „CanvasAssistant“ anspreche, bzw. ob ich die Methode SetBoundChildren(…) oder OnBoundChildrenChanged(…) verwenden muss.
Ein Beispielcode meiner Klasse „EICAS_canvas“ könnte so aussehen:
EDIT: Dass das Folgende über eine for-Schleife läuft, ist mir klar.

        public static void DrawMessages(object mess, int i, string text, string typ)
        {
            FontFamily arial = new FontFamily("Arial");
            TextBlock[] EICAS_message = (TextBlock[])mess;

            int y = (i * 15) + 2;
            int x = 200;
            if (typ == "advisory") { x = 208; }
            
            if (typ == "warning") { EICAS_message[i].Foreground = Brushes.Red; }
            else if (typ == "memo") { EICAS_message[i].Foreground = Brushes.White; }
            else { EICAS_message[i].Foreground = Brushes.Yellow; }
            
            EICAS_message[i].FontSize = 12;
            EICAS_message[i].FontFamily = arial;
            EICAS_message[i].TextAlignment = TextAlignment.Center;
            EICAS_message[i].Text = text;
            EICAS_message[i].Width = 200;

            Canvas.SetLeft(EICAS_message[i], x);
            Canvas.SetTop(EICAS_message[i], y);
        }

Dazu würde ein Array mit der Größe 11 (Parameter i) den Text (Parameter text) und den Typ (warning, caution, advisory oder memo) der Message übergeben.
Ich bitte um Hilfe.
Grüße

5.657 Beiträge seit 2006
vor 3 Jahren

Du kannst die Positions-Eigenschaften deiner ViewModels direkt an die Canvas.Left- bzw. Canvas.Top-Eigenschaften binden. Dazu benötigst du aber erst einmal ein ViewModel für die Objekte, die in der Canvas gezeichnet werden sollen.

Text in eine Canvas zu zeichnen ist aber irgendwie sinnlos, wenn er nicht beliebig platziert werden muß. Dafür gibt es geeignetere Controls, wie z.B. ListView.

Ich würde dir übrigens empfehlen, angesichts deines derzeitigen Kenntnisstands erst einmal die Grundlagen von C# aufzufrischen. Der Schritt von "kann ich ein Programm in einer einzigen Klasse schreiben" zu "wie kann ich ViewModels an eine Canvas binden" ist schon etwas extrem.

Weeks of programming can save you hours of planning

S
stefanpc81 Themenstarter:in
24 Beiträge seit 2017
vor 3 Jahren

Innerhalb der 11 TextBlocks können aber auch leere Zeilen (wie ich sie im Beitrag Eine Klasse oder mehrere Klassen für ein WPF-Programm (Flugsimulator)? weiter oben versucht habe zu erklären). Daher ist m.E. eine ListView keine Alternative dazu. Für andere Text-Elemente ist die Position auch mit der Zeit anders. Zumal Elemente der Canvas je nach Wert bestimmter Felder aus anderen Klassen sogar teilweise ausgeblendet werden sollen.
Als ich die "berühmte" Frage stellte, ob ich alles in eine Klasse packen kann, wusste ich noch nicht, dass man wie von User JimStark erläutert, eine "Referenz einer Klasse einer anderen mitgeben" kann.

Ohne MVVM auf ein Canvas zu zeichnen, allerdings nur innerhalb der Klasse und dem Konstruktor MainWindow, weiß ich aber wie das geht. Ich habe mir schon seit ein paar Wochen vor dem Erstellen dieses Threads Lernprojekte gemacht. Canvas zeichnen, Audio abspielen, Daten aus Dateien auslesen, scrollen des Fensters, welches ca. 2200pixel hoch ist waren Inhalt dieser. Verständnis wie man if/else-Zweige und for-Schleifen logisch aufbaut, habe ich prinzipiell keine Probleme, siehe Vorkenntnisse Javascript. - Was soll ich deiner Meinung nach insbesondere von den "Grundlagen" anschauen?

5.657 Beiträge seit 2006
vor 3 Jahren

Verständnis wie man if/else-Zweige und for-Schleifen logisch aufbaut, habe ich prinzipiell keine Probleme, siehe Vorkenntnisse Javascript.

Wenn du meinst, daß man mit if/else und for-Schleifen größere Projekte umsetzen kann, dann überschätzt du dich aber gewaltig.

Was soll ich deiner Meinung nach insbesondere von den "Grundlagen" anschauen?

Ich kann schlecht per Ferndiagnose einen Lehrplan für dich erstellen, aber hier ein paar Ideen: Vererbung, Klassendesign, Events, Enumerations, Linq, generische Typen... Du sagtest einmal, du hast ein C#-Buch. Arbeite das durch, schau dir ein paar Tuturials auf YouTube an und erstelle ein paar kleinere Übungsprojekte. Und dann beschäftige dich damit, wie WPF funktioniert.

Weeks of programming can save you hours of planning