2010-08-18 7 views
3

Je suis totalement nouveau sur WPF, j'ai créé une simple application WPF qui répertorie la structure complète du lecteur (dossier, fichiers) en TreeView, car ce processus prend du temps, j'ai essayé d'utiliser un thread pour exécuter la méthode GetFolderTree() et empêcher l'IU de ne plus répondre, mais je rencontre des problèmes, j'ai créé une classe nommée FolderBrowser où j'ai tout ce code de collecte de structure, dans cette classe je crée une nouvelle instance de TreeViewItem détient structure d'entraînement à la fin il est utilisé comme valeur de retour pour alimenter le TreeView, Voici le code:Problème lors de l'utilisation du multithread avec l'application WPF

using System.IO; 
using System.Windows.Controls; 

namespace WpfApplication 
{ 
    public class FolderBrowser 
    { 
    private TreeViewItem folderTree; 
    private string rootFolder; 

    public FolderBrowser(string path) 
    { 
     rootFolder = path; 
     folderTree = new TreeViewItem(); 
    } 

    private void GetFolders(DirectoryInfo di, TreeViewItem tvi) 
    { 
     foreach (DirectoryInfo dir in di.GetDirectories()) 
     { 
      TreeViewItem tviDir = new TreeViewItem() { Header = dir.Name };   

      try 
      { 
       if (dir.GetDirectories().Length > 0) 
        GetFolders(dir, tviDir); 

       tvi.Items.Add(tviDir); 
       GetFiles(dir, tviDir); 
      } 
      //catch code here 
     } 

     if (rootFolder == di.FullName) 
     { 
      folderTree.Header = di.Name; 
      GetFiles(di, folderTree); 
     } 
    } 

    private void GetFiles(DirectoryInfo di, TreeViewItem tvi) 
    { 
     foreach (FileInfo file in di.GetFiles()) 
     { 
      tvi.Items.Add(file.Name); 
     } 
    } 

    public TreeViewItem GetFolderTree() 
    { 
     DirectoryInfo di = new DirectoryInfo(rootFolder); 
     if (di.Exists) 
     {     
      GetFolders(di, folderTree);         
     } 

     return folderTree; 
    } 
    } 
} 

Comment pourrais-je créer de nouvelles instances de contrôle dans ce nouveau fil?

Merci à l'avance

+0

de travail sur un petit échantillon en utilisant XAML/MVVM ... afficherons une nouvelle réponse dans quelques mins –

Répondre

1

Vous ne pouvez pas interagir avec l'interface utilisateur dans un fil, mais le fil de l'interface utilisateur, mais vous pouvez utiliser l'objet Dispatcher interface utilisateur pour exécuter un rappel à l'intérieur du thread d'interface utilisateur:

System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() => { /* your UI code here */ })); 

Une façon plus "propre" d'obtenir le répartiteur consiste à le passer de l'objet de l'interface utilisateur au thread/classe qui génère le thread, lorsque vous le créez.

Edit:

Je recommande la solution de HCL sur le mien. Cependant, vous avez demandé dans les commentaires comment faire pour que cela fonctionne sans dupliquer ce gros bloc de code partout:

Dans votre constructeur, faites référence à un objet Dispatcher et stockez-le dans votre classe.

Ensuite, faire une méthode comme ceci:

private void RunOnUIThread(Action action) 
{ 
    this.dispatcher.Invoke(action); 
} 

et l'appeler comme ceci:

RunOnUIThread(() => { /* UI code */ }); 

Vous pouvez envelopper de gros blocs de code ainsi:

RunOnUIThread(() => 
{ 
    Console.WriteLine("One statement"); 
    Console.WriteLine("Another statement"); 
}); 

Si vous essayez de repasser trop de ce code dans l'interface utilisateur, mais ce ne sera pas différent que si vous avez exécuté tout le code dans l'interface utilisateur. ead, et sera toujours accrocher l'interface utilisateur.

Cependant, la suggestion de HCL de peuplant une structure d'arbre personnalisé, au lieu d'avoir ce code savoir quoi que ce soit au sujet des contrôles de l'interface utilisateur, est beaucoup mieux :)

+0

Autant que je sache, il doit créer chaque TreeViewItem dans l'UI-Thread. Cette solution peut donc être un peu difficile (mais possible). Cependant, je ne suis pas sûr si c'est comme je le pense. – HCL

+0

J'ai utilisé votre code dans la méthode GetFolders Application.Current.Dispatcher.Invoke (new System.Action (() => {tviDir = new TreeViewItem() {En-tête = dir.Name};})); et cela a fonctionné mais qu'en est-il des autres endroits où ce tviDir est modifié, devrais-je utiliser le même Application.Current.Dispatcher.Invoke (...) pour chaque ligne/bloc de code, y at-il une syntaxe qui me permette d'embrasser toute la méthode au lieu ? – Albert

+0

Oh ok, je voulais savoir s'il y avait un moyen de le faire directement avec les contrôles de l'interface utilisateur, mais il semble que je ferais mieux de les éviter à ce sujet. Merci – Albert

2

Si la solution de Merkyn Morgan-Graham ne fonctionne pas (voir mon commentaire , Je ne suis pas sûr), je propose de créer une structure-objet indépendante qui contient vos objets-répertoire.

