none
Diagramme zeichen in WPF RRS feed

  • Frage

  • Einen schönen guten Tag.

    Ich möchte in einem Programm von mir Daten von Sensoren (>16) über eine Schnittstelle auslesen und diese grafische darstellen lassen. Derzeit lese ich mich gerade in das Thema ein, wie man dies realisieren könnte. WPf und verschiedene  Toolkits (u.a. WPF Toolkit) scheinen hierfür nicht gerade sehr gut geeignet zu sein, aufgrund ihrer Performance  bei vielen Datenpunkten. Nun habe ich gelesen, das Windowsforms mit Chart eine sehr gute Performance zum zeichnen von Diagrammen bieten soll. Daher ist nun mein Gedanke, dies mit Hilfe von WindowsFormsHost in eine WPF-Anwendung  reinzuholen. Nun wollte ich fragen, ob hier jemand damit schon Erfahrungen gemacht hat und ob die Performance auch bei vielen Datenpunkten wirklich so gut  und es so viel besser als die verschiedenen Toolkits von WPF ist. Danke schon einmal für konstruktive Antworten.

    Mit freundlichen Grüßen

    Max

    Freitag, 9. Juni 2017 10:24

Antworten

  • Hi Max,
    hier mal eine Demo in VB.NET, die in WPF super schnell arbeitet. Die Punkte laufen aller 10 MS ein.

    XAML:

    <Window x:Class="Window05"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="Window05" Height="300" Width="300" Loaded="Window_Loaded">
        <Grid>
        <TabControl>
          <TabItem Header="Tab1"/>
          <TabItem Header="Tab2">
            <Image Name="img"/>
          </TabItem>
        </TabControl> 
        </Grid>
    </Window>

    CodeBehind:

    Imports System.Threading
    
    Public Class Window05
    
      Private bmp As New WriteableBitmap(250, 250, 96, 96, PixelFormats.Bgr24, Nothing)
      Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
        Me.img.Source = bmp
        ResetBitmap()
        Dim th As New Thread(New ParameterizedThreadStart(AddressOf setBit))
        th.Start()
      End Sub
    
      Private Sub ResetBitmap()
        Dim colordata() As Byte = {255, 255, 255, 0}
        For i = 0 To CType(bmp.Width - 1, Integer)
          For k = 0 To CType(bmp.Height - 1, Integer)
            bmp.WritePixels(New Int32Rect(i, k, 1, 1), colordata, 4, 0)
          Next
        Next
      End Sub
    
      Private Sub setBit(obj As Object)
        Dim colordata() As Byte = {255, 0, 0, 0}
        Dim i As Integer = 0
        Dim k As Double = 0
        Do
          Dim wert = CType(100 * Math.Sin(k + i / 50), Integer) + 100
          Dispatcher.Invoke(Sub()
                              bmp.WritePixels(New Int32Rect(i, wert, 1, 1), colordata, 4, 0)
                              i += 1
                              If i >= bmp.Width Then
                                k += i / 50
                                i = 0
                              End If
                            End Sub)
          Thread.Sleep(10)
        Loop
      End Sub
    End Class


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 9. Juni 2017 13:11
  • Hallo,

    ich würde es auch so machen wie Peter das vorschlägt. Nur würde ich anti aliased Linen von Punkt zu Punkt zeichnen

    Z.b. mit dem Bresenham Algorithmus. Hier auch der Code in C# von hier

    void SetPixelAA(int x, int y, double alpha)
            {
                var a = 1.0 - alpha / 255.0;
            }
    
            void AALine(int x0, int y0, int x1, int y1)
            {
                var dx = Math.Abs(x1 - x0);
                var sx = x0 < x1 ? 1 : -1;
                var dy = Math.Abs(y1 - y0);
                var sy = y0 < y1 ? 1 : -1;
                var err = dx + dy;
                var ed = dx + dy == 0 ? 1 : Math.Sqrt(dx * dx + dy * dy);
                int e2, x2;
    
                for (;;)
                {
                    SetPixelAA(x0, y0, 255 * Math.Abs(err - dx + dy) / ed);
    
                    e2 = err; x2 = x0;
                    if (2 * e2 >= -dx)
                    {                                            
                        if (x0 == x1)
                            break;
    
                        if (e2 + dy < ed)
                            SetPixelAA(x0, y0 + sy, 255 * (e2 + dy) / ed);
    
                        err -= dy; x0 += sx;
                    }
                    if (2 * e2 <= dy)
                    {                                             
                        if (y0 == y1)
                            break;
    
                        if (dx - e2 < ed)
                            SetPixelAA(x2 + sx, y0, 255 * (dx - e2) / ed);
    
                        err += dx; y0 += sy;
                    }
                }
            }


    Gruß, Thomas

    Sage nie, ich kann es nicht - sage nur, ich kann es noch nicht!

    Icon für UWP


    Samstag, 10. Juni 2017 23:39

