Laden...

Aus einem Event bezogene Daten in bestimmten Intervallen verarbeiten

Erstellt von PierreDole vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.275 Views
P
PierreDole Themenstarter:in
74 Beiträge seit 2017
vor 4 Jahren
Aus einem Event bezogene Daten in bestimmten Intervallen verarbeiten

Moin, ich möchte Daten, die ich von einem Event bekomme, in anderen Intervallen verarbeiten als das Event ausgelöst wird. Das Event feuert 60 Mal in der Sekunde. Das ist fix und kommt von einem Framework. Da es sich um sehr viele Daten handelt, und mein Programm die CPU mittlerweile zu 10% auslastet, suche ich nach Möglichkeiten alles etwas performanter zu gestalten.
Einige Daten könnte ich z.B. 10 Mal die Sekunde verarbeiten, andere sogar nur einmal in der Sekunde. Ich habe nur leider keine Idee, wie ich das anstelle. Hat jemand eine Idee?

T
2.219 Beiträge seit 2008
vor 4 Jahren

Je nachdem was du für Daten hast, müsstest du schauen ob du diese z.B. in einer DB speicherst und dann in einem nachgelagerten Prozess abarbeitest.

Je nachdem wie du die Daten verarbeitest, könnte man den Prozess optimieren um nicht soviel CPU zu ziehen.
Aber dazu müsstest du deinen Prozess prüfen und bei bestimmten Problemen kann dir hier bestimmt geholfen werden 😃

Nachtrag:
Ebenfalls solltest du schauen, falls noch nicht vorhanden, in wie weit du deine Verarbeitung mit Tasks auf mehrere Kerne verteilen kannst.
Je nach Kernanzahl kannst du damit auch den Durchsatz deiner Anwendung nochmal optimieren.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.806 Beiträge seit 2008
vor 4 Jahren

Den Vorschlag mit einer Datenbank wird vermutlich das System verlangsamen - das ist keine gute Idee.
Eine Datenbank im Sinne einer SQL Datenbank ist nicht für das Zwischenspeichern von Nachrichten optimiert.
Wenn dann eine ordentliche Message Queue, die für so ein Szenario gedacht ist.

Damit man die Frage überhaupt beantworten kann:

  • Von welchen Events sprichst Du? Events im Sinne von Event Sourcing oder von .NET Events?
  • Wie verarbeitest Du aktuell die Nachrichten? Wie bzw. woher kommen die Nachrichten rein?

Wenn wir von In Process Messaging reden - also einfache .NET Events - so kannst Du prinzipiell auf die TPL und DataFlow setzen.
Ist im Grunde nichts anderes als ein Producer Consumer Pattern, wobei der Event der Producer darstellt und die Verarbeitung die Consumer.
Es ist damit sehr einfach eine Parallelisierung mit vielen Consumern umzusetzen und so sehr performant Events zu verarbeiten; skaliert aber nur auf einer einzigen Maschine - eben In Process.

Wenn Du jedoch eine Mehrinstanz-Skalierung benötigst und die Events im Sinne von Event Sourcing gemeint sind, dann kann man das ganz anders aufziehen.
Damit könnte man auch einen Puffer umsetzen, falls so viele Nachrichten eintrudeln, die nicht mehr von den vorhandenen Instanzen ohne weiteres abgearbeitet werden können.

Im Endeffekt müsstest aber mehr Infos liefern woher die Daten kommen und wie sie verarbeitet werden bzw. was das Ziel ist.
Oft ist verarbeiten schneller als Filtern (vor allem wenn dazu Datenbankabfragen notwendig werden).

60 Events/Sekunde ist jedoch weder für ein In Process Handling noch für ein größeres System viel.
Mein derzeit "leistungsfähigstes IoT Projekt" verarbeitet zur Spitzenzeit fast 1 Mio Nachrichten pro Minute 😃

Message Queues wie RabbitMQ und ActiveMQ leisten ca. 50.000 Nachrichten pro Sekunde pro üblicher Core - nur mal so als Hausnummer 😉

P
PierreDole Themenstarter:in
74 Beiträge seit 2017
vor 4 Jahren

Ja, bei meinem Code gibt es bestimmt viele andere Stellen, die optimiert werden könnten und müssten. Ich setzt halt da an, wo ich einen Ansatz sehe. Bloße Gewißheit reicht leider nicht aus. 😃

