2017-09-27 7 views
-2

Lorsque j'essaie de mettre à jour un ObservableCollection que j'utilise dans mon XAML à partir d'un thread séparé puis le thread ui, j'obtiens une exception XamlParseException qui dit que le DependencySource doit être créé sur le même Thread le DependencyObject. J'utilise Caliurn Micro pour lier le ViewModel à la vue.WPF Mise à jour de ObservableList à partir de la tâche async throws XamlParseException

J'ai essayé plusieurs moyens d'atteindre mon objectif, et celui ci-dessous semble être l'approche la plus raisonnable pour moi. Je passe le SyncronizationContext de l'interface utilisateur à la tâche afin qu'il puisse mettre à jour l'interface utilisateur après avoir effectué la lourde charge de travail.

Qu'est-ce que je fais de mal?

Voir

<Window x:Class="QuickScope.Views.NavigatorView" 
    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:QuickScope.Views" 
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
    xmlns:quickScope="clr-namespace:QuickScope" 
    mc:Ignorable="d"> 
<Grid> 
    <TextBox Grid.Row="0" 
      Name="TextBox" 
      HorizontalContentAlignment="Stretch" 
      VerticalContentAlignment="Center" 
      Margin="5,0,5,5" 
      Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> 
    </TextBox> 
    <Popup PlacementTarget="{Binding ElementName=TextBox}"> 
     <ListView x:Name="ItemList" ItemsSource="{Binding Items}" SelectedItem ="{Binding SelectedItem}"> 
      <ListView.ItemTemplate> 
       <DataTemplate> 
        <WrapPanel> 
         <Image Source="{Binding IconSource}" Width="20" Height="20"></Image> 
         <Label Content="{Binding SearchName}"></Label> 
        </WrapPanel> 
       </DataTemplate> 
      </ListView.ItemTemplate> 
     </ListView> 
    </Popup> 
</Grid> 

ViewModel

using System; 
using System.Collections.ObjectModel; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Input; 
using System.Windows.Interop; 
using System.Windows.Threading; 
using Caliburn.Micro; 
using QuickScope.Views; 

namespace QuickScope.ViewModels 
{ 
public class NavigatorViewModel : Screen 
{ 
    private readonly ItemService _itemService; 

    public NavigatorViewModel() 
    { 
     _itemService = new ItemService(); 
     Items = new ObservableCollection<ItemViewModel>(); 

    } 

    public ObservableCollection<ItemViewModel> Items { get; } 

    public ItemViewModel SelectedItem { get; set; } 

    private string _searchText; 

    public string SearchText 
    { 
     get => _searchText; 
     set 
     { 
      _searchText = value; 
      NotifyOfPropertyChange(() => SearchText); 
      UpdateItemList(_searchText); 
     } 
    } 

    private void UpdateItemList(string searchText) 
    { 
     Items.Clear(); 

     var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

     Task.Factory.StartNew(() => 
     { 
      //the long running workload 
      var items = _itemService.GetByFilter(searchText); 

      //update the ui 
      Task.Factory.StartNew(() => 
      { 
       foreach (var item in items) 
        Items.Add(item); 

       if (items.Any()) 
       { 
        SelectedItem = items.First(); 
        NotifyOfPropertyChange(() => SelectedItem); 
       } 
      }, CancellationToken.None, TaskCreationOptions.None, uiScheduler); 
     }); 
    } 
} 
} 

EDIT

J'ai essayé l'approche de Shadrix, mais malheureusement ça n'a pas marché non plus. J'ai également essayé les réponses de marked duplicate de Peter Dunihos mais je n'ai pas réussi non plus (j'obtiens la même exception de XamlParseException que décrite ci-dessus).

La dernière chose que j'ai essayée est la méthode de génération CM dans OnUIThread, qui enveloppe fondamentalement le Dispatcher.CurrentDispatcher. Évidemment (compte tenu de mes deux dernières tentatives ratées), il a échoué aussi. La mise en œuvre actuelle ressemble à quelque chose comme les suivants (i supprimé autres, les propriétés non pertinentes et méthodes):

public class NavigatorViewModel : Screen 
{ 
public NavigatorViewModel() 
{ 
    Items = new ObservableCollection<ItemViewModel>(); 
} 

public ObservableCollection<ItemViewModel> Items { get; set; } 

public ItemViewModel SelectedItem { get; set; } 

private string _searchText; 

public string SearchText 
{ 
    get => _searchText; 
    set 
    { 
     _searchText = value; 
     NotifyOfPropertyChange(() => SearchText); 
     UpdateItemList(_searchText); 
    } 
} 

private void UpdateItemList(string searchText) 
{ 
    Items.Clear(); 

    var updater = new ItemUpdater(); 
    updater.ItemsUpdated += (s, e) => { 
     OnUIThread(() => 
     { 
      var items = ((ItemsUpdatedEventArgs) e).Items; 
      foreach (var item in items) 
      { 
       Items.Add(item); 
      } 
      NotifyOfPropertyChange(() => Items); 
     }); 
    }; 
    var updateThread = new Thread(updater.GetItems); 
    updateThread.Start(searchText); 
} 
} 

public class ItemUpdater 
{ 
public event EventHandler ItemsUpdated; 
private readonly ItemService _itemService; 

public ItemUpdater() 
{ 
    _itemService = new ItemService(); 
} 

public void GetItems(object searchText) 
{ 
    var items = _itemService.GetByFilter((string)searchText); 

    ItemsUpdated?.Invoke(this, new ItemsUpdatedEventArgs(items)); 
} 
} 

public class ItemsUpdatedEventArgs : EventArgs 
{ 
public ItemsUpdatedEventArgs(IList<ItemViewModel> items) 
{ 
    Items = items; 
} 

public IList<ItemViewModel> Items { get; } 
} 

Il me rend fou que je ne suis pas en mesure de résoudre ce problème, donc s'il y a quelqu'un là-bas qui serait J'aime aider un jeune junior, je l'apprecierais fortement. :)

Vous pouvez trouver le code source complet here.

Merci à tous!

Répondre

1

Utilisez le courant de thread d'interface utilisateur Dispatcher: merci

//update the ui 
Application.Current.Dispatcher.BeginInvoke(new Action(() => 
{ 
    foreach (var item in items) 
    { 
     Items.Add(item); 
    } 

    if (items.Any()) 
    { 
     SelectedItem = items.First(); 
     NotifyOfPropertyChange(() => SelectedItem); 
    } 
})); 
+0

vous pour votre réponse, malheureusement, il n'a pas résolu mon problème. Je reçois toujours l'exception décrite (voir la question éditée pour plus d'informations) – CiniMod