2016-03-25 10 views
1

Ich habe Probleme, das Ergebnis der asynchronen Methode an die UI zu übermitteln.Asynchrone PointCollection-Änderung in UI propagieren

XAML

<Window x:Class="COVMin.MainWindow" 
    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:COVMin"   
    mc:Ignorable="d" 
    Title="MainWindow" Height="350" Width="525"> 
    <Window.DataContext> 
     <local:MainViewModel/> 
    </Window.DataContext> 
    <Grid Margin="0,0,0,0"> 
     <Border BorderThickness="1" BorderBrush="Black" Background="White" Margin="4" VerticalAlignment="Top" Height="170"> 
     <Polygon Points="{Binding Points}" Stretch="Fill" Fill="Black" Opacity="0.8" /> 
    </Border> 
    <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Height="24" Marin="0,0,0,10" VerticalAlignment="Bottom" Width="75" Command="{Binding DrawPointsCommand}"/> 
    </Grid> 

Ansichtsmodell

class MainViewModel : ViewModelBase 
{   
    private PointCollection points { get; set; } 

    public PointCollection Points 
    { 
     get { return this.points; } 
     set 
     {     
      this.points = value; 
      OnPropertyChanged("Points"); 
     } 
    } 

    public ICommand DrawPointsCommand { get; private set; } 

    /// <summary> 
    /// Simplified, in real it´s long time operation causing UI to freeze. 
    /// </summary>   
    private Task<PointCollection> ConvertToPointCollection() 
    { 
     return Task.Run<PointCollection>(() => 
     { 
      PointCollection points = new PointCollection(); 
      points.Add(new System.Windows.Point(0, 6236832));     
      points.Add(new System.Windows.Point(255, 6236832)); 

      return points; 
     }); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    private async Task<PointCollection> Process() 
    {    
     this.Points = await ConvertToPointCollection(); 
     return this.Points; 
    } 

    /// <summary> 
    /// Method calling long-time operation bound to button as a Command. 
    /// </summary> 
    private async void GetValues() 
    { 
     this.Points = await Process();    
    } 

    /// <summary> 
    /// Constructor. 
    /// </summary> 
    public MainViewModel() 
    { 
     this.DrawPointsCommand = new DelegateCommand(GetValues); 
    } 
} 

ViewModelBase

/// <summary> 
    /// Base class for PropertyChanged event handling. 
    /// </summary>   
    class ViewModelBase : INotifyPropertyChanged 
    { 
     public event PropertyChangedEventHandler PropertyChanged; 

     public void OnPropertyChanged(string name) 
     { 
      if (PropertyChanged != null) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(name)); 
      } 
     } 
    } 

DelegateCommand Klasse

public class DelegateCommand : ICommand 
{   
    private readonly Action _action; 

    public DelegateCommand(Action action) 
    { 
     _action = action; 
    } 

    public void Execute(object parameter) 
    { 
     _action(); 
    } 

    public bool CanExecute(object parameter) 
    { 
     return true; 
    } 

    public event EventHandler CanExecuteChanged;   
} 

Das Problem ist mit OnPropertyChange, die verursacht System.ArgumentException sagt mir, dass ich DependencySource für den gleichen Thread wie DependencyObject erstellen muss. Ich habe einige Stunden damit verbracht, mit den Dispatchern zu versuchen und was nicht, aber immer noch nicht gut.

+0

_ "Erzähl mir, dass ich DependencySource auf dem gleichen Thread wie DependencyObject erstellen muss" _ - was Sie tun. Technisch gesehen macht dies Ihre Frage zu einem Duplikat der kanonischen Antwort "Dispatcher verwenden". Das heißt, Ihre Frage ist zu vage, um sicher zu sein, dass dies alles ist, was Sie brauchen. Mit der "Task" -basierten Methode sollten Sie in der Lage sein, das Ergebnis zu "erwarten" und eine explizite Interaktion mit dem Objekt "Dispatcher" zu vermeiden. Aber Ihr Codebeispiel oben zeigt nicht einmal, wie 'Process()' aufgerufen wird. Bitte stellen Sie eine gute [mcve] zur Verfügung, die das Problem zuverlässig reproduziert. –

+0

Vielen Dank für Ihre Antwort. Ich habe das obige Beispiel aktualisiert, aber ich bin mir nicht sicher, was ich sonst noch hinzufügen sollte. – Tomas

+0

Der Artikel unter dem Link, den ich im vorherigen Kommentar angegeben habe, enthält alle Informationen, die Sie wissen müssen, "was Sie sonst noch dort hinstellen sollten". –

Antwort

0

Das Hauptproblem ist, dass PointCollection ist ein DependencyObject und so gehört der Thread, der es erstellt. Normalerweise können Sie solche Objekte nicht in anderen Threads verwenden. Die Verwendung von (explizit oder implizit mit await als Codebeispiel) würde hier nicht helfen, da es nicht der UI-Thread ist, der das Objekt besitzt. In der Tat versucht es, dieses Objekt im UI-Thread zu verwenden, der das Problem verursacht.

Es gibt jedoch eine wichtige Ausnahme für die Regel "Keine gemeinsame Nutzung über Threads", für DependencyObjects, die auch Freezable Objekte sind, wie PointCollection ist. Wenn Sie das Objekt im besitzenden Thread einfrieren, bevor ein anderer Thread darauf zugreifen kann, kann es in anderen Threads verwendet werden.

So können Sie Ihre ConvertToPointCollection() Methode ändern wie folgt aussehen:

private Task<PointCollection> ConvertToPointCollection() 
{ 
    return Task.Run<PointCollection>(() => 
    { 
     PointCollection points = new PointCollection(); 
     points.Add(new System.Windows.Point(0, 6236832));     
     points.Add(new System.Windows.Point(255, 6236832)); 

     points.Freeze(); 

     return points; 
    }); 
} 

Natürlich wird dies auch das Objekt verhindern, dass später geändert werden. Wenn Sie die Sammlung ändern müssen, müssen Sie einen anderen Ansatz wählen. Erstellen Sie beispielsweise im UI-Thread die Datei PointCollection, und verwenden Sie dann einen Zwischentyp (wie List<Point>), um neue Punkte von Ihrem Hintergrundthread an den UI-Thread zu übergeben. Der UI-Thread kann dann diese Punkte in die PointCollection kopieren.

+0

Vielen Dank Peter, das Einfrieren der Sammlung hat den Trick gemacht. – Tomas