Es handelt sich um ein .NET Event. Das Ganze ist ein iRacing SDK, bzw ein Wrapper, der auf diesem SDK basiert. Ich bekomme (fast) alle Informationen, die auf der Rennstrecke erzeugt werden. Woran es hackt, ist der Spotter, bzw, die Abstandsmessung zwischen mir und den anderen Fahrzeugen. Ich bekomme von dem Event den prozentuellen Abstand aller Fahrzeuge von der Start/Ziel-Linie aus gesehen und berechne daraus den Abstand der anderen Fahrzeuge zu meinem Fahrzeug. Das Ding ist, das Fahrzeug ist keine vier Meter lang, die Strecke aber 6.4km. Vier Meter in dem prozentuellen Wert sind fast gar nichts (auch, wenn der prozentuelle Wert sechsstellig ist).
Wenn ich mir alle berechneten Abstände in einer Liste live anzeigen lasse, hacken die manchmal, quasi sie laggen. Nicht gut, wenn es um jede Tausendstel geht (naja, eigentlich um 1/60 Sekunde). Ich schiebe das jetzt auf meinen nicht performanten Programmierstil. Aber wenn Millionen Daten in einer Sekunde verarbeitet werden können, dann frage ich mich jetzt, woran es sonst liegen könnte.

T
2.219 Beiträge seit 2008
vor 4 Jahren

@PierreDole
Wie sieht den bisheriger dein Code aus?
Wenn du hier schon bei der Verarbeitung der Ergebnise ein Problem hast, was dich gerade bremst, dann müsstest du den groben Ablauf mal anzeigen.
Vielleicht gibt es einen besseren Ansatz in der Auswertung, den du aktuell nur noch nicht kennst 😃
Aber dazu wäre eben ein wenig Code nötig mit der Stelle an der es Probleme gibt.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

P
PierreDole Themenstarter:in
74 Beiträge seit 2017
vor 4 Jahren

Quellcode anzeigen wollte ich eigentlich vermeiden, da ihr dann wohl kreischend wegrennt. 😄

Zuerst speichere ich einige Daten in Properties, da ich nicht weiß, was der Wrapper im Hintergrund tut und ich eveltuelle Neuberechnungen vermeiden will. In diesen Properties (car, player, driver, track und session) passiert nichts weiter.

Ok, hier das gefeuerte Event:


private void OnTelemetryUpdate(object sender, SdkWrapper.TelemetryUpdatedEventArgs e)
        {
            car.Speed = e.TelemetryInfo.Speed.Value;
            car.Engine.CurrentRPM = (int)e.TelemetryInfo.RPM.Value;
            car.Gearbox.CurrentGear = e.TelemetryInfo.Gear.Value;

            car.Pedals.ThrottleLevel = e.TelemetryInfo.Throttle.Value;
            car.Pedals.BrakeLevel = e.TelemetryInfo.Brake.Value;
            car.Pedals.ClutchLevel = e.TelemetryInfo.Clutch.Value;

            car.Tank.FuelLevel = e.TelemetryInfo.FuelLevel.Value;
            
            session.PlayerPosition = wrapper.GetTelemetryValue<int>("PlayerCarPosition").Value;
            session.PlayerClassPosition = wrapper.GetTelemetryValue<int>("PlayerCarClassPosition").Value;
            session.PlayerLapCount = e.TelemetryInfo.Lap.Value;
            session.PlayerLapDistPct = e.TelemetryInfo.CarIdxLapDistPct.Value[(int)car.Id];

            player.LapDistance = e.TelemetryInfo.CarIdxLapDistPct.Value[(int)player.CarId];
            player.EstimatedTime = e.TelemetryInfo.CarIdxEstTime.Value[(int)player.CarId];
            player.IsOnPitRad = e.TelemetryInfo.CarIdxOnPitRoad.Value[(int)player.CarId];
            player.IsOnTrack = e.TelemetryInfo.IsOnTrack.Value;

            // tmp - zeige Abstände von den Fahrern zu mir
            telemetryTextBox.Text = "";
            float playerLapDistance = player.LapDistance * track.Length;
            foreach(Driver driver in session.Drivers)
            {
                driver.LapDistance = e.TelemetryInfo.CarIdxLapDistPct.Value[(int)driver.CarId];
                driver.EstimatedTime = e.TelemetryInfo.CarIdxEstTime.Value[(int)driver.CarId];
                driver.IsOnPitRad = e.TelemetryInfo.CarIdxOnPitRoad.Value[(int)driver.CarId];

                if(driver.LapDistance != -1)
                    telemetryTextBox.Text += "pd: " + Math.Round(playerLapDistance) + "/" + Math.Round(driver.LapDistance * track.Length) + " : " + Math.Round(Math.Abs(playerLapDistance - (driver.LapDistance * track.Length))) + "\n";
            }
            
            

            if(player.IsOnTrack)
            {
                this.RPMControl();
                this.GearControl();
                this.PedalControl();
                this.DisplayControl();
                this.SpotterControl();
                kmhLabel.Content = Math.Round(car.Speed * 3.6);

                // show Hud Panel if on track
                if(hudPanel.Visibility == Visibility.Hidden)
                    hudPanel.Visibility = Visibility.Visible;

                //  dient der Berechnung des Verbrauchs pro Runde
                if(shouldUpdateFuelLevel)
                {
                    fuelLevelNewLap = e.TelemetryInfo.FuelLevel.Value;
                    shouldUpdateFuelLevel = false;
                }
            }
            else
            {
                // hide Hud Panel if not on track
                if(hudPanel.Visibility == Visibility.Visible)
                    hudPanel.Visibility = Visibility.Hidden;

                shouldUpdateFuelLevel = true;
            }
        }