Alle Antworten

  • Hi Max,
    je nachdem, wie die Grafik erzeugt und dargestellt wird, gibt es auch unterschiedlichen Ressourcenaufwand (Zeit und Speicher).

    Wenn Du die Darstellung als Bild zeichnest und dieses Bild darstellst, ist der Aufwand in WindowsForms und in WPF praktisch identisch. Wenn Du jedoch die Darstellung der einzelnen Punkte mit WPF-Steuerelementen realisierst, dann kann die Performance bei vielen Steuerelementen drastisch sinken. Es wird Dir nur eine Variantenbetrachtung bleiben, um zu bestimmen, welcher Weg optimal für Deine Anforderungen ist.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 9. Juni 2017 10:52
  • Guten Tag Peter,

    Danke für die schnelle Antwort.  Ich erhalte ca. alle paar hundert ms einen Messwert für den jeweiligen Sensor. Den Wert stelle ich in einem Label als Text kurzzeitig da. In einem anderen TabItem soll währenddessen ein Diagramm für den jeweiligen Sensor ein Diagramm zeichnen. So wie ich gelesen und gehört habe, soll hier die reine WPF Anwendung schnell an ihre Performancegrenze stoßen, falls die erhaltenen Werte nicht irgendwann überschrieben werden und der Graph im Diagramm "weiterzieht" (Links nicht mehr irgendwann 0  bei der x-Achse sondern wandert weiter). Abhilfe soll vllt auch ein weniger häufiges Abfragen der Werte schaffen. dies möchte ich aber nicht unbedingt. Daher ist nun die Frage, gibt es performantere kostenlose Toolkits oder wäre das integrieren des Windowsformhost mit Chart die wahrscheinlich beste Variante, um dies zu realisieren. Die mir gleichzeitig auch die größte Performance bringt.

    Mit freundlichen Grüßen

    Max

    • Bearbeitet Max Erik12 Freitag, 9. Juni 2017 11:44
    Freitag, 9. Juni 2017 11:44
  • Hi Max,
    hier mal eine Demo in VB.NET, die in WPF super schnell arbeitet. Die Punkte laufen aller 10 MS ein.

    XAML:

    <Window x:Class="Window05"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="Window05" Height="300" Width="300" Loaded="Window_Loaded">
        <Grid>
        <TabControl>
          <TabItem Header="Tab1"/>
          <TabItem Header="Tab2">
            <Image Name="img"/>
          </TabItem>
        </TabControl> 
        </Grid>
    </Window>

    CodeBehind:

    Imports System.Threading
    
    Public Class Window05
    
      Private bmp As New WriteableBitmap(250, 250, 96, 96, PixelFormats.Bgr24, Nothing)
      Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
        Me.img.Source = bmp
        ResetBitmap()
        Dim th As New Thread(New ParameterizedThreadStart(AddressOf setBit))
        th.Start()
      End Sub
    
      Private Sub ResetBitmap()
        Dim colordata() As Byte = {255, 255, 255, 0}
        For i = 0 To CType(bmp.Width - 1, Integer)
          For k = 0 To CType(bmp.Height - 1, Integer)
            bmp.WritePixels(New Int32Rect(i, k, 1, 1), colordata, 4, 0)
          Next
        Next
      End Sub
    
      Private Sub setBit(obj As Object)
        Dim colordata() As Byte = {255, 0, 0, 0}
        Dim i As Integer = 0
        Dim k As Double = 0
        Do
          Dim wert = CType(100 * Math.Sin(k + i / 50), Integer) + 100
          Dispatcher.Invoke(Sub()
                              bmp.WritePixels(New Int32Rect(i, wert, 1, 1), colordata, 4, 0)
                              i += 1
                              If i >= bmp.Width Then
                                k += i / 50
                                i = 0
                              End If
                            End Sub)
          Thread.Sleep(10)
        Loop
      End Sub
    End Class


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 9. Juni 2017 13:11
  • Hallo,

    ich würde es auch so machen wie Peter das vorschlägt. Nur würde ich anti aliased Linen von Punkt zu Punkt zeichnen

    Z.b. mit dem Bresenham Algorithmus. Hier auch der Code in C# von hier

    void SetPixelAA(int x, int y, double alpha)
            {
                var a = 1.0 - alpha / 255.0;
            }
    
            void AALine(int x0, int y0, int x1, int y1)
            {
                var dx = Math.Abs(x1 - x0);
                var sx = x0 < x1 ? 1 : -1;
                var dy = Math.Abs(y1 - y0);
                var sy = y0 < y1 ? 1 : -1;
                var err = dx + dy;
                var ed = dx + dy == 0 ? 1 : Math.Sqrt(dx * dx + dy * dy);
                int e2, x2;
    
                for (;;)
                {
                    SetPixelAA(x0, y0, 255 * Math.Abs(err - dx + dy) / ed);
    
                    e2 = err; x2 = x0;
                    if (2 * e2 >= -dx)
                    {                                            
                        if (x0 == x1)
                            break;
    
                        if (e2 + dy < ed)
                            SetPixelAA(x0, y0 + sy, 255 * (e2 + dy) / ed);
    
                        err -= dy; x0 += sx;
                    }
                    if (2 * e2 <= dy)
                    {                                             
                        if (y0 == y1)
                            break;
    
                        if (dx - e2 < ed)
                            SetPixelAA(x2 + sx, y0, 255 * (dx - e2) / ed);
    
                        err += dx; y0 += sy;
                    }
                }
            }


    Gruß, Thomas

    Sage nie, ich kann es nicht - sage nur, ich kann es noch nicht!

    Icon für UWP


    Samstag, 10. Juni 2017 23:39
  • Hallo Max,

    mich würde noch interessieren wie lange läuft eine Messung? Handelt es sich um einen definierten Zeitraum (eine Minute, eine Woche...)? Und sollen die Daten gespeichert werden? 

    Diverse Toolkits gibt es, ob sie jedoch kostenlos sind, hängt bei einigen davon ab, wie du dein Programm nutzt (privat, gewerblich....). Reinschauen würde ich auf jeden Fall bei Syncfusion. Ob das für dich performant genug ist, weiß ich nicht. Ich habe in meinen Anwendungen das sammeln der Daten und diese anzuzeigen getrennt.

    Gruß, Stefan


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP

    Sonntag, 11. Juni 2017 06:58
  • Einen schönen guten Abend Stefan, Thomas und Peter.

    Vielen, vielen Dank für die guten Hinweise, den Programmcode und die tatkräftige Unterstützung. Ich schaue mir morgen beides mal intensiver an und versuche es passend für meine Bedürfnisse zu implementieren. Es hat mir auf alle Fälle schon einmal sehr geholfen. Danke.

    @ Stefan: Es handelt sich um so etwas wie eine Überprüfung eines Gerätes, Ich schalte das Gerät über meinen Programmcode an und anschließend stelle ich die Sensorwerte dar. (als Label Content und grafisch im separaten Tab (Diagramm)) Dies kann nur einige Minuten oder aber auch mehrere Stunden (x>6h).  Je nachdem, wie man es braucht. Ob ich die Dateien speichern möchte, weiß ich noch nicht ganz aber ich denke mal ja. Da hatte ich mir gedacht, die Werte z.B. in einer Log-Datei zu erfassen.

    Mit freundlichen Grüßen

    Max


    • Bearbeitet Max Erik12 Sonntag, 11. Juni 2017 17:59
    Sonntag, 11. Juni 2017 16:53
  • Hi Max,
    wenn Du die funktionellen Schichten trennst, dann kannst Du später einfach weitere Anpassungen mit relativ wenig Aufwand durchführen.

    Es gibt einen Puffer mit den erfassten Sensorwerten.

    Diesen Puffer kann man nach Bedarf speichern, z.B. als Log-Datei oder in eine Datenbank.

    Dieser Puffer wird mit den einlaufenden Sensorwerten gefüllt. Ggf. wird die Oberfläche informiert, dass es neue Werte für die Anzeige gibt. Ggf. wird auch die Datenschicht informiert, dass es neue Werte gibt, die abzuspeichern sind.

    Die Oberfläche holt sich die gewünschten Sensorwerte (alle oder eine vorgegebene Anzahl der letzten Werte) und erzeugt die Grafik.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Sonntag, 11. Juni 2017 17:12
  • Ein schönen guten Abend Peter.

    Das ist ein sehr interessant, was du erwähnst. Danke. Mein Programm soll recht flexibel sein, so dass es ohne große Probleme auch für andere ähnliche Geräte genutzt werden kann.

    Die Idee mit dem Puffer ist sehr gut. Standardmäßig gibt es sowas nicht oder? Ich müsste dafür einen speziellen Speicher oder ein Array zur Verfügung stellen, in die jeweiligen Sensorwerte gespeichert werden. Dies müsst dann für jeden Sensor angelegt werden und am Ende zusammengebracht werden oder sehe ich das falsch?  Ich werde mich Morgen auf alle Fälle mal dazu belesen, wie man so etwas bewerkstelligen könnte.

    Mit freundlichen Grüßen

    Max

    Sonntag, 11. Juni 2017 17:58
  • Hi Max,
    der Puffer kann eine BlockingCollection sein.

    Jeder Sensorwert wird der BlockingCollection hinzugefügt. Für jeden Sensorwert wird ein Objekt vom Typ einer eigenen Klasse erzeugt. In diesem Objekt können dann x-Wert (z.B. Zeit), y-Wert (Sensorwert) und noch weitere Werte (z.B. von welchem Sensor bekommen, Status usw.) gespeichert werden. Ob für jeden Sensor eine eigene Collection erforderlich ist, muss entschieden werden. Anfangen kann man mit einer Collection und ggf. später entscheiden, die Erfassung und Auswertung zu trennen.

    Die BlockingCollection ist sinnvoll, wenn das Erfassen der Werte in einem Thread parallel zur Oberfläche (anderer Thread) und parallel zur Datenspeicherung ausgeführt wird.

    Die Oberfläche kann sich bei einer Collection die Werte des betreffenden Sensors für die Anzeige holen (Filter).


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Sonntag, 11. Juni 2017 18:21
  • Einen schönen guten Morgen Peter,

    Vielen Dank für den Hinweis. Dann werde ich mich jetzt da mal einlesen und werde versuchen, es erst einmal Testweise für einen Sensor zu implementieren. Wenn dies dann funktioniert, werde ich entscheiden, ob ich es für jeden Sensor separat mache oder einen großen.

    Mit freundlichen Grüßen

    Max

    !!!EDIT!!!

    Einen schönen guten Morgen.

    Ich habe doch noch einmal ein anliegen. Ich habe leider vergessen im ersten Post zu erwähnen, das mein komplettes bisheriges Programm in Visual C# geschrieben ist. Das müsste doch trotzdem ähnlich funktionieren mit der gleichen Leistungsfähigkeit oder? BlockingCollection gibt es schließlich auch c#. 

    Mir ist das leider jetzt erst aufgefallen, da ich das Wochenende nicht da war und jetzt erst Zeit hatte, alles zu überfliegen und zu testen.

    Mit freundlichen Grüßen

    Max 

    • Bearbeitet Max Erik12 Montag, 12. Juni 2017 06:30
    Montag, 12. Juni 2017 06:08
  • Hallo Max,

    Ich habe leider vergessen im ersten Post zu erwähnen, das mein komplettes bisheriges Programm in Visual C# geschrieben ist. Das müsste doch trotzdem ähnlich funktionieren mit der gleichen Leistungsfähigkeit oder? BlockingCollection gibt es schließlich auch c#.

    Peters Code lässt sich mit einem Konverter nach Visual C# konvertieren. In der Regel verläuft die Konvertierung reibungslos. Lediglich die Zeile

    Dim wert = CType(100 * Math.Sin(k + i / 50), Integer) + 100

    wird von diesem Konverter als

    dynamic wert = Convert.ToInt32(100 * Math.Sin(k + i / 50)) + 100;

    wiedergegeben, was man durch folgende Zeile ersetzen könnte:

    int wert = Convert.ToInt32(100 * Math.Sin(k + i / 50)) + 100; 

    Gruß,
    Dimitar


    Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „IT-Pros helfen IT-Pros“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Freitag, 16. Juni 2017 11:20
    Moderator
  • Einen schönen guten Tag Dimitar,

    Vielen Dank für die Ergänzung und den Änderungsvorschlag.

    Mit freundlichen Grüßen

    Max

    Montag, 19. Juni 2017 05:51