Pour que cela fonctionne avec un BackgroundWorker. Si c'est fini, utilisez cette structure pour construire les nœuds TreeViewItem directement (parce que ce n'est pas si lent si vous en avez quelques centaines) ou utilisez-le comme ViewModel (mieux).

BackgroundWorker bgWorker = new BackgroundWorker(); 
bgWorker.DoWork += (s, e) => { 
    // Create here your hierarchy 
    // return it via e.Result     
}; 
bgWorker.RunWorkerCompleted += (s, e) => { 
    // Create here your TreeViewItems with the hierarchy from e.Result     
}; 
bgWorker.RunWorkerAsync(); 
+0

+1, car il est préférable que votre vue et votre implémentation soient indépendantes les unes des autres et utilisez la liaison de données pour les assembler (MVVM). –

+0

J'ai essayé votre exemple de code, mais je reçois toujours "Le thread appelant doit être STA, car de nombreux composants de l'interface utilisateur l'exigent." quand il essaie de créer une nouvelle instance de TreeViewItem – Albert

+0

@Albert: Je suppose que vous essayez de créer les TreeViewItems dans l'événement DoWork. Comme je l'ai déjà écrit, c'est autant que je sache pas possible. Essayez de créer une structure qui n'est pas des objets visuels (plus exactement, de DependencyObjects) comme TreeViewItems sont, puis dans RunWorkerComplete, utilisez ceci comme une source pour construire vos éléments ou comme VM. – HCL

0

Je vous suggère de regarder dans les modèles hiérarchiques au lieu de construire manuellement l'arborescence. Vous pouvez créer la structure entière dans un thread d'arrière-plan, puis lier la structure de données résultante à votre arborescence.

0

Voici une réponse en utilisant MVVM (enfin, au moins la partie vue/modèle de vue) et les threads de travail en arrière-plan. Cela utilise le travailleur d'arrière-plan pour remplir le modèle de vue (récursivement) et le modèle de données hiérarchique pour lier la vue au modèle de vue. Notez que nous avons toujours le même problème de thread, car le thread de travail ne peut pas modifier ObservableCollection. Ainsi, nous utilisons le gestionnaire d'événements RunWorkerCompleted (qui s'exécute dans le thread UI) pour remplir la collection.

MainWindow.xaml:

<Window 
    x:Class="WpfApplication.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:WpfApplication"> 
    <StackPanel> 
     <TextBlock Text="Contents:" /> 
     <TreeView ItemsSource="{Binding BaseDirectory.Contents}"> 
      <TreeView.Resources> 
       <HierarchicalDataTemplate 
         DataType="{x:Type local:FileSystemEntry}" 
         ItemsSource="{Binding Contents}"> 
        <TextBlock Text="{Binding Name}" /> 
       </HierarchicalDataTemplate> 
      </TreeView.Resources> 
     </TreeView> 
    </StackPanel> 
</Window> 

MainWindowViewModel.cs:

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.IO; 

namespace WpfApplication 
{ 
    public class MainWindowViewModel 
    { 
     public MainWindowViewModel() 
     { 
      this.BaseDirectory = new FileSystemEntry("C:\\"); 
      this.BaseDirectory.Populate(); 
     } 

     public FileSystemEntry BaseDirectory { get; private set; } 
    } 

    public class FileSystemEntry 
    { 
     public FileSystemEntry(string path) 
      : this(new DirectoryInfo(path)) 
     { 
     } 

     private FileSystemEntry(DirectoryInfo di) 
      : this() 
     { 
      this.Name = di.Name; 
      this.directoryInfo = di; 
     } 

     private FileSystemEntry(FileInfo fi) 
      : this() 
     { 
      this.Name = fi.Name; 
      this.directoryInfo = null; 
     } 

     private FileSystemEntry() 
     { 
      this.contents = new ObservableCollection<FileSystemEntry>(); 
      this.Contents = new ReadOnlyObservableCollection<FileSystemEntry>(this.contents); 
     } 

     public string Name { get; private set; } 

     public ReadOnlyObservableCollection<FileSystemEntry> Contents { get; private set; } 

     public void Populate() 
     { 
      var bw = new BackgroundWorker(); 

      bw.DoWork += (s, e) => 
      { 
       var result = new List<FileSystemEntry>(); 

       if (directoryInfo != null && directoryInfo.Exists) 
       { 
        try 
        { 
         foreach (FileInfo file in directoryInfo.GetFiles()) 
          result.Add(new FileSystemEntry(file)); 

         foreach (DirectoryInfo subDirectory in 
          directoryInfo.GetDirectories()) 
         { 
          result.Add(new FileSystemEntry(subDirectory)); 
         } 
        } 
        catch (UnauthorizedAccessException) 
        { 
         // Skip 
        } 
       } 

       System.Threading.Thread.Sleep(2000); // Todo: Just for demo purposes 

       e.Result = result; 
      }; 

      bw.RunWorkerCompleted += (s, e) => 
      { 
       var newContents = (IEnumerable<FileSystemEntry>)e.Result; 

       contents.Clear(); 
       foreach (FileSystemEntry item in newContents) 
        contents.Add(item); 

       foreach (FileSystemEntry subItem in newContents) 
        subItem.Populate(); 
      }; 

      bw.RunWorkerAsync(); 
     } 

     private ObservableCollection<FileSystemEntry> contents; 
     private DirectoryInfo directoryInfo; 
    } 
} 
Questions connexes