Um es vollständig zu machen, hier noch die Control-Methoden:

#region Hud Control

        int currentGear = 0;
        private void RPMControl()
        {
            currentGear = car.Gearbox.CurrentGear == -1 ? 0 : car.Gearbox.CurrentGear;

            rpmLabel.Content = Math.Round((float)car.Engine.CurrentRPM / 1000, 1);

            rpmLED01.Fill = (car.Engine.CurrentRPM > (70 * car.Engine.ShiftRPM / 100)) ? this.rpmLedOnColor : new SolidColorBrush(this.rpmLedOffColor);
            rpmLED02.Fill = (car.Engine.CurrentRPM > (75 * car.Engine.ShiftRPM / 100)) ? this.rpmLedOnColor : new SolidColorBrush(this.rpmLedOffColor);
            rpmLED03.Fill = (car.Engine.CurrentRPM > (80 * car.Engine.ShiftRPM / 100)) ? this.rpmLedOnColor : new SolidColorBrush(this.rpmLedOffColor);
            rpmLED04.Fill = (car.Engine.CurrentRPM > (85 * car.Engine.ShiftRPM / 100)) ? this.rpmLedOnColor : new SolidColorBrush(this.rpmLedOffColor);
            rpmLED05.Fill = (car.Engine.CurrentRPM > (90 * car.Engine.ShiftRPM / 100)) ? this.rpmLedOnColor : new SolidColorBrush(this.rpmLedOffColor);
            rpmLED06.Fill = (car.Engine.CurrentRPM > (95 * car.Engine.ShiftRPM / 100)) ? this.rpmLedOnColor : new SolidColorBrush(this.rpmLedOffColor);

            if(car.Engine.CurrentRPM >= car.Engine.ShiftRPM && car.Engine.CurrentRPM < car.Engine.BlinkRPM)
            {
                rpmLED07.Fill = new SolidColorBrush(this.rpmLedShiftColor);
                rpmLED08.Fill = new SolidColorBrush(this.rpmLedShiftColor);
                rpmLED09.Fill = new SolidColorBrush(this.rpmLedShiftColor);
                rpmShiftFlash.Fill = rpmLedShiftFlashColor;
            }
            else if(car.Engine.CurrentRPM > car.Engine.BlinkRPM)
            {
                rpmLED07.Fill = new SolidColorBrush(this.rpmLedMaxColor);
                rpmLED08.Fill = new SolidColorBrush(this.rpmLedMaxColor);
                rpmLED09.Fill = new SolidColorBrush(this.rpmLedMaxColor);
                rpmShiftFlash.Fill = rpmLedShiftFlashColor;
            }
            else if(car.Gearbox.CurrentGear > 1 && car.Engine.CurrentRPM < car.Engine.ShiftDownSafeRPM[currentGear] && car.Engine.CurrentRPM > car.Engine.ShiftDownLastRPM[currentGear])
            {
                rpmShiftFlash.Fill = new SolidColorBrush(Color.FromRgb(255, 191, 0));
            }
            else if(car.Gearbox.CurrentGear > 1 && car.Engine.CurrentRPM < car.Engine.ShiftDownLastRPM[currentGear])
            {
                rpmShiftFlash.Fill = new SolidColorBrush(Color.FromRgb(166, 124, 0));
            }
            else
            {
                rpmLED07.Fill = new SolidColorBrush(this.rpmLedOffColor);
                rpmLED08.Fill = new SolidColorBrush(this.rpmLedOffColor);
                rpmLED09.Fill = new SolidColorBrush(this.rpmLedOffColor);
                rpmShiftFlash.Fill = Brushes.Transparent;
            }

        }

        private void GearControl()
        {
            if(gearLabel.Content.ToString() != car.Gearbox.CurrentGear.ToString())
            {
                if(car.Gearbox.CurrentGear == -1)
                    gearLabel.Content = "R";
                else if(car.Gearbox.CurrentGear == 0)
                    gearLabel.Content = "N";
                else
                    gearLabel.Content = car.Gearbox.CurrentGear.ToString();
            }
        }

        // Pedalwerte gehen von 0 bis 1
        private void PedalControl()
        {
            throttleBar.Height = Math.Abs(Math.Round((throttlePanel.Height - 4) * car.Pedals.ThrottleLevel));
            brakeBar.Height = Math.Abs(Math.Round((brakePanel.Height - 4) * car.Pedals.BrakeLevel));
            clutchBar.Height = Math.Abs(Math.Round((clutchPanel.Height - 4) * (1f - car.Pedals.ClutchLevel)));
        }


        private TimeSpan tmpTime;
        private bool fuelLevelUpdate = true;
        private float fuelUsage = 0f;

        private void DisplayControl()
        {
            // TODO: Start/Ziel-Überfahrt per LapCount bestimmen
            if(session.PlayerLapDistPct > 0.9999 && this.fuelLevelUpdate)
            {
                fuelUsage = fuelLevelNewLap - car.Tank.FuelLevel;
                fuelLevelNewLap = car.Tank.FuelLevel;
                fuelLevelUpdate = false;
            }
            else if(session.PlayerLapDistPct < 0.1 && !this.fuelLevelUpdate)
                fuelLevelUpdate = true;

            fuelLevelLabel.Content = "Fuel:\n" + Math.Round(car.Tank.FuelLevel, 2) + " l / " + Math.Round(car.Tank.MaxFuel, 2) + " l";
            fuelUsageLabel.Content = "Fuel Usage:\n" + (Math.Round(fuelUsage, 2) == 0 ? "-" : Math.Round(fuelUsage, 2) + " l");


            tmpTime = TimeSpan.FromMilliseconds(double.Parse(wrapper.GetTelemetryValue<float>("LapCurrentLapTime").Value.ToString()) * 1000);
            LapTimeLabel.Content = tmpTime.ToString(@"mm\:ss\:fff") == "00:00:000" ? "-:--" : tmpTime.ToString(@"mm\:ss\:fff");

            // TODO: muss nur einmal pro Runde gesetzt werden
            tmpTime = TimeSpan.FromMilliseconds(double.Parse(wrapper.GetTelemetryValue<float>("LapLastLapTime").Value.ToString()) * 1000);
            LastLapTimeLabel.Content = tmpTime.ToString(@"mm\:ss\:fff") == "00:00:000" ? "-:--" : tmpTime.ToString(@"mm\:ss\:fff");

            LapsLabel.Content = session.PlayerLapCount + " / " + (session.Laps == 0 ? "-" : session.Laps.ToString());


            PositionLabel.Content = (session.PlayerClassPosition == 0 ? "-" : session.PlayerClassPosition.ToString()) + " / " + session.Drivers.Count;

            temperatureLabel.Content = Math.Round(track.AirTemperature, 1) + "°C / " + Math.Round(track.TrackTemperature, 1) + "°C";
        }

        float playerLapDistance;
        private void SpotterControl()
        {
            if(!player.IsSpactator)
            {
                playerLapDistance = player.LapDistance * track.Length;
                foreach(Driver driver in session.Drivers)
                {
                    if(driver.CarId != player.CarId && driver.LapDistance != -1 && Math.Abs(playerLapDistance - (driver.LapDistance * track.Length)) < 4 && spotterLeft.Fill != spotterOnColor)
                    {
                        spotterLeft.Fill = spotterOnColor;
                        spotterRight.Fill = spotterOnColor;
                        break;
                    }
                    else if(spotterLeft.Fill != spotterOffColor)
                    {
                        spotterLeft.Fill = spotterOffColor;
                        spotterRight.Fill = spotterOffColor;
                    }
                }
            }
        }

        #endregion

