none
Performante Darstellung in WPF RRS feed

  • Frage

  • Hallo, hab folgendes Problem:

    Ich empfange über einen UDP-Client in sehr kurzen Abständen (100-500ms) Datenpaket, welche Ich Tabellarisch visualisieren soll. Da Problem ist, dass durch meine View der Workerthread für die Kommunikation zu lange blockiert wird, und sich dieser dann verabschiedet. Die Darstellung erfolgt in einer Listview...

     <ListView Grid.Row="1" x:Name="listBox"
                              ItemsSource="{Binding ItemList, Mode=OneWay,NotifyOnSourceUpdated=False}"
                              ScrollViewer.VerticalScrollBarVisibility="Auto"
                              Width="Auto"
                             ScrollViewer.IsDeferredScrollingEnabled="False"
                              ScrollViewer.CanContentScroll="True"
                              VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" Grid.ColumnSpan="4">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <VirtualizingStackPanel Orientation="Horizontal" VirtualizationMode="Recycling">
                                    <Label  Content="{Binding Position, Mode=OneWay,NotifyOnSourceUpdated=False}" Width="70">
                                        <Label.Resources>
                                            <Style TargetType="Label">
                                                <Style.Triggers>
                                                    <DataTrigger  Binding="{Binding GetPositionError,NotifyOnSourceUpdated=False, Mode=OneWay}" Value="True">
                                                        <Setter Property="BorderBrush"  Value="Red"></Setter>
                                                        <Setter Property="BorderThickness" Value="3" />
                                                    </DataTrigger>
                                                </Style.Triggers>
                                            </Style>
                                        </Label.Resources>
                                    </Label>
                                    <ListView  ItemsSource="{Binding AnalogOutputs,Mode=OneWay,NotifyOnSourceUpdated=False}"
                                           VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
                                        <ListView.ItemTemplate>
                                            <DataTemplate>
                                                <Border Visibility="{Binding IsEnabled,NotifyOnSourceUpdated=False,Converter={StaticResource BooleanToVisibilityConverter}}">
                                                    <Border.Resources>
                                                        <Style TargetType="Border">
                                                            <Style.Triggers>
                                                                <DataTrigger  Binding="{Binding ErrorCode, Mode=OneWay,NotifyOnSourceUpdated=False}" Value="{x:Static local:ErrorCodes.OutOfTolerance100}">
                                                                    <Setter Property="BorderBrush"  Value="Red"></Setter>
                                                                    <Setter Property="BorderThickness" Value="3" />
                                                                </DataTrigger>
                                                                <DataTrigger  Binding="{Binding ErrorCode,Mode=OneWay,NotifyOnSourceUpdated=False}" Value="{x:Static local:ErrorCodes.OutOfTolerance50}">
                                                                    <Setter Property="BorderBrush"  Value="Orange"></Setter>
                                                                    <Setter Property="BorderThickness" Value="3" />
                                                                </DataTrigger>
                                                            </Style.Triggers>
                                                        </Style>
                                                    </Border.Resources>
                                                    <Grid Visibility="{Binding IsEnabled,Converter={StaticResource BooleanToVisibilityConverter}}">
                                                        <Grid.RowDefinitions>
                                                            <RowDefinition />
                                                            <RowDefinition />
                                                        </Grid.RowDefinitions>
                                                        <TextBox Grid.Row="0" Text="{Binding MeasValue1, Mode=OneWay,NotifyOnSourceUpdated=False}"
                                                           Style="{StaticResource BaseLabelStyle}" Visibility="{Binding IsEnabled,Converter={StaticResource BooleanToVisibilityConverter}}" ></TextBox>
                                                        <TextBox Grid.Row="1" Text="{Binding MeasValue2,Mode=OneWay,NotifyOnSourceUpdated=False}"
                                                           Style="{StaticResource BaseLabelStyle}" Visibility="{Binding IsEnabled,Converter={StaticResource BooleanToVisibilityConverter}}">
                                                        </TextBox>
                                                    </Grid>
                                                </Border>
                                            </DataTemplate>
                                        </ListView.ItemTemplate>
                                        <ListView.ItemsPanel>
                                            <ItemsPanelTemplate>
                                                <VirtualizingStackPanel Orientation="Horizontal" Width="Auto" VirtualizationMode="Recycling">
                                                </VirtualizingStackPanel>
                                            </ItemsPanelTemplate>
                                        </ListView.ItemsPanel>
                                    </ListView>
                                </VirtualizingStackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>

    Mein Objekte werden in einer ObservableCollection "ItemList" gespeichert. Dass Problem scheint die Methode:

    MS.Win32.UnsafeNativeMethods.GetMessageW  (ist angeblich für das Updaten der Dependency Properties zuständig)  welche im Mainthread aufgerufen wird, zu sein.

    Gibt es eine performantere Darstellung als die ListView?

     

    Freitag, 13. November 2015 17:14