16.806 Beiträge seit 2008
vor 4 Jahren

Mit den TPL DataFlows und nem sauberen Code (lager Deine Logik etc aus und schreib das nicht alles in eine riesen Methode in den Event) solltest das easy sauber umsetzen können.
Dann hast mehrere Tasks, die Deine Events verarbeiten etc.

Das ganze UI Zeug lässt sich super auch mit Reactive Extensions und Subscriptions vereinfachen.

4.931 Beiträge seit 2008
vor 4 Jahren

Da kann man wirklich einiges noch optimieren.

Das erste ist ersteinmal, daß du zusätzlich einen Timer (mit z.B. 100ms Intervall) benutzen solltest , um die Anzeige von dem Datenempfang zu trennen.
In der Telemetry-Ereignismethode speicherst du dann nur die Daten ab (wie du es ja bisher auch schon machst) und in der Anzeigemethode aktualisierst du dann die Controls.

Da du WPF benutzt, ist aber gerade die direkte Verwendung von Controls nicht zu empfehlen, sondern MVVM, s. [Artikel] MVVM und DataBinding.

Und noch ein Tipp zu deinem bisherigen Code:

  • Ressourcen, wie z.B. SolidColorBrush, sollten nur einmalig erzeugt werden, d.h. lege dafür dann Membervariablen an.

Aber warum sind rpmLedOnColor und rpmLedOffColor anscheinend 2 unterschiedliche Typen (Brush und Color), trotz ähnlichem Namen?

PS: Bei WPF heißt der Timer DispatcherTimer.

16.806 Beiträge seit 2008
vor 4 Jahren

Das erste ist ersteinmal, daß du zusätzlich einen Timer (mit z.B. 100ms Intervall) benutzen solltest , um die Anzeige von dem Datenempfang zu trennen.

Absolut guter Hinweis.
Hier kann man jedoch auch einfach eine Reactive Extension Subscription verwenden. Total simpel und viel einfacher als Timer.

Beispiel: [Gelöst] Stunde dauerhaft überprüfen bis 04:00 Uhr

Observable
            .Interval(<interval>)
            .Select(async _ => await ExecuteAnyThingAsync())
            .Subscribe();
5.657 Beiträge seit 2006
vor 4 Jahren

Um es nochmal kurz zusammenzufassen: Die Performance-Probleme werden nicht durch die Berechnung verursacht, sondern durch das ständige Aktualisieren der Benutzeroberfläche.

Das kannst du umgehen, indem du die Daten für die Anzeige weniger häufig aktualisierst (siehe Abts Antwort), und indem du nur die Anzeigen aktualisierst, die sich auch wirklich geändert haben (das passiert beim DataBinding automatisch, siehe Th69s Antwort).

Weeks of programming can save you hours of planning

P
PierreDole Themenstarter:in
74 Beiträge seit 2017
vor 4 Jahren

Ich bin begeistert! Vielen Dank für die Vorschläge, ich habe sie alle beherzigt und den Code umgeschrieben. Dank den Timern und vor allem dem ViewModel läuft alles sehr viel flüssiger, da nicht alles ständig neu gezeichnet wird. Sogar der Spotter funktioniert auf Anhieb. 🙂 Die CPU-Auslastung ging von 10% auf 0.0% - 0.2% runter (abhängig von der Fahreranzahl auf der Strecke). Wie gesagt, ich bin begeistert! 😁

Noch bin ich nicht ganz fertig. Einiges muss ich noch ändern und ergänzen. Bin aber zumindest auf einen besseren Weg.