Antworten

  • Hi,
    nachfolgend mal eine kleine Demo, die annähernd Deinen Anforderungen entspricht. Ich habe den Takt des Datenempfangs auf 50 Millisekunden gesetzt und bei meinem Prozessor (5 Jahre alter Intel 2,67 GHz) ruckelt die Oberfläche nicht.

    XAML:

    <Window x:Class="Window06"
            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:WpfApplication1VB"
            mc:Ignorable="d"
            Title="Window06" Height="300" Width="400">
      <Window.Resources>
        <local:Window06VM x:Key="vm"/>
        <local:Window06Converter x:Key="conv"/>
        <DataTemplate x:Key="DataTemplate">
          <StackPanel Orientation="Horizontal" 
                      DataContext="{Binding}">
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[0]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[1]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[2]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[3]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[4]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[5]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[6]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[7]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[8]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[9]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
          </StackPanel>
        </DataTemplate>
      </Window.Resources>
      <StackPanel DataContext="{Binding Source={StaticResource vm}}">
        <Label Content="{Binding Info}"/>
        <DataGrid ItemsSource="{Binding ItemList}"
                  ScrollViewer.VerticalScrollBarVisibility="Auto"
                  AutoGenerateColumns="False">
          <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Position}"/>
            <DataGridTemplateColumn CellTemplate="{StaticResource DataTemplate}" 
                                    Width="250"/>
          </DataGrid.Columns>
        </DataGrid>
      </StackPanel>
    </Window>

    Code dazu:

    Public Class Window06VM
      Implements INotifyPropertyChanged
      Public Sub New()
        'Call (New Threading.Thread(AddressOf RefreshThread)).Start()
        If Not DesignerProperties.GetIsInDesignMode(New DependencyObject) Then
          Call (New Threading.Thread(AddressOf LoadData)).Start()
        End If
      End Sub
    
      Private Sub RefreshThread()
        Do
          Threading.Thread.Sleep(1000)
          Application.Current.Dispatcher.Invoke(Sub()
                                                  cvs.View.Refresh()
                                                End Sub)
        Loop
      End Sub
    
      Private zeit1 As Date = Now
    
      Private Sub LoadData()
        Dim rnd As New Random
        Do
          Threading.Thread.Sleep(50)
          Dim zeit2 = Now
          If col.Count < 20 Then
            Application.Current.Dispatcher.Invoke(Sub()
                                                    Dim item1 As New Window06Data1 With {.Position = rnd.Next(1, 100).ToString}
                                                    col.Add(item1)
                                                    For index = 1 To 10
                                                      item1.AnalogOutputs.Add(New Window06Data2())
                                                    Next
                                                  End Sub)
          Else
            Dim i = rnd.Next(0, col.Count)
            col(i).Position = rnd.Next(1, 100).ToString
            Dim k = rnd.Next(0, col(i).AnalogOutputs.Count)
            Dim spalte As Window06Data2 = col(i).AnalogOutputs(k)
            spalte.MeasValue1 = rnd.Next(1, 100).ToString
            spalte.MeasValue2 = rnd.Next(1, 100).ToString
            spalte.ErrorCode = rnd.Next(1, 101)
            spalte.IsEnabled = rnd.NextDouble > 0.2
            Info = String.Format("Zeitintervall {0}", (zeit2 - zeit1).Milliseconds)
            zeit1 = zeit2
          End If
        Loop
      End Sub
    
      Private col As New ObservableCollection(Of Window06Data1)
      Private cvs As CollectionViewSource
    
      Public ReadOnly Property ItemList As ICollectionView
        Get
          If cvs Is Nothing Then
            cvs = New CollectionViewSource
            cvs.Source = col
          End If
          Return cvs.View
        End Get
      End Property
    
      Private _info As String
      Public Property Info As String
        Get
          Return Me._info
        End Get
        Set(value As String)
          If Me._info <> value Then
            Me._info = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
    #Region " PropertyChanged"
      Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
      Private Sub OnPropertyChanged(<CallerMemberName> Optional propname As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
      End Sub
    
    #End Region
    
    End Class
    
    Public Class Window06Data1
      Implements INotifyPropertyChanged
    
      Private _position As String
      Public Property Position() As String
        Get
          Return Me._position
        End Get
        Set(value As String)
          Me._position = value
          OnPropertyChanged()
        End Set
      End Property
    
      Public Property AnalogOutputs As List(Of Window06Data2) = New List(Of Window06Data2)
    
    #Region " PropertyChanged"
      Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
      Private Sub OnPropertyChanged(<CallerMemberName> Optional propname As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
      End Sub
    
    #End Region
    End Class
    
    Public Class Window06Data2
      Implements INotifyPropertyChanged
    
      Private _measValue1 As String
      Public Property MeasValue1() As String
        Get
          Return Me._measValue1
        End Get
        Set(value As String)
          If Me._measValue1 <> value Then
            Me._measValue1 = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
      Private _measValue2 As String
      Public Property MeasValue2() As String
        Get
          Return Me._measValue2
        End Get
        Set(value As String)
          If Me._measValue2 <> value Then
            Me._measValue2 = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
      Private _isEnabled As Boolean
      Public Property IsEnabled() As Boolean
        Get
          Return Me._isEnabled
        End Get
        Set(value As Boolean)
          If Me._isEnabled <> value Then
            Me._isEnabled = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
      Private _errorCode As Integer
      Public Property ErrorCode() As Integer
        Get
          Return Me._errorCode
        End Get
        Set(value As Integer)
          If Me._errorCode <> value Then
            Me._errorCode = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
    #Region " PropertyChanged"
      Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
      Private Sub OnPropertyChanged(<CallerMemberName> Optional propname As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
      End Sub
    
    #End Region
    End Class
    
    Public Class Window06Converter
      Implements IValueConverter
    
      Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        If targetType Is GetType(Visibility) AndAlso value.GetType Is GetType(Boolean) Then
          Return If(CType(value, Boolean), Visibility.Visible, Visibility.Hidden)
        End If
        If targetType Is GetType(Brush) AndAlso value.GetType Is GetType(Integer) Then
          Select Case CType(value, Integer)
            Case < 50
              Return Brushes.White
            Case < 100
              Return Brushes.Orange
            Case 100
              Return Brushes.Red
          End Select
    
        End If
        Return Nothing
      End Function
    
      Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        Throw New NotImplementedException()
      End Function
    End Class


    --
    Viele Grüsse
    Peter Fleischer (MVP, Partner)
    Meine Homepage mit Tipps und Tricks
    Kommas richtig setzen!
    Schüler sagen, Lehrer haben es gut.
    Schüler, sagen Lehrer, haben es gut

    • Als Antwort markiert Hartl_D Montag, 16. November 2015 13:11
    Montag, 16. November 2015 08:05
  • Hallo Hartl_D,

    Ein Paar Inputs von meiner Seite:

    Ab .NET 4.5 kannst Du bei der WPF einen Delay eintragen. D.h. du kannst sagen, dass das Databinding nur z.B. alle 500ms ausgelöst werden soll. Damit kannst Du den Dispatcher entlasten:

    <TextBlock Text="{Binding Name, Delay=500}"/>
    

    http://www.jonathanantoine.com/2011/09/21/wpf-4-5-part-4-the-new-bindings-delay-property/

    Es gibt starke Performance Unterschiede zwischen verschiedenen Grids. Probier doch mal die Grids von Telerik, DevExpress, etc aus. Die sind oft schon out-of-the-box viel besser als das "normale" DataGrid.

    Schau Dir mal diese Anwendung an:

    https://wpfrealtime.codeplex.com/

    Der Artikel dazu geht voll in die Details und am Schluss kommt ein "fast Echtzeit" Dashboard in WPF raus. Ich glaube, das ist genau das, was Du brauchst:

    http://www.codeproject.com/Articles/326641/WPF-MVVM-Real-Time-Trading-Application

    Die .NET Reactive Extensions (.NET Rx) könnten Dir auch weiterhelfen:

    http://stackoverflow.com/questions/19452935/wpf-realtime-chart-application-architecture

    Gruss MM


    PS: Please mark as answer if helpful. Thanks!
    Blog: http://www.manuelmeyer.net
    Twitter: https://twitter.com/manumeyer1

    • Als Antwort markiert Hartl_D Mittwoch, 25. November 2015 13:37
    Dienstag, 24. November 2015 09:55

Alle Antworten

  • Hallo,

    eine GUI ist immer langsam. Und wenn du 2 bis 10mal in der Sekunde ein Update machen willst ist das natürlich heftig. Ich würde dazu tendieren jede Sekunde nur einmal alle Elemente zu übertragen die man wärend dessen zwischen gespeichert hat.

    Alternative wäre vielleicht ein Blick auf die BeginInvoke-Methode einen Blick wert, sofern du Invoke benutzt. Die von mir genannte blockiert den UI Thread nicht sofort sondern kann es auch verzögert tun.

    Viel performanter durch ein anderes Control dürfte es aber nicht werden.

    PS: GetMessageW ist eine native Methode, welche durchaus beim aktualisieren einer DP aufgerufen werden kann, hängt aber nicht zwingend direkt damit zusammen.


    Tom Lambert - .NET (C#) MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Freitag, 13. November 2015 17:32
    Moderator
  • Hi,
    wenn die Anzahl der Elemente in der ItemList konstant ist, dann wäre auch ein Grid Denbar, welches in jeder Zelle konkrete Eigenschaften anzeigt. Das dürfte schneller sein als ein ListView.

    --
    Viele Grüsse
    Peter Fleischer (MVP, Partner)
    Meine Homepage mit Tipps und Tricks
    Kommas richtig setzen!
    Schüler sagen, Lehrer haben es gut.
    Schüler, sagen Lehrer, haben es gut

    Freitag, 13. November 2015 17:36
  • Vielen Dank für eure Antworten,

    Das Problem ist,  dass dieses Programm auf einer Maschine zum Einsatz kommt, wo der Benutzer sofort jede Abweichung visualisiert bekommen soll. Habs schon mal probiert mit 1s probiert, leider dasselbe Ergebnis. Das andere Problem ist ja auch noch, dass das ganze auf einem PC mit Intel DualCore und Standard Intel HD Grafikkarte laufen soll, also von vornerein nicht die besten Vorraussetzungen,....  

    Das grundsätzliche Problem ist eigentlich, dass ohne Exception auf einmal der "Standard" Microsoft UDP-Client kein Receive Event mehr feuert...

    Die ItemList wächst auf 20 Elemente und danach wird das letzte Element gelöscht und das neue am Anfang eingefügt.... Es wird eigentlich nicht viel dargestellt, nur upgedatet.

    Freitag, 13. November 2015 18:20
  • Hallo Hartl_D,

    ich weiß nicht, ob dieser Vorschlag genau ist, was du suchst, aber kannst ihn ja mal wenigstens im Hinterkopf behalten:

    Wenn du das erhalten der Daten in einem anderen (Hintergrund-) Thread laufen lässt und in deinem Haupt-Thread die UI hast, könnten diese unabhängig arbeiten.

    Ferner bräuchtest du noch einen geteilten speicher (static volatile ?). Wenn die Verbindung nun neue Daten hat, gibt diese die Daten an den Speicher weiter.

    Dein Haupt-Thread indes fragt immer wieder, ob es Änderungen gab (nimm einfach einen Zähler). Sofern die Daten aktualisiert wurden, kann dieser Thread sich diese Daten nehmen und aufbereiten und anzeigen.

    Der Vorteil wäre, dass sich beide Threads nicht blocken könnten. Bzw. es währe dann die Aufgabe des Systems zu sorgen, dass beide Threads aktiv bleiben.



    © 2015 Thomas Roskop
    Germany //  Deutschland

    Samstag, 14. November 2015 13:05
  • Hi,
    ich habe mal eine kleine Demo getestet mit Thomas' Anregungen. Das ListView-Steuerelement verbraucht enorm viel Zeit bei der Aktualisierung, insbesondere, weil im DataTemplate wieder ein ListView eingebaut ist. Wenn aller 100 msec neue Daten kommen und jede Sekunde die Oberfläche mit dem ListView aktualisiert wird, dann ruckelt es auch bei meinem recht flotten Rechner. Dabei arbeiten neben dem Haupt-Thread der Datenempfang in einem separaten Thread und die Aktualisierung wird auch in einem separaten Thread angestoßen.

    Man kann das Flackern nur verhindern, wenn man auf eine Neuformatierung der Oberfläche verzichtet und nur den anzuzeigenden Inhalt aktualisiert. Das bedeutet, dass die Anzeige einmalig vollständig aufgebaut wird und dann bezüglich Größen, Lagen und Abständen unverändert bleibt.


    --
    Viele Grüsse
    Peter Fleischer (MVP, Partner)
    Meine Homepage mit Tipps und Tricks
    Kommas richtig setzen!
    Schüler sagen, Lehrer haben es gut.
    Schüler, sagen Lehrer, haben es gut


    Sonntag, 15. November 2015 04:48
  • Hallo, danke, wie kann Ich eine Neuformatierung der Oberfläche verhindern?
    Sonntag, 15. November 2015 13:05
  • Hi,
    Du strukturierst die Oberfläche (alle Container, Steuerelemente) mit festen Größen (Höhen, Breiten, Abständen) und aktualisierst nur die Anzeigen direkt in den Steuerelementen (Label, TextBlock). Das läuft bei mir recht flüssig. Diese Aktualisierung kann man auch noch verzögern, indem nicht alle Änderungen sofort aktualisiert werden, sondern nur regelmäßig z.B. nach 1 Sekunde. Das bedeutet aber etwas mehr Aufwand im ViewModel.

    --
    Viele Grüsse
    Peter Fleischer (MVP, Partner)
    Meine Homepage mit Tipps und Tricks
    Kommas richtig setzen!
    Schüler sagen, Lehrer haben es gut.
    Schüler, sagen Lehrer, haben es gut

    Sonntag, 15. November 2015 13:43
  • Hi,
    nachfolgend mal eine kleine Demo, die annähernd Deinen Anforderungen entspricht. Ich habe den Takt des Datenempfangs auf 50 Millisekunden gesetzt und bei meinem Prozessor (5 Jahre alter Intel 2,67 GHz) ruckelt die Oberfläche nicht.

    XAML:

    <Window x:Class="Window06"
            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:WpfApplication1VB"
            mc:Ignorable="d"
            Title="Window06" Height="300" Width="400">
      <Window.Resources>
        <local:Window06VM x:Key="vm"/>
        <local:Window06Converter x:Key="conv"/>
        <DataTemplate x:Key="DataTemplate">
          <StackPanel Orientation="Horizontal" 
                      DataContext="{Binding}">
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[0]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[1]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[2]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[3]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[4]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[5]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[6]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[7]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[8]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
            <Border BorderBrush="{Binding ErrorCode, Converter={StaticResource conv}}" 
                    BorderThickness="3" 
                    DataContext="{Binding AnalogOutputs[9]}"
                    Visibility="{Binding IsEnabled, Converter={StaticResource conv}}">
              <StackPanel Width="25" >
                <TextBlock Text="{Binding MeasValue1}" />
                <TextBlock Text="{Binding MeasValue2}" />
              </StackPanel>
            </Border>
          </StackPanel>
        </DataTemplate>
      </Window.Resources>
      <StackPanel DataContext="{Binding Source={StaticResource vm}}">
        <Label Content="{Binding Info}"/>
        <DataGrid ItemsSource="{Binding ItemList}"
                  ScrollViewer.VerticalScrollBarVisibility="Auto"
                  AutoGenerateColumns="False">
          <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Position}"/>
            <DataGridTemplateColumn CellTemplate="{StaticResource DataTemplate}" 
                                    Width="250"/>
          </DataGrid.Columns>
        </DataGrid>
      </StackPanel>
    </Window>

    Code dazu:

    Public Class Window06VM
      Implements INotifyPropertyChanged
      Public Sub New()
        'Call (New Threading.Thread(AddressOf RefreshThread)).Start()
        If Not DesignerProperties.GetIsInDesignMode(New DependencyObject) Then
          Call (New Threading.Thread(AddressOf LoadData)).Start()
        End If
      End Sub
    
      Private Sub RefreshThread()
        Do
          Threading.Thread.Sleep(1000)
          Application.Current.Dispatcher.Invoke(Sub()
                                                  cvs.View.Refresh()
                                                End Sub)
        Loop
      End Sub
    
      Private zeit1 As Date = Now
    
      Private Sub LoadData()
        Dim rnd As New Random
        Do
          Threading.Thread.Sleep(50)
          Dim zeit2 = Now
          If col.Count < 20 Then
            Application.Current.Dispatcher.Invoke(Sub()
                                                    Dim item1 As New Window06Data1 With {.Position = rnd.Next(1, 100).ToString}
                                                    col.Add(item1)
                                                    For index = 1 To 10
                                                      item1.AnalogOutputs.Add(New Window06Data2())
                                                    Next
                                                  End Sub)
          Else
            Dim i = rnd.Next(0, col.Count)
            col(i).Position = rnd.Next(1, 100).ToString
            Dim k = rnd.Next(0, col(i).AnalogOutputs.Count)
            Dim spalte As Window06Data2 = col(i).AnalogOutputs(k)
            spalte.MeasValue1 = rnd.Next(1, 100).ToString
            spalte.MeasValue2 = rnd.Next(1, 100).ToString
            spalte.ErrorCode = rnd.Next(1, 101)
            spalte.IsEnabled = rnd.NextDouble > 0.2
            Info = String.Format("Zeitintervall {0}", (zeit2 - zeit1).Milliseconds)
            zeit1 = zeit2
          End If
        Loop
      End Sub
    
      Private col As New ObservableCollection(Of Window06Data1)
      Private cvs As CollectionViewSource
    
      Public ReadOnly Property ItemList As ICollectionView
        Get
          If cvs Is Nothing Then
            cvs = New CollectionViewSource
            cvs.Source = col
          End If
          Return cvs.View
        End Get
      End Property
    
      Private _info As String
      Public Property Info As String
        Get
          Return Me._info
        End Get
        Set(value As String)
          If Me._info <> value Then
            Me._info = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
    #Region " PropertyChanged"
      Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
      Private Sub OnPropertyChanged(<CallerMemberName> Optional propname As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
      End Sub
    
    #End Region
    
    End Class
    
    Public Class Window06Data1
      Implements INotifyPropertyChanged
    
      Private _position As String
      Public Property Position() As String
        Get
          Return Me._position
        End Get
        Set(value As String)
          Me._position = value
          OnPropertyChanged()
        End Set
      End Property
    
      Public Property AnalogOutputs As List(Of Window06Data2) = New List(Of Window06Data2)
    
    #Region " PropertyChanged"
      Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
      Private Sub OnPropertyChanged(<CallerMemberName> Optional propname As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
      End Sub
    
    #End Region
    End Class
    
    Public Class Window06Data2
      Implements INotifyPropertyChanged
    
      Private _measValue1 As String
      Public Property MeasValue1() As String
        Get
          Return Me._measValue1
        End Get
        Set(value As String)
          If Me._measValue1 <> value Then
            Me._measValue1 = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
      Private _measValue2 As String
      Public Property MeasValue2() As String
        Get
          Return Me._measValue2
        End Get
        Set(value As String)
          If Me._measValue2 <> value Then
            Me._measValue2 = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
      Private _isEnabled As Boolean
      Public Property IsEnabled() As Boolean
        Get
          Return Me._isEnabled
        End Get
        Set(value As Boolean)
          If Me._isEnabled <> value Then
            Me._isEnabled = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
      Private _errorCode As Integer
      Public Property ErrorCode() As Integer
        Get
          Return Me._errorCode
        End Get
        Set(value As Integer)
          If Me._errorCode <> value Then
            Me._errorCode = value
            OnPropertyChanged()
          End If
        End Set
      End Property
    
    #Region " PropertyChanged"
      Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
      Private Sub OnPropertyChanged(<CallerMemberName> Optional propname As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
      End Sub
    
    #End Region
    End Class
    
    Public Class Window06Converter
      Implements IValueConverter
    
      Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
        If targetType Is GetType(Visibility) AndAlso value.GetType Is GetType(Boolean) Then
          Return If(CType(value, Boolean), Visibility.Visible, Visibility.Hidden)
        End If
        If targetType Is GetType(Brush) AndAlso value.GetType Is GetType(Integer) Then
          Select Case CType(value, Integer)
            Case < 50
              Return Brushes.White
            Case < 100
              Return Brushes.Orange
            Case 100
              Return Brushes.Red
          End Select
    
        End If
        Return Nothing
      End Function
    
      Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
        Throw New NotImplementedException()
      End Function
    End Class


    --
    Viele Grüsse
    Peter Fleischer (MVP, Partner)
    Meine Homepage mit Tipps und Tricks
    Kommas richtig setzen!
    Schüler sagen, Lehrer haben es gut.
    Schüler, sagen Lehrer, haben es gut

    • Als Antwort markiert Hartl_D Montag, 16. November 2015 13:11
    Montag, 16. November 2015 08:05
  • Vielen Dank,

    hab jetzt zwar eine flüssige Darstellung und sehr geringe CPU Auslastung auch auf meinem Dual Core,

    aber ab und zu verabschiedet sich der UDP Client (Keine Exception,etc....). Bzw. die ReceiveCallBack Funktion wird nicht mehr aufgerufen !? Hat jemand Erfahrung mit einem ähnlichen Fehler?

    private void ReceiveCallback(IAsyncResult ar)
        {
          try
          {
            if (ar == currentAsyncResult)
            {
              MulticastListener receiver = (MulticastListener)(ar.AsyncState);
    
              UdpClient udpClient = receiver.UdpClient;
              IPEndPoint ipEndPoint = receiver.LocalIPEndPoint;
              if (udpClient == null || ipEndPoint == null)
              {
                log.FatalFormat("udpClient = {0}, ipEndPoint = {1}", udpClient, ipEndPoint);
              }
              else
              {
                byte[] receiveBytes = udpClient.EndReceive(ar, ref ipEndPoint);
                EventHandler<ReceivedEventArgs> handler = DataReceived;
                if (handler != null)
                {
                  handler(this, new ReceivedEventArgs(receiveBytes));
                }
              }
            }
            lock (syncRoot)
            {
              if (udpClient != null && udpClient.Client != null && udpClient.Client.IsBound)
              {
                BeginReceive();
              }
            }
          }
          catch (ObjectDisposedException)
          {
            // expected exception fired when we close - swallow it up
          }
          catch (ArgumentException)
          {
            // expected exception fired when we close - swallow it up
          }
          catch (Exception ex)
          {
            log.FatalFormat("ReceiveCallback exception: {0}", ex);
            throw;
          }
        }
    
        private void BeginReceive()
        {
          AsyncCallback receiveCallback = new AsyncCallback(ReceiveCallback);
          currentAsyncResult = UdpClient.BeginReceive(receiveCallback, this);
        }

    Montag, 16. November 2015 13:14
  • Hi,
    mit dem Fragment kann ich nichts anfangen. Es sieht recht chaotisch aus und weit weg von typischer Kapselung. Nicht zu erkennen ist die Empfangs-Schleife.

    --
    Viele Grüsse
    Peter Fleischer (MVP, Partner)
    Meine Homepage mit Tipps und Tricks
    Kommas richtig setzen!
    Schüler sagen, Lehrer haben es gut.
    Schüler, sagen Lehrer, haben es gut

    Montag, 16. November 2015 17:03
  • Hallo Hartl_D,

    Ein Paar Inputs von meiner Seite:

    Ab .NET 4.5 kannst Du bei der WPF einen Delay eintragen. D.h. du kannst sagen, dass das Databinding nur z.B. alle 500ms ausgelöst werden soll. Damit kannst Du den Dispatcher entlasten:

    <TextBlock Text="{Binding Name, Delay=500}"/>
    

    http://www.jonathanantoine.com/2011/09/21/wpf-4-5-part-4-the-new-bindings-delay-property/

    Es gibt starke Performance Unterschiede zwischen verschiedenen Grids. Probier doch mal die Grids von Telerik, DevExpress, etc aus. Die sind oft schon out-of-the-box viel besser als das "normale" DataGrid.

    Schau Dir mal diese Anwendung an:

    https://wpfrealtime.codeplex.com/

    Der Artikel dazu geht voll in die Details und am Schluss kommt ein "fast Echtzeit" Dashboard in WPF raus. Ich glaube, das ist genau das, was Du brauchst:

    http://www.codeproject.com/Articles/326641/WPF-MVVM-Real-Time-Trading-Application

    Die .NET Reactive Extensions (.NET Rx) könnten Dir auch weiterhelfen:

    http://stackoverflow.com/questions/19452935/wpf-realtime-chart-application-architecture

    Gruss MM


    PS: Please mark as answer if helpful. Thanks!
    Blog: http://www.manuelmeyer.net
    Twitter: https://twitter.com/manumeyer1

    • Als Antwort markiert Hartl_D Mittwoch, 25. November 2015 13:37
    Dienstag, 24. November 2015 09:55