Eine Kleinigkeit hätte ich noch. Die ViewModel Datei ist etwas lang geraten und es kommt noch viel mehr rein. Ist das normal, oder kann man die irgendwie auf mehrere Dateien aufteilen, z.B. die Daten, die in den Labels angezeigt werden in eine Datei und alle Farben für Schriften und Hintergründe in eine andere?

namespace iRacingHud.ViewModel
{
    class HudViewModel : BaseViewModel
    {
        private string currentLapTime;
        private string lastLapTime;

        private double airTemperature;
        private double trackTemperature;

        private double throttleBarHeight;
        private double brakeBarHeight;
        private double clutchBarHeight;

        private string rpmLED01Color;
        private string rpmLED02Color;
        private string rpmLED03Color;
        private string rpmLED04Color;
        private string rpmLED05Color;
        private string rpmLED06Color;
        private string rpmLED07Color;
        private string rpmLED08Color;
        private string rpmLED09Color;

        private string shiftFlashlightColor;
        private bool isShiftFlashlightVisible;

        private string gear;

        private int speed;
        private float rpm;

        private string position;
        private string laps;

        private double fuelLevel;
        private double maxFuel;
        private string fuelUsage;

        private bool isSpotterVisible;

        private bool isHudPanelVisible;


        // Methods
        public string CurrentLapTime
        {
            get { return currentLapTime; }
            set
            {
                if(value != currentLapTime)
                {
                    currentLapTime = value;
                    OnPropertyChanged("CurrentLapTime");
                }
            }
        }
        public string LastLapTime
        {
            get { return lastLapTime; }
            set
            {
                if(value != lastLapTime)
                {
                    lastLapTime = value;
                    OnPropertyChanged("LastName");
                }
            }
        }
        

        public double AirTemperature
        {
            get { return airTemperature; }
            set
            {
                if(value != airTemperature)
                {
                    airTemperature = value;
                    OnPropertyChanged("AirTemperature");
                    OnPropertyChanged("AirTrackTemperature");
                }
            }
        }
        public double TrackTemperature
        {
            get { return trackTemperature; }
            set
            {
                if(value != trackTemperature)
                {
                    trackTemperature = value;
                    OnPropertyChanged("TrackTemperature");
                    OnPropertyChanged("AirTrackTemperature");
                }
            }
        }
        public string AirTrackTemperature
        {
            get { return string.Format("{0}°C / {1}°C", AirTemperature, TrackTemperature); }
        }


        public double ThrottleBarHeight
        {
            get { return throttleBarHeight; }
            set
            {
                if(value != throttleBarHeight)
                {
                    throttleBarHeight = value;
                    OnPropertyChanged("ThrottleBarHeight");
                }
            }
        }
        public double BrakeBarHeight
        {
            get { return brakeBarHeight; }
            set
            {
                if(value != brakeBarHeight)
                {
                    brakeBarHeight = value;
                    OnPropertyChanged("BrakeBarHeight");
                }
            }
        }
        public double ClutchBarHeight
        {
            get { return clutchBarHeight; }
            set
            {
                if(value != clutchBarHeight)
                {
                    clutchBarHeight = value;
                    OnPropertyChanged("ClutchBarHeight");
                }
            }
        }


        public string RpmLED01Color
        {
            get { return rpmLED01Color; }
            set
            {
                if(value != rpmLED01Color)
                {
                    rpmLED01Color = value;
                    OnPropertyChanged("RpmLED01Color");
                }
            }
        }
        public string RpmLED02Color
        {
            get { return rpmLED02Color; }
            set
            {
                if(value != rpmLED02Color)
                {
                    rpmLED02Color = value;
                    OnPropertyChanged("RpmLED02Color");
                }
            }
        }
        public string RpmLED03Color
        {
            get { return rpmLED03Color; }
            set
            {
                if(value != rpmLED03Color)
                {
                    rpmLED03Color = value;
                    OnPropertyChanged("RpmLED03Color");
                }
            }
        }
        public string RpmLED04Color
        {
            get { return rpmLED04Color; }
            set
            {
                if(value != rpmLED04Color)
                {
                    rpmLED04Color = value;
                    OnPropertyChanged("RpmLED04Color");
                }
            }
        }
        public string RpmLED05Color
        {
            get { return rpmLED05Color; }
            set
            {
                if(value != rpmLED05Color)
                {
                    rpmLED05Color = value;
                    OnPropertyChanged("RpmLED05Color");
                }
            }
        }
        public string RpmLED06Color
        {
            get { return rpmLED06Color; }
            set
            {
                if(value != rpmLED06Color)
                {
                    rpmLED06Color = value;
                    OnPropertyChanged("RpmLED06Color");
                }
            }
        }
        public string RpmLED07Color
        {
            get { return rpmLED07Color; }
            set
            {
                if(value != rpmLED07Color)
                {
                    rpmLED07Color = value;
                    OnPropertyChanged("RpmLED07Color");
                }
            }
        }
        public string RpmLED08Color
        {
            get { return rpmLED08Color; }
            set
            {
                if(value != rpmLED08Color)
                {
                    rpmLED08Color = value;
                    OnPropertyChanged("RpmLED08Color");
                }
            }
        }
        public string RpmLED09Color
        {
            get { return rpmLED09Color; }
            set
            {
                if(value != rpmLED09Color)
                {
                    rpmLED09Color = value;
                    OnPropertyChanged("RpmLED09Color");
                }
            }
        }

        public string ShiftFlashlightColor
        {
            get { return shiftFlashlightColor; }
            set
            {
                if(value != shiftFlashlightColor)
                {
                    shiftFlashlightColor = value;
                    OnPropertyChanged("ShiftFlashlightColor");
                }
            }
        }
        public bool IsShiftFlashlightVisible
        {
            get { return isShiftFlashlightVisible; }
            set
            {
                if(value != isShiftFlashlightVisible)
                {
                    isShiftFlashlightVisible = value;
                    OnPropertyChanged("IsShiftFlashlightVisible");
                }
            }
        }

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

        public int Speed
        {
            get { return speed; }
            set
            {
                if(value != speed)
                {
                    speed = value;
                    OnPropertyChanged("Speed");
                }
            }
        }
        public float RPM
        {
            get { return rpm; }
            set
            {
                if(value != rpm)
                {
                    rpm = value;
                    OnPropertyChanged("RPM");
                }
            }
        }


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

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


        public double FuelLevel
        {
            get { return fuelLevel; }
            set
            {
                if(value != fuelLevel)
                {
                    fuelLevel = value;
                    OnPropertyChanged("FuelLevel");
                    OnPropertyChanged("FuelDisplay");
                }
            }
        }
        public double MaxFuel
        {
            get { return maxFuel; }
            set
            {
                if(value != maxFuel)
                {
                    maxFuel = value;
                    OnPropertyChanged("MaxFuel");
                    OnPropertyChanged("FuelDisplay");
                }
            }
        }
        public string FuelDisplay
        {
            get { return string.Format("Fuel\n{0} l / {1} l", FuelLevel, MaxFuel); }
        }
        public string FuelUsage
        {
            get { return fuelUsage; }
            set
            {
                if(value != fuelUsage)
                {
                    fuelUsage = value;
                    OnPropertyChanged("FuelUsage");
                }
            }
        }


        public bool IsSpotterVisible
        {
            get { return isSpotterVisible; }
            set
            {
                if(value != isSpotterVisible)
                {
                    isSpotterVisible = value;
                    OnPropertyChanged("IsSpotterVisible");
                }
            }
        }


        public bool IsHudPanelVisible
        {
            get { return isHudPanelVisible; }
            set
            {
                if(value != isHudPanelVisible)
                {
                    isHudPanelVisible = value;
                    OnPropertyChanged("IsHudPanelVisible");
                }
            }
        }
    }
}

4.931 Beiträge seit 2008
vor 4 Jahren

Das freut mich, daß dir unsere Antworten weitergeholfen haben.

Zu deinem ViewModel:

  • die RpmLED0XColor-Eigenschaften solltest du als ObservableCollection<T> umsetzen (du kannst auch direkt ein Element dann binden, s. z.B. Binding to an array element) oder dann direkt diese Liste an ein ItemsControl o.ä. binden.
  • das manuelle Schreiben (bzw. C&P) der Eigenschaften kann man mittels einiger Libraries (stark) vereinfachen (s. Liste Top 20 NuGet INotifyPropertyChanged Packages - ich persönlich kenne PropertyChanged.Fody und Kind Of Magic - der Name einer anderen Library, die ich früher bei einem Projekt gerne benutzt hatte, fällt mir leider nicht mehr